Most caches can only shout “it’s stale”
Think about how a conventional cache tells you something changed. It either pushes you the whole new value, or — more often — it invalidates: it tells every reader “that key is dirty now,” and leaves them to stampede back and refetch the entire object. Both are blunt. When a pricing object has forty fields and one of them ticks, you’ve either re-sent thirty-nine unchanged fields or triggered a round-trip to re-read all forty. Multiply that by thousands of subscribers and a high change rate, and the invalidation model becomes the bottleneck you spend the next quarter working around.
The quiet problem is that the cache is modeling the wrong thing. Change in these systems is incremental — a price moves, a flag flips, one config key updates — but the cache only knows how to talk about whole values. Aeron Cache was built to speak the language change actually arrives in: deltas. We call it patch-native streaming, and once you have it, a class of systems gets dramatically simpler.
Two ideas that compose: merge patch and patch-mode subscriptions
The foundation is JSON Merge Patch (RFC 7386). It’s a small, elegant spec for partial updates to a JSON document, with three rules worth memorizing:
- A field present in the patch replaces the stored field (scalars and arrays replace wholesale).
- A nested object in the patch merges recursively into the stored object.
- A field set to
nullin the patch deletes that field.
Aeron Cache applies merge patch on writes through patchItem, so you can update one field of a stored JSON value without rewriting the rest. The mechanics and edge cases are in JSON Merge Patch.
The second idea carries that all the way to subscribers. Aeron Cache streaming supports patch-mode subscriptions: a subscriber can ask to receive only the PATCH_ITEM deltas — the changed fields — rather than full ADD_ITEM values. (You can also narrow with keyed subscriptions to follow only the keys you care about, and the subscribe acknowledgement acts as a barrier so you know exactly when your subscription is live and can safely reason about what you’ve seen.) The deep dive is at streaming subscriptions.
Put together: a writer patches one field; the cache streams just that field; every subscriber receives just that field.
Concrete: a pricing object on the wire
Say the stored value for key GBPUSD is:
{
"symbol": "GBPUSD",
"bid": 1.2710,
"ask": 1.2713,
"mid": 1.27115,
"venue": "LDN",
"tier": "institutional",
"updatedAt": "2026-10-10T09:00:00Z"
}
The bid moves. With a full-value model, every subscriber receives all seven fields again, or an invalidation that forces a refetch of all seven:
{
"symbol": "GBPUSD",
"bid": 1.2711,
"ask": 1.2713,
"mid": 1.27120,
"venue": "LDN",
"tier": "institutional",
"updatedAt": "2026-10-10T09:00:01Z"
}
With patch-native streaming, the writer sends a merge patch, and that’s exactly what crosses the wire to patch-mode subscribers:
{
"bid": 1.2711,
"mid": 1.27120,
"updatedAt": "2026-10-10T09:00:01Z"
}
Three fields instead of seven, and no refetch round-trip. And if a field needs to disappear — say the venue is temporarily withdrawn — the delta says so explicitly with null, which the merge semantics interpret as a delete:
{
"venue": null
}
The savings scale with two things you can’t control at runtime: how fat your objects are, and how often they change. The fatter and the busier, the more the full-value model costs you and the more patch-native streaming returns.
The piece that makes it ergonomic: the embedded object cache
Streaming deltas would be a chore if every subscriber had to reassemble the full object by hand. It doesn’t, because the embedded clients do it for you. EmbeddedObjectCache — available across Java, TypeScript, Python, and Rust — keeps a local mirror of structured JSON values and deep-merges PATCH_ITEM deltas into the stored object using RFC 7386 semantics: nested objects merge recursively, scalars and arrays replace, and a null field deletes it. So a patch-mode subscription that streams only the changed fields reconstructs the full, correct object locally without losing the untouched ones.
// Java: a locally-consistent, patch-fed mirror. Reads are local — no round-trip.
EmbeddedObjectCache prices = client.getObjectCache("fx-prices");
// The stream delivers only PATCH_ITEM deltas; the embedded cache deep-merges them.
ObjectNode gbpusd = prices.getLocal("GBPUSD"); // full object, always current
Price typed = prices.getLocalAs("GBPUSD", Price.class); // or deserialize into a POJO
A detail worth knowing: the string-valued EmbeddedAeronCache deliberately can’t do this — you can’t merge a delta into an opaque string — so it ignores PATCH_ITEM events and rejects patch-mode subscriptions. Patch-native streaming is specifically the province of the object cache, which is exactly where structured config and pricing data belong.
What this unlocks
Dynamic config and feature flags. This is the pattern the pieces were built for: store each service’s config as a JSON object, flip one flag with patchItem, and every instance running an EmbeddedObjectCache on a patch-mode subscription sees that one field change — field-by-field, locally consistent, no full-config redistribution and no refetch stampede. Every service reads its config from a local map with no network hop, yet stays current to the field.
Pricing and market-data fan-out. The example above, at scale. Ticks are deltas by nature, so streaming them as deltas means you ship the bytes that moved and nothing else, to every downstream consumer, with the embedded cache maintaining a complete book on each one.
The contrast is the point. Full-value invalidation treats every change as a reason to move or re-read the whole object. Patch-native streaming treats a change as what it is — a small, precise delta — and moves exactly that. For config distribution and pricing fan-out, that’s not an optimization at the margin; it’s a different and simpler way to build the system.
Start with JSON Merge Patch for the write-side semantics and streaming subscriptions for the subscription model, then wire up an EmbeddedObjectCache and watch your objects stay current one field at a time.