Repository navigation
V9.29.0
Data-loss and silent-failure fixes across the event append path, DCB tagging, and schema naming. Two of these lose or drop data with no error at all, so they are worth reading even if the rest is not relevant to you.
Data loss
A retried commit could lose its events entirely (#5262). QuickAppendEventsOperationBase released its pooled parameter buffers in PostprocessAsync, but the resilience pipeline can re-execute the same NpgsqlBatch — and a released PooledList reports Count = 0, which Npgsql binds as the array length. The retry sent empty event arrays, mt_quick_append_events wrote nothing and raised nothing, and SaveChangesAsync returned success having committed the documents without the events. The rentals now belong to the unit of work.
Commits are no longer retried when the outcome is unknown (#5262). A commit carries event appends and is not idempotent, so replaying one that already committed server-side appends the events twice. SaveChangesAsync now runs through a separate write pipeline that retries only when the previous attempt is known to have left nothing behind — a transient error PostgreSQL reported itself, or a failure Marten's own post-processing raised and rolled back. A command timeout or a dropped connection now surfaces instead of being replayed.
This is deliberately not NpgsqlException.IsTransient, whose definition is built for idempotent work and includes the whole connection-exception class. ConfigurePolly and ExtendPolly now govern reads only; ConfigureWritePolly / ExtendWritePolly are new for the commit path. See Resiliency Policies.
Only the first DCB tag of a given type was stored (#5265). mt_quick_append_events carries one array per tag type, parallel to the events, so it had exactly one slot per (event, tag type). An event legitimately carrying two tags of one type lost the rest — and a lost tag is not an error to a DCB query, it is simply absent from the answer. The surplus is now written as ordinary tag rows. In DcbStorageMode.HStore, which cannot represent the case at all, this now throws rather than dropping silently.
Schema
Long primary key constraint names could collide outright. A document table's PK constraint was left on Weasel's pkey_{table}_{columns} default, which passes 63 characters for a document type of no great length once tenant_id joins the key. PostgreSQL truncates rather than rejecting, and the constraint's backing index is schema-scoped, so two document types whose names agree for long enough failed with 42P07 relation "pkey_..." already exists. Also fixed for the natural key table (#5271). No-op for any schema that was not already being truncated.
Multi-database tenancy ignored Command Timeout on shard connection strings (#5269). StoreOptions.CommandTimeout was only ever raised from StoreOptions.Connection(...), which multi-database setups never call — so every command ran at the 5 second default no matter what the shards said. The database being used now has a say. An explicitly set store-wide value still wins.
FetchForWritingByTags broke later full-schema operations (#5264). EventGraph.Build<TDoc>() resolved the id type through MappingFor, which registers a DocumentMapping as a side effect — so a pure [BoundaryAggregate], which has no identity by design, became a document type with no IdMember. ResetAllData, ApplyAllConfiguredChangesToDatabaseAsync and the db-patch / db-apply commands then threw.
Projections and DCB
EF Core projection rebuilds corrupted the DbContext (#5266). The daemon applies a range's slices through a 10-wide block, all sharing one storage instance — and the EF Core storage wraps a single non-thread-safe DbContext. Requires JasperFx 2.53.0, which lets a storage declare it cannot take concurrent slices.
Bulk-imported events now carry DCB tags in hstore mode (#5267), thanks to @erdtsieck. Previously every bulk-imported event landed untagged, so a store whose history arrived by migration had a consistency boundary that silently excluded most of it.
The DCB hstore tag index can be built out of band (#5268). Enabling hstore mode on an existing store built a GIN index under ACCESS EXCLUSIVE — a write outage rather than a migration, with no way around it under per-tenant partitioning. EventGraph.HStoreTagIndexName can now be passed to Events.IgnoreIndex so an operator builds it themselves; see the DCB docs for the procedure.
A lost append race under conjoined tenancy surfaced as a raw PostgresException (#5270). The exception transform recognised the stream-version guard index by an enumerated list of names, and partitioning gives each partition a differently-named child index. It was missing three, not one — including on the single-tenant configuration #3520 was meant to have fixed. Now matched by shape.
Also
- The Inline
FetchForWritingaggregate cache now really does skip the snapshot load —IdentityMapDocumentStorage.LoadManyAsyncwas issuing an empty-id query even when the item map had satisfied everything (#5258, #5259, #5260). - Weasel 9.25.1 and JasperFx 2.53.0. Do not pin Weasel 9.25.0 — its identifier validation rejects primary key constraint names that PostgreSQL accepts, which breaks Marten in two places, one of them silently.
Thanks
@erdtsieck for #5262, #5264, #5267, #5268 and #5269 — several of these were forensic reports of silent data loss with the mechanism worked out, which is how they got fixed this quickly. @Noblix for the #5265 repro.