
As anybody knows who follows the Critter Stack on our Discord server, I’m uncomfortable with the rapid pace of releases that we’ve sustained in the past couple quarters and I think I would like the release cadence to slow down. However, open issues and pull requests feel like money burning a hole in my pocket, and I don’t letting things linger very long. Our rapid cadence is somewhat driven by JasperFx Software client requests, some by our community being quite aggressive in contributing changes, and our users finding new issues that need to be addressed. While I’ve been known to be very unhappy with feedback saying that our frequent release cadence must be a sign of poor quality, I think our community seems to mostly appreciate that we move relatively fast. I believe that we are definitely innovating much faster and more aggressively than any of the other asynchronous messaging tools in the .NET space, so there’s that. Anyway, enough of that, here’s a rundown of the new releases today.
It’s been a busy week across the Critter Stack! We shipped coordinated releases today across all five projects: Marten 8.27, Wolverine 5.25, Polecat 1.5, Weasel 8.11.1, and JasperFx 1.21.1. Here’s a rundown of what’s new.
Marten 8.27.0
Sharded Multi-Tenancy with Database Pooling
For teams operating at extreme scale — we’re talking hundreds of billions of events — Marten now supports a sharded multi-tenancy model that distributes tenants across a pool of databases. Each tenant gets its own native PostgreSQL LIST partition within a shard database, giving you the isolation benefits of per-tenant databases with the operational simplicity of a managed pool.
Configuration is straightforward:
opts.MultiTenantedWithShardedDatabases(x => { // Connection to the master database that holds the pool registry x.ConnectionString = masterConnectionString; // Schema for the registry tables in the master database x.SchemaName = "tenants"; // Seed the database pool on startup x.AddDatabase("shard_01", shard1ConnectionString); x.AddDatabase("shard_02", shard2ConnectionString); x.AddDatabase("shard_03", shard3ConnectionString); x.AddDatabase("shard_04", shard4ConnectionString); // Choose a tenant assignment strategy (see below) x.UseHashAssignment(); // this is the default });
Calling MultiTenantedWithShardedDatabases() automatically enables conjoined tenancy for both documents and events, with native PG list partitions created per tenant.
Three tenant assignment strategies are built-in:
- Hash Assignment (default) — deterministic FNV-1a hash of the tenant ID. Fast, predictable, no database queries needed. Best when tenants are roughly equal in size.
- Smallest Database — assigns new tenants to the database with the fewest existing tenants. Accepts a custom
IDatabaseSizingStrategyfor balancing by row count, disk usage, or any other metric. - Explicit Assignment — you control exactly which database hosts each tenant via the admin API.
The admin API lets you manage the pool at runtime: AddTenantToShardAsync, AddDatabaseToPoolAsync, MarkDatabaseFullAsync — all with advisory-locked concurrent safety.
See the multi-tenancy documentation for the full details.
Bulk COPY Event Append for High-Throughput Seeding
For data migrations, test fixture setup, load testing, or importing events from external systems, Marten now supports a bulk COPY-based event append that uses PostgreSQL’s COPY ... FROM STDIN BINARY for maximum throughput:
// Build up a list of stream actions with events var streams = new List<StreamAction>(); for (int i = 0; i < 1000; i++) { var streamId = Guid.NewGuid(); var events = new object[] { new OrderPlaced(streamId, "Widget", 5), new OrderShipped(streamId, $"TRACK-{i}"), new OrderDelivered(streamId, DateTimeOffset.UtcNow) }; streams.Add(StreamAction.Start(store.Events, streamId, events)); } // Bulk insert all events using PostgreSQL COPY for maximum throughput await store.BulkInsertEventsAsync(streams);
This supports all combinations of Guid/string identity, single/conjoined tenancy, archived stream partitioning, and metadata columns. When using conjoined tenancy, a tenant-specific overload is available:
await store.BulkInsertEventsAsync("tenant-abc", streams);
See the event appending documentation for more.
Other Fixes
- FetchForWriting now auto-discovers natural keys without requiring an explicit projection registration, and works correctly with strongly typed IDs combined with
UseIdentityMapForAggregates - Compiled queries using
IsOneOfwith array parameters now generate correct SQL - EF Core
OwnsOne().ToJson()support (via Weasel 8.11.1) — schema diffing now correctly handles JSON column mapping when Marten and EF Core share a database - Thanks to @erdtsieck for fixing duplicate codegen when using secondary document stores!
Wolverine 5.25.0
This is a big release with 12 PRs merged — a mix of bug fixes, new features, and community contributions.
MassTransit and NServiceBus Interop for Azure Service Bus Topics
Previously, MassTransit and NServiceBus interoperability was only available on Azure Service Bus queues. With 5.25, you can now interoperate on ASB topics and subscriptions too — making it much easier to migrate incrementally or coexist with other .NET messaging frameworks:
// Publish to a topic with NServiceBus interop opts.PublishAllMessages().ToAzureServiceBusTopic("nsb-topic") .UseNServiceBusInterop(); // Listen on a subscription with MassTransit interop opts.ListenToAzureServiceBusSubscription("wolverine-sub") .FromTopic("wolverine-topic") .UseMassTransitInterop(mt => { }) .DefaultIncomingMessage<ResponseMessage>().UseForReplies();
Both UseMassTransitInterop() and UseNServiceBusInterop() are available on AzureServiceBusTopic (for publishing) and AzureServiceBusSubscription (for listening). This is ideal for brownfield scenarios where you’re migrating services one at a time and need different messaging frameworks to talk to each other through shared ASB topics.
Other New Features
- Handler Type Naming for Conventional Routing —
NamingSource.FromHandlerTypenames listener queues after the handler type instead of the message type, useful for modular monolith scenarios with multiple handlers per message - Enhanced WolverineParameterAttribute — new
FromHeader,FromClaim, andFromMethodvalue sources for binding handler parameters to HTTP headers, claims, or static method return values - Full Tracing for InvokeAsync — opt-in
InvokeTracingMode.Fullemits the same structured log messages as transport-received messages, with zero overhead in the default path - Configurable SQL transport polling interval — thanks to new contributor @xwipeoutx!
Bug Fixes
- Global Partitioning with Kafka — the interceptor now correctly fires for Kafka messages that arrive with only raw bytes
- Outbox stuck with multi-tenant RabbitMQ —
TenantedSendernow correctly acknowledges messages after successful send - PublishAsync with RequireResponse — request-reply pattern now works correctly with
PublishAsync - FluentValidation with AsParameters + FromBody — validators on the
[AsParameters]type are no longer silently skipped - Codegen with Startup.Configure() — new
--startflag for legacy hosting patterns - SQL Server saga storage now supports
nvarcharidentity columns (thanks @kakins!)
Polecat 1.5.0
Polecat — the Critter Stack’s newer, lighter-weight event store option — had a big jump from 1.2 to 1.5:
- net9.0 support and CI workflow
SingleStreamProjection<TDoc, TId>with strongly-typed ID support- Auto-discover natural keys for
FetchForWriting - Conjoined tenancy support for DCB tags and natural keys
- Fix for
FetchForWritingwithUseIdentityMapForAggregatesand strongly typed IDs
Weasel 8.11.1
- EF Core
OwnsOne().ToJson()support — Weasel’s schema diffing now correctly handles EF Core’s JSON column mapping, preventing spurious migration diffs when Marten and EF Core share a database
JasperFx 1.21.1 / JasperFx.Events 1.24.1
- Skip unknown flags when
AutoStartHostis true — fixes an issue where unrecognized CLI flags would cause errors during host auto-start - Retrofit
IEventSlicertests
Upgrading
All packages are available on NuGet now. The Marten and Wolverine releases are fully coordinated — if you’re using the Critter Stack together, upgrade both at the same time for the best experience.
As always, please report any issues on the respective GitHub repositories and join us on the Critter Stack Discord if you have questions!













