A Big Week for the Critter Stack: Per-Tenant Event Partitioning, F# Codegen, and 33 Releases β
What happens when a .NET stack ships 33 NuGet releases in 7 days, lands two major new feature tracks across five repos, and absorbs 17+ community bug reports β all without breaking GA?
That was the past week in the JasperFx organization (Marten, Wolverine, Polecat, JasperFx, Weasel, CritterWatch). Here's a tour for .NET developers building event-sourced, multi-tenant, or high-throughput systems.
π¦ The Release Wave (May 30 β June 6) β
Marten: 11 releases (V9.3.4 β V9.6.0, plus an 8.37.2 backport) Wolverine: 8 releases (V6.3.0 β V6.5.0) JasperFx: 6 releases (V2.2.4 β V2.8.1) Polecat: 2 releases (V4.2.0, V4.2.1) Weasel: 1 release (V9.0.3)
Most of those are coordinated patch waves. The big version bumps (Marten 9.4.0, Wolverine 6.3.0/6.5.0, JasperFx 2.5.0) each anchor a major feature track.
π― The Big Tracks β
π Per-Tenant Event Partitioning β
The headline feature of the week. In Marten 9.4.0+, opt in with one line:
opts.Events.UseTenantPartitionedEvents = true;What that unlocks:
- Native PostgreSQL LIST partitioning of
mt_eventsandmt_streamsbytenant_id - A per-tenant event sequence (
mt_events_sequence_{suffix}) mt_event_progressionkeyed by(name, tenant_id)via a folded{Name}:{ShardKey}:{tenantId}shard grammar (no new column needed)- Vectorized per-tenant high-water detection β one round-trip per database emits a high-water vector for every active tenant
- Per-tenant rebuild isolation + cross-tenant rebuild fan-out
- Composite single-pass rebuild executor (read-once / fan-out)
Constraints (validated at DocumentStore construction):
- Requires conjoined tenancy
EventAppendMode.QuickorQuickWithServerTimestampsonly (Rich is out of scope)- Can't currently combine with
UseArchivedStreamPartitioning(planned follow-up)
The flag defaults to false; existing stores keep the global append path byte-for-byte.
Wolverine 6.5.0 followed through with a full handler-side matrix: [ReadAggregate] / [WriteAggregate], required-write isolation, optimistic versioning, cascading tenant inheritance, MartenOps tenant overloads, AlwaysEnforceConsistency, natural keys, multi-node distribution, ancillary stores, and the IEventStream<T> compound-handler append path.
Polecat (the SQL Server-backed sibling) started its parallel track immediately.
This is the kind of feature that removes a real scalability ceiling β the single shared event store stops being the bottleneck across tenants. If you're running an event-sourced multi-tenant SaaS on PostgreSQL, this is the upgrade to plan for.
π Per-Tenant Event Partitioning docs: https://martendb.io/events/multitenancy#per-tenant-event-partitioning
π F# Pre-Generated Code β
Wolverine 6.3.0 added a one-line CLI command that turns your handlers into runnable F# source files you can check into your repo:
dotnet run -- codegen write --language fsharpThe end-to-end slices that ship as compile-gated runnable samples:
- EF Core handlers
- Marten document handlers
- Marten event-sourced aggregate handlers
- FluentValidation + CosmosDB
- HTTP routing (JSON, route binding, query binding β both string and typed)
- In-memory sagas
- Behavioural Static-mode run-step
This positions Wolverine as a first-class F# option β not just "C# that happens to compile" but actual idiomatic F# emission from the code generator. There's a tutorial at https://wolverinefx.io/tutorials/fsharp.html and an open community-input discussion on idiomatic F# handler discovery (module-level functions, DUs, records) that would benefit from real F# adopter input.
π JasperFx.Aspire β CLI Verbs as Dashboard Commands β
If you're using .NET Aspire for your dev loop, JasperFx CLI verbs (describe, codegen, openapi, wolverine-diagnostics describe-handlers, etc.) are now exposed as Aspire dashboard commands with startup gates. Click a button instead of hopping to a terminal.
Pairs with the existing build-time openapi command and the source-generated discovery work that shipped the prior week.
π Inline Request/Reply Over Wolverine.Http and Wolverine.Grpc β
InvokeAsync<T> now flows efficiently over Wolverine's own HTTP and gRPC sender transports. Combined with the Result<T> work that shipped the week before, this rounds out the RPC-style story for Wolverine β you can do remote request/reply in the idiomatic Wolverine way without dropping down to bare HttpClient/Grpc.
π Critical Reliability Fixes Shipped β
Highlights worth flagging if you're on the GA majors:
- DCB tag-concurrency race β concurrent DCB tag-boundary appends sharing a tag could both commit. Fixed by serializing through a side table.
OnExceptionmiddleware now cascades return-value messages β a long-standing UX gap.- Concurrent health-check race that corrupted leadership locks under sustained load.
- Codegen scope priming for service-located
MessageContext/IDocumentSessionβ closes the family of duplicate-variable codegen bugs that hit several adopters at GA. BatchedQuery.FetchForExclusiveWritingrace that wedged Wolverine HTTP[Aggregate(LoadStyle = Exclusive)]endpoints under concurrency.SecondaryStoreProxyFactoryregression in 9.5.1 β first concurrent build threw "Duplicate type name within an assembly". Fixed in 9.5.2 hours after report.- Projection rebuild stress-test fixes: write-path
*Projectedvariants and read-pathLoadProjectedAsyncnow bypass session-shared trackers, andProjectionDocumentSessionroutes user-codeLoadAsyncthrough a projection-safe path. Closes thread-safety side effects of the 9.0 changes.
π Marten.ScaleTesting CLI β
A new performance/scale harness landed: an event seeder, a TelehealthComposite 4+2+2 scenario, plus rebuild / validate / stress subcommands. This is becoming the official rebuild-correctness stress vehicle. Signals a near-term focus on rebuild correctness and projection performance at production scale.
π The Community Story β
The thing I find most encouraging about this stack right now is how much real-world feedback is flowing through the issue tracker.
This week alone:
- 12 community issue reporters filed real, reproducible bugs
- 2 first-time PR contributors landed merges (@steve-ziegler reported AND fixed a race in
BatchedQuery.FetchForExclusiveWriting<T>in their first PR; @midub fixed a NATS JetStream scheduled-send bug) - 3 other community PRs merged from established contributors
A few standouts:
@erdtsieck continues to be the de facto QA partner for advanced Marten paths β filed four high-quality per-tenant-partitioning bug reports surfacing real adopter edge cases (sharded provisioning, StartStream semantics, optimistic-append compatibility, BulkInsertEventsAsync NULL types). Every one was real, reproducible, and closed within the week.
@RorySan found and fixed an IndexOutOfRangeException regression on tenant-mapped projection documents in their first contribution, then immediately found a second related regression (9.5.1 β 9.5.2 patch cycle).
@dmytro-pryvedeniuk caught a concurrent health-check race that could corrupt leadership locks under sustained load β exactly the kind of failure that only shows up in production-shaped workloads.
The patch cadence on community-reported bugs is typically sub-24-hour for high-severity issues. That tight loop is the real story.
π Patterns Worth Watching β
The per-tenant partitioning rollout was a model multi-repo execution. Umbrella issue β top-of-chain JasperFx surface β Marten implementation β Wolverine handler matrix β Polecat parallel track β community-reported edge cases β cascading patch wave. All inside a week.
Patch cadence is sub-12-hour for severe bugs. A 9.5.1 regression was fixed in 9.5.2 within hours of being reported. The GA
messageContextcodegen regression cluster from late May is now entirely closed.Aspire is becoming the canonical dev-time UI for the Critter Stack.
JasperFx.Aspirebrings CLI verbs into the dashboard, the docs site picked up an Aspire integration page, and the friction to debug/inspect a Critter Stack app at dev time has dropped significantly.Marten.ScaleTesting CLI signals a near-term focus on rebuild correctness and projection performance at production scale. Expect benchmark-driven changes in the next month.
Three open community Discussions are worth a triage pass β particularly one asking about the Marten release cadence and another asking what replaced
ProjectEventAsyncin Marten 9 (almost certainly a migration-docs gap).
If You're Adopting β
Safe upgrade matrix as of this morning:
- Marten 9.6.0 + Marten.AspNetCore 9.6.0
- Wolverine 6.5.0
- Polecat 4.2.1
- JasperFx 2.8.1 family (RuntimeCompiler stays on its 5.x line)
Two semantic changes from Marten 8 β 9 that are worth knowing before you upgrade:
- The default
IDocumentSessionresolved from DI is now a lightweight session (no identity map, no dirty tracking). If your code depends on identity-map semantics, opt in explicitly. - Marten 9 uses source-generated projection dispatch. Your self-aggregating types compile out of the box now, but
partialis still required on*Projectionsubclasses.
The 5 β 6 migration on Wolverine covers ServiceLocationPolicy defaults, the Lamar removal (the built-in ServiceProvider is now the container), and the AOT story.
Net β
33 releases, two major new feature tracks shipped end-to-end, 17+ community bug reports triaged or fixed, and Aspire integration begun β in one week.
If you're building event-sourced multi-tenant systems on .NET, the per-tenant partitioning feature alone removes a real scalability ceiling that traditionally pushed people toward either app-level sharding or completely separate databases per tenant. If you're an F# shop that has been quietly watching from the sidelines, the codegen write --language fsharp work means the Critter Stack is now a first-class option.
Find the work on GitHub (https://github.com/JasperFx), the docs at https://martendb.io and https://wolverinefx.io, or the JasperFx Discord. Issues and PRs from real adopters are landing within hours β if you're using this stack and hit something rough, file it.
#dotnet #csharp #fsharp #eventsourcing #postgresql #multitenancy #microservices #Marten #Wolverine #CritterStack

