Skip to content

The search box in the website knows all the secretsโ€”try it!

For any queries, join our Discord Channel to reach us faster.

JasperFx Logo

JasperFx provides formal support for Wolverine and other JasperFx libraries. Please check our Support Plans for more details.

A Big Week for the Critter Stack โ€‹

The post-GA cadence has not let up. Between June 22 and June 29, we shipped three Wolverine releases, three Marten releases, and three Polecat releases โ€” a week heavy on database-backed messaging, brand-new interoperability with the rest of the .NET messaging ecosystem, and a steady drumbeat of work to make every part of the stack more observable and more manageable from CritterWatch.

Here's a tour of what landed.


Release Timeline โ€‹

DayWolverineMartenPolecat
Jun 22โ€”9.10.0โ€”
Jun 236.14.0โ€”4.5.2
Jun 25โ€”โ€”4.6.0
Jun 266.15.09.11.0โ€”
Jun 296.16.09.12.04.7.0

CritterWatch โ€‹

(Coming โ€” written separately.)


Wolverine โ€‹

Three releases this week, but the headline is clear: database-backed messaging got faster, and Wolverine now speaks the queueing protocols of the two biggest .NET messaging frameworks.

๐Ÿš€ Database queue performance โ€‹

Both the PostgreSQL and SQL Server transports got a focused performance pass that targets the hottest part of any database-backed queue: the dequeue path.

  • PostgreSQL transport โ€” indexed dequeue path plus more robust idempotency handling (#3278).
  • SQL Server transport โ€” indexed dequeue path plus an opt-in clustered queue layout so the physical table organization matches how the queue is actually read (#3277).

On SQL Server the new layout is one fluent call. Clustering the queue and scheduled tables on a monotonic seq identity (instead of the previous random-Guid clustered key) turns FIFO dequeue into a clustered seek with physically contiguous deletes:

csharp
opts.UseSqlServerPersistenceAndTransport(connectionString)
    .OptimizeQueueThroughput();

The raw-DDL benchmark behind the PR tells the story โ€” same hardware, same workload:

LayoutThroughputp50 latencyp99 latency
baseline (clustered Guid, no index)98/s845 ms1,860 ms
OptimizeQueueThroughput() (clustered seq)34,612/s2.4 ms3.7 ms

If you lean on Wolverine's database queues โ€” whether as a no-broker option or to keep messaging transactionally consistent with your business data โ€” the indexed dequeue path is a free win on upgrade. OptimizeQueueThroughput() is opt-in specifically because enabling it on an existing database triggers a one-time queue-table rebuild, so it's a maintenance-window change for existing systems and a no-brainer for new apps.

๐Ÿ“– SQL Server transport docs ยท ๐Ÿ“– PostgreSQL transport docs

๐Ÿ†• Interop with MassTransit and NServiceBus over SQL Server and PostgreSQL โ€‹

This is the big one. Wolverine can now send to and receive from MassTransit and NServiceBus applications using each framework's own SQL Server or PostgreSQL queueing โ€” reading and writing their native tables directly, no shared broker required.

Landed across 6.14.0 and 6.16.0:

  • NServiceBus over SQL Server (#3198)
  • NServiceBus over PostgreSQL (#3201)
  • MassTransit over PostgreSQL (#3203)
  • Each interop transport is pinned to a dedicated database under multi-tenanted storage (#3271), Seq is indexed on the NServiceBus PostgreSQL queue table (#3205), and a shared DatabaseListener base now backs the polling loop across all of these (#3206).

For NServiceBus, Wolverine reads and writes the NServiceBus queue tables directly โ€” one table per queue with a JSON Headers column and a raw Body column:

csharp
using Wolverine.SqlServer.Transport.NServiceBus;

builder.UseWolverine(opts =>
{
    // Wolverine's own durable inbox/outbox still lives in SQL Server
    opts.PersistMessagesWithSqlServer(connectionString, "wolverine");

    opts.UseNServiceBusSqlServerInterop();

    // Publish to an NServiceBus endpoint's queue table
    opts.PublishMessage<OrderPlaced>().ToNServiceBusSqlServerQueue("nsb");

    // Listen to Wolverine's own queue table and use it for replies
    opts.ListenToNServiceBusSqlServerQueue("wolverine").UseForReplies();

    // Bind NServiceBus interface-typed messages to Wolverine's concrete types
    opts.Policies.RegisterInteropMessageAssembly(typeof(IOrderContract).Assembly);
});

PostgreSQL is identical with the UseNServiceBusPostgresqlInterop() / ListenToNServiceBusPostgresqlQueue() / ToNServiceBusPostgresqlQueue() trio. MassTransit is a different shape โ€” its SQL transport is a function-driven, two-table model (transport.message + transport.message_delivery) that MassTransit owns and migrates, so Wolverine calls its stored functions rather than touching a table:

csharp
using Wolverine.Postgresql.Transport.MassTransit;

builder.UseWolverine(opts =>
{
    opts.PersistMessagesWithPostgresql(connectionString, "wolverine");

    opts.UseMassTransitPostgresqlInterop(autoProvision: true);

    opts.PublishMessage<OrderPlaced>().ToMassTransitPostgresqlQueue("masstransit");
    opts.ListenToMassTransitPostgresqlQueue("wolverine").UseForReplies();

    opts.Policies.RegisterInteropMessageAssembly(typeof(IOrderContract).Assembly);
});

These join the existing Amazon SQS interop options (which also picked up two bug fixes this week, #3190) and a fix to map Wolverine's TenantId from incoming MassTransit messages (#3192). The practical upshot: you can introduce Wolverine into an existing MassTransit or NServiceBus shop incrementally, service by service, over infrastructure both sides already trust.

๐Ÿ“– Interop with NServiceBus over database transports ยท ๐Ÿ“– Interop with MassTransit over database transports

๐Ÿ”ญ Observability & health โ€‹

A large share of the week's Wolverine work exists to make running systems legible โ€” much of it surfaced directly through CritterWatch:

  • A shared BackgroundReceiveLoop with receive-loop health reporting, now adopted across SQS, Redis, the PostgreSQL queue, the SQL Server queue, and Kafka (#3236).
  • Transport connection state surfaced in EndpointHealthSnapshot, with a new IReportConnectionState implemented for NATS, MQTT, Pulsar, and Redis (#3231), plus a force-restart path for stuck listeners (#3232).
  • A sanitized, credential-free broker connection summary on BrokerDescription (#3272) โ€” so the dashboard can show you where a broker points without ever leaking secrets.
  • Richer metrics: every instrument tagged with a source service name (#3221), dimensional inbox/outbox/scheduled gauges, and configurable histogram buckets (#3224).
  • The discovered gRPC endpoint manifest is now exposed via a ServiceCapabilities descriptor source (#3268, #3266), and RabbitMQ sending endpoints are now properly named in health snapshots (#3273).

๐Ÿ“– Instrumentation and Metrics ยท ๐Ÿ“– Diagnostics

๐Ÿ› Reliability fixes & Pulsar โ€‹

6.14.0 also closed out a major Pulsar re-evaluation effort โ€” DLQ/retry precedence, initial subscription position, multi-topic and regex subscriptions, native per-message redelivery, acknowledgment-strategy choice, a Reader interface for bounded replay and non-durable hot-tail, a tiered retry-letter error policy, producer deduplication, and both JSON and Avro schema support with broker-side registration (#3194โ€“#3215).

Two of those are worth showing. Pulsar's defining feature is broker-side schema registration and compatibility checking โ€” now a single fluent call, with the message body still owned by Wolverine's serialization:

csharp
opts.PublishMessage<OrderPlaced>()
    .ToPulsarTopic("persistent://public/default/orders")
    .UseJsonSchema<OrderPlaced>();   // or UseAvroSchema<T>() for Avro on the wire

And the new tiered retry-letter policy โ€” the Pulsar analogue of the Kafka transport's MoveToKafkaRetryTopic โ€” expresses native redelivery delays as a first-class, discoverable error policy:

csharp
// On failure: redeliver after 5s, then 30s, then 2m, then dead-letter.
opts.OnException<TransientException>()
    .MoveToPulsarRetryTopic(5.Seconds(), 30.Seconds(), 2.Minutes());

๐Ÿ“– Pulsar schema support ยท ๐Ÿ“– Tiered retry-letter policy ยท ๐Ÿ“– Producer deduplication

Plus targeted reliability fixes: a RabbitMQ agent that could latch Disconnected after a channel-only shutdown (#3187), stable node identity for storeless Solo hosts (#3189), and re-attaching the sender wire tap to recovered envelopes (#3276).


Polecat โ€” Making It More Robust โ€‹

Polecat shipped three releases this week (4.5.2, 4.6.0, 4.7.0), and the through-line is hardening: fewer sharp edges, more parity with Marten's behavior, and a real document-metadata story.

๐Ÿ›ก๏ธ Robustness & correctness fixes โ€‹

  • Repopulate the natural-key lookup table on projection rebuild (#261) โ€” rebuilds no longer leave natural-key lookups stale (mirrored by the same fix in Marten, below).
  • Patch().Set() now honors EnumStorage (#264) and supports DateTime/DateTimeOffset/DateOnly/TimeOnly (#265).
  • Sequential GUIDs for auto-assigned document ids (#245) โ€” far friendlier to index locality than random GUIDs.
  • AsString enum LINQ predicates honor the JsonNamingPolicy (#224), computed-column indexes are usable by the LINQ translator (#225), on-the-fly event-store schema creation and InitialData seeding work on startup (#233), and IEventStore.Identity now varies by StoreName so multiple stores stay distinct (#208).

๐Ÿ†• Document metadata โ€‹

A genuinely new capability area: opt-in document metadata, end to end โ€” mirroring Marten's metadata model so the two stores behave alike. Enable the columns you want with a fluent DSL (or attributes) (#251, #252):

csharp
opts.Schema.For<Order>().Metadata(m =>
{
    m.LastModifiedBy.Enabled = true;
    m.CorrelationId.Enabled = true;
    m.CreatedAt.MapTo(x => x.CreatedDate);   // project a column onto your own member
});

Then read just the metadata for a row โ€” no document body deserialization โ€” via the new MetadataForAsync<T> API (#253):

csharp
DocumentMetadata metadata = await session.MetadataForAsync(order);
// metadata.Version, .LastModified, .LastModifiedBy, .CorrelationId, .CausationId, ...

Rounding it out: an opt-in user_name (LastModifiedBy) event-metadata column (#248), auto-seeding of CorrelationId/CausationId from Activity.Current on session open (#250), and session-level Headers with SetHeader/GetHeader (#249).

๐Ÿ”ญ Observability & CritterWatch โ€‹

  • An opt-in polecat.event.append OpenTelemetry counter (#247) and runtime event-append observations via IEventStoreInstrumentation.AppendObserver (#215).
  • IDocumentStoreDiagnostics with an enriched mapping descriptor (#210), structured partitioning in the DocumentMappingDescriptor (#214), and metadata capabilities + an IEventStore bridge with tenant-scoped document diagnostics (#254) โ€” the same descriptor surface Marten exposes, so CritterWatch sees Polecat stores the same way it sees Marten.

๐Ÿ†• Range partitioning โ€‹

Declarative range partitioning for document tables (#257, #212), now with a Marten-parity fluent surface โ€” the classic time-series retention pattern:

csharp
// Marten manages the boundaries:
opts.Schema.For<MetricsSample>().PartitionOn(x => x.BucketEnd).ByRange(jan, feb, mar);

// Or let a DBA / pg_partman own SPLIT/SWITCH/DROP at runtime for retention:
opts.Schema.For<MetricsSample>().PartitionOn(x => x.BucketEnd).ByExternallyManagedRange(jan, feb);

ByExternallyManagedRange(...) provisions the partitions once and then never reconciles them, so a later schema apply won't clobber the months your retention job has been splitting and dropping.

๐Ÿ“– Wolverine + Polecat integration guide ยท ๐Ÿ“ฆ Polecat on GitHub


Marten โ€‹

Three releases (9.10.0, 9.11.0, 9.12.0), with a mix of concurrency-hardening, new partitioning options, and โ€” again โ€” observability work feeding CritterWatch.

๐Ÿ› Concurrency & correctness โ€‹

  • Close the mt_events_sequence gap on concurrent Quick OCC failures (#4771) โ€” a first contribution from @KMDjkb. Under truly concurrent FetchForWriting + Quick-append writes to the same stream, a losing transaction could burn a sequence value it never rolled back, leaving a permanent gap that stalls the async daemon's high-water mark. A new opt-in option takes a FOR UPDATE lock in the OCC path so the loser blocks and raises a clean concurrency error before consuming a sequence value โ€” no schema migration required:

    csharp
    opts.Events.UseExclusiveLockOnConcurrentAppends = true;
  • Fix a false ConcurrencyException from non-RETURNING event ops in a batched SaveChanges (#4784).

  • Repopulate mt_natural_key on projection rebuild (#4793) โ€” the Marten side of the same natural-key fix that landed in Polecat.

๐Ÿ†• Partitioning & queries โ€‹

  • Range-partition a document table by a non-tenant date column (#4780) โ€” the PartitionOn(member, cfg) API already existed; a Weasel 9.3.0 fix makes the date-keyed retention path stable across deployments and time zones (partition bounds are now compared by normalized instant rather than raw SQL literal, so migrations no longer report a spurious destructive rebuild).
  • Metadata-filtered document and event queries (#4792) โ€” the diagnostics surface can now filter documents and events by correlation_id / causation_id / last_modified_by, honored only when the store actually captures that metadata column.

๐Ÿ“– Document storage & date range partitioning ยท ๐Ÿ“– Document & event metadata

๐Ÿ”ญ Observability & CritterWatch โ€‹

  • IDocumentStoreDiagnostics with an enriched mapping descriptor (#4776) and populated event/document metadata capabilities with tenant-scoped document diagnostics (#4790).
  • Runtime event-append observations via IEventStoreInstrumentation (#4783) and an exact-identity DeleteProjectionProgressByShardNameAsync for surgical projection-progress management (#4786).

The Common Thread โ€‹

Three themes ran through all nine releases this week:

  1. Database-backed messaging matured โ€” Wolverine's PostgreSQL and SQL Server queues got faster, and now interoperate directly with MassTransit and NServiceBus over the same databases.
  2. Polecat got tougher โ€” a stack of correctness fixes, sequential GUIDs, a full document-metadata story, and range partitioning.
  3. Everything got more observable โ€” diagnostics descriptors, instrumentation hooks, OpenTelemetry counters, connection-state reporting, and credential-safe broker summaries across Wolverine, Marten, and Polecat โ€” all converging on a single, consistent surface for CritterWatch to manage.

As always: upgrade, and tell us what breaks. This week's patch cadence is the proof that we listen.

Released under the MIT License.