Skip to content

Marten 9.38.0

Choose a tag to compare

@jeremydmiller jeremydmiller released this 21 Sep 14:07
· 122 commits to master since this release

Eight changes since 9.37.0. Most of this release is silent-wrongness: a permanent sequence gap that stalls the async daemon, telemetry spans attached to the wrong parent, a DbContext nothing ever disposed, and a vector projection that embedded stale text. Three of them were reported from production by the community.

⚠️ Behaviour change

A VectorProjection declaring MapFromAggregate registered anything other than Async is now refused when the store is built (#5451, #5468). The class remarks always said Async was required; nothing enforced it, so an Inline registration built without complaint and then embedded text that missed the event that triggered it. Measured: the first save wrote no row at all, because live aggregation reads committed events and the stream had none yet; every later save wrote the state as of the previous event. The content hash recorded that stale text as current and nothing failed.

If you have such a registration it will now fail at DocumentStore.For(...) with a message naming MapFromAggregate and ProjectionLifecycle.Async. Event-mapped vector projections registered Inline are unaffected and remain supported.

Event store

  • A failed batch no longer leaves a permanent gap in the event sequence (#5454, #5456). Under QuickWithServerTimestamps — the V9 default — a StartStream took a per-event INSERT route whose seq_id came from an inlined nextval() that nothing read back. The events kept Sequence 0, and tombstone creation skips exactly those, so any failure later in the same batch (a losing UpdateRevision, a duplicate document INSERT) rolled back with sequence numbers drawn and nothing to fill them. The async daemon's high-water detector then stalled for every tenant in the database until it had seen the gap for StaleSequenceThreshold. A StartStream now goes through the same mt_quick_append_events function an ordinary append already used, which returns its sequence numbers. Reported and fixed by @vpetrevski.

  • mt_quick_append_events no longer writes a new stream's mt_streams row twice (#5464). It inserted the row with version 0 and then let the trailing UPDATE set the final version — two heap tuples and two WAL records per stream creation, in one transaction, where nothing outside it could observe the intermediate value. This became the default stream-creation path with #5456, so it was worth removing. A one-event StartStream measured 471µs on the old per-event route, 511µs through the function, and 480µs with this change (PostgreSQL 17.10, medians of 3). FetchForWriting against a stream that does not exist yet gets the same saving.

Projections and read models

  • An EF Core projection's DbContext is disposed (#5457, #5466). EfCoreProjectionStorage owns a context built once per tenant per batch, and nothing disposed it on any path — IProjectionStorage<,> declares no disposal contract, so the type that owned it had nothing to hook. Disposal now belongs to the transaction participant, whose lifetime already equals a batch. A second, unconditional leak is fixed alongside it: the live-aggregation fallback registered its owner only when the session was an ITransactionParticipantRegistrar, and a plain IQuerySession — what AggregateStreamAsync passes — is not one, so nobody released an eagerly-opened connection. Reported by @wpei-infotrack, with investigation from @arnelirobles.

  • An absent key no longer projects as an explicit JSON null in a Select() (#5461, #5462).

Telemetry

  • Marten activities attach to the current activity, not to its parent (#5459, #5465). MartenTracing passed parentActivity?.ParentId to ActivitySource.StartActivity, which parents a span to the parent of the activity it is handed — its grandparent. With an incoming Server/Consumer span carrying a remote traceparent, every marten.connection span hung off the sending service's span instead of the one doing the work. It stayed hidden because it is invisible in the two easy cases: with no ambient activity, or with an ambient root, ParentId is null and ActivitySource falls back to Activity.Current, which is the right answer by accident. Diagnosed from the source by @bohdan-hukivskyi.

Documentation

  • Unit testing event-sourced handlers with StubEventStream is now documented (#5455, #5458, #5463). Also fixes StubEventStream(aggregate, StoreOptions), which built a second EventGraph so MapEventType aliases never reached it.

  • The pgvector docs no longer describe the ef_search cap that 9.37 removed (#5452, #5467), including the non-obvious case that a hybrid search asks for its CandidateDepth rather than its limit and so reaches pgvector's 1000-row ceiling at a limit above 250. Contributed by @arnelirobles.

Dependencies

JasperFx and JasperFx.Events 2.73.2; Weasel 9.32.0.