Skip to content

Rebase feature/aggregate-cache-fetchforwriting onto the shared JasperFx IAggregateWriteCache (jasperfx#674) #5251

Description

@jeremydmiller

#674 / JasperFx/jasperfx#676 moves IAggregateWriteCache into JasperFx.Events, so Marten, Polecat and Fisher share one second-level aggregate-snapshot cache. Marten's implementation is the existing local branch, rebased onto the promoted contract.

The branch

feature/aggregate-cache-fetchforwriting, built 2026-07-28, measured 2026-07-30, rebased onto V9.20.2. Two commits — 834e5a286 (Async lifecycle) and 5620d4039 (Inline lifecycle) — plus AGGREGATE_WRITE_CACHE_SPIKE.md. Local only, never pushed. 5/5 new Inline tests, 228/228 FetchForWriting regression, 1484 pass / 0 fail across EventSourcingTests at the time it was parked.

What changes on the rebase

Delete the Marten-local copies and use the promoted types:

was, on the branch now
Marten.Events.Fetching.IAggregateWriteCache JasperFx.Events.Fetching.IAggregateWriteCache
Marten.Events.Fetching.AggregateCacheKey JasperFx.Events.Fetching.AggregateCacheKey
MemoryCacheAggregateWriteCache RecentlyUsedAggregateWriteCache — see below
NulloAggregateWriteCache promoted as-is
FetchForWritingCacheOptions AggregateWriteCacheOptions
EventGraph.AggregateWriteCaching / CacheAggregatesForWriting<T>() delete bothEventGraph derives from EventRegistry, which now declares them
IEventStoreOptions.AggregateWriteCaching / CacheAggregatesForWriting<T>() keep only if Marten wants them on its own interface; the inherited members already work

Everything genuinely Marten-shaped stays on the branch: FetchAsyncPlan.cs / .ForUpdate.cs, FetchInlinedPlan.cs / .ForUpdate.cs, AggregateWriteCacheWriteBack.cs (PendingAggregateCacheWrites + AggregateWriteCacheListener), and the tests.

⚠️ Drop the Microsoft.Extensions.Caching.Memory dependency. The spike's own follow-up list flagged it as a decision to make before graduation, and #676 decided it: the promoted default is backed by JasperFx.Core's existing RecentlyUsedCache, so Marten.csproj and Directory.Packages.props revert. Same three-member interface, same take-on-read semantics, no new package on core Marten. If a deployment specifically wants entries in a shared IMemoryCache, that is now a small adapter on the consumer's side rather than a dependency on everyone.

AggregateWriteCacheOptions.ResolveCache(Type) returns NulloAggregateWriteCache for a type nobody enrolled, so wherever the branch branches on "is caching enabled for T", that check can go — every take just misses.

Semantics are unchanged, and now written down

Grade 1 only. The promoted interface's XML docs carry the whole argument: baseline-only, version and delta always re-read, OCC untouched, take-on-read as a contract requirement rather than an implementation detail, and the version taken from the committed StreamAction rather than off the document.

⛔ The retired "trusted" variant is named as retired in the interface docs, with the measurement that retired it (0.19 ms of a 13.2 ms round). Please keep it retired on the rebase.

Compliance

public class aggregate_write_cache_compliance
    : AggregateWriteCacheCompliance<MartenComplianceFixture, IDocumentOperations, IQuerySession>;

Needs one new registrar member — IComplianceStoreRegistrar.CacheAggregatesForWriting<TDoc>(IAggregateWriteCache cache), two lines against the shared EventRegistry:

opts.Events.AggregateWriteCaching.Cache = cache;
opts.Events.CacheAggregatesForWriting<TDoc>();

It carries a throwing default, so Marten keeps compiling until it enrolls.

The suite covers both lifecycles with the daemon deliberately never started, so the Async aggregate's snapshot always lags and every fetch has a real delta to fold. It asserts the OCC fact #674 asked for by name, plus the stale / ahead-of-stream / evicted baselines, and it does not pin when an entry is written — that genuinely differs by lifecycle, which the branch already knows.

Expect meaningful overlap with the branch's own caching_async_aggregates_for_writing and caching_inline_aggregates_for_writing. Keep the Marten-specific ones that assert things the shared suite deliberately does not — cache_hit_folds_only_the_delta (which counts queries rather than watching a hit counter) and a_failed_commit_leaves_no_entry_behind inspecting the cache directly — and let the shared suite own the portable facts.

Follow-ups still open from the spike doc

These were on AGGREGATE_WRITE_CACHE_SPIKE.md §6 and are not resolved by the promotion:

  • Confirm the commit-side load is really skipped under Inline. Assert it by counting queries; do not infer it.
  • Decide UseIdentityMapForAggregates. The item-map bridge only fires when it is on, and that is currently silent — either require it for Inline caching or make the store path work without it.
  • A concurrency test for the Inline write-back. Two sessions committing the same stream concurrently should leave exactly one entry, at the winning version. The Async suite covers take-on-read under contention; Inline has no equivalent.
  • Decide the Microsoft.Extensions.Caching.Memory dependency — decided in Typename not getting properly written to mt_streams when using private apply strategy #676, see above.

Needs JasperFx 2.51.0.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions