Skip to content

Marten 9.24.0

Choose a tag to compare

@jeremydmiller jeremydmiller released this 15 Aug 19:00
· 14 commits to master since this release

Fixes

Cross-tenant event rewrites under UseTenantPartitionedEvents (#5234)

If you use Events.UseTenantPartitionedEvents together with event masking or stream compaction, read this one.

Under per-tenant event partitioning each tenant draws from its own sequence, so seq_id is not unique across tenants — seq_id = 1 exists in every tenant's partition. Three operations that rewrite mt_events keyed their WHERE on seq_id alone, so while the read side was correctly scoped by ForTenant(...), the write escaped it:

  • Masking destroyed an uninvolved tenant's event payload and replaced it with the calling tenant's masked JSON — a cross-tenant write and disclosure, in the right-to-erasure feature.
  • Compaction permanently deleted an uninvolved tenant's events and left the calling tenant's whole aggregate state, as Compacted<T>, sitting in their stream.

Nothing threw in either case. All three operations now carry the tenant predicate.

Damage already written by an earlier version cannot be reversed by this fix — restore the affected tenants from a backup or archival storage. Stores that never enabled UseTenantPartitionedEvents are unaffected, because a single global sequence makes seq_id store-unique there.

Reported with executed failing tests by @arnelirobles.

EF Core inline projection connection leak (#5228)

The EF Core integration's placeholder connection was released only on the success path of BeforeCommitAsync. A projection that threw, an optimistic concurrency failure, or a throw inside the commit hook each stranded a pooled connection — and because an inline multi-stream projection builds its storage for events it will not even process, a workload that merely had one registered leaked on every failed save. Measured at 20 saves: 41 stranded backends before, 2 after. Reported by @markotny.

Compiled query parameters inside Select() (#5233)

Count(predicate) inside a projection is now re-bound per invocation of an ICompiledQuery instead of being frozen at plan time. A projection Marten cannot translate to SQL, which is applied by a delegate compiled once per plan, now throws InvalidCompiledQueryException at plan time when it reads a value off the compiled query instance, rather than silently returning the first invocation's results forever.

Dead letter persistence failures are no longer swallowed (#5229)

IEventDatabase.StoreDeadLetterEventAsync wrapped its whole body in an empty catch. A failed write dropped the only record that a projection skipped an event, with no exception and no log line; a wrong storage argument made the method a silent no-op. Failures are now logged at Error with the projection, shard, sequence and tenant, and a wrong argument throws.

Features

Count(predicate) translated inside Select() projections (#5223)

x.Lines.Count(line => line.IsActive) in a projection is now computed by PostgreSQL rather than by deserializing the whole document on the client. Same translation Where() has used since 9.14.1. This also fixes bare boolean predicates and && compounds in the Where() form — Where(x => x.Lines.Count(l => l.IsActive) == 3) previously threw.

Custom fetch planners (#5226)

StoreOptions.Projections.FetchPlanners is now public, so an application can supply its own IFetchPlanner to take over FetchForWriting() / FetchLatest() for the aggregate types it recognizes.

Opt-in FetchForWriting metrics (#5227)

OpenTelemetryOptions.TrackFetchForWritingMetrics() enables a marten.fetch_for_writing.events_replayed histogram, tagged by aggregate type and fetch plan, so the cost of Live / Async / Inline is directly comparable.

Both contributed by @erdtsieck.

Internal

  • The NoDataReturnedCall SQL audit now reports a visible skip for operations whose SQL it cannot see, instead of passing on a stand-in string (#5222).
  • Dependencies: JasperFx / JasperFx.Events 2.48.0 (#5235), Weasel 9.24.0 unchanged.