Skip to content

Bump Marten from 9.15.0 to 9.17.1#6

Closed
dependabot[bot] wants to merge 1 commit into
devfrom
dependabot/nuget/code/K9Crush-scaffold/K9Crush/Marten-9.17.1
Closed

Bump Marten from 9.15.0 to 9.17.1#6
dependabot[bot] wants to merge 1 commit into
devfrom
dependabot/nuget/code/K9Crush-scaffold/K9Crush/Marten-9.17.1

Conversation

@dependabot

@dependabot dependabot Bot commented on behalf of github Jul 21, 2026

Copy link
Copy Markdown
Contributor

Updated Marten from 9.15.0 to 9.17.1.

Release notes

Sourced from Marten's releases.

9.17.0

High-water health check: opt-in autoRestart + heartbeat primary signal (#​4986)

Builds on the detection-only check from 9.16 (#​4984). Requires JasperFx 2.32.0 (jasperfx#​539), which this release rolls up to (#​4987).

  • Opt-in autoRestartAddMartenHighWaterHealthCheck(TimeSpan? staleThreshold = null, long minimumGap = 1, bool autoRestart = false). When the check is Unhealthy and autoRestart is on, it asks the local projection coordinator's daemon to restart the high-water agent's poll loop only — the mark is never advanced — capped to once per staleness window per database. The cycle is still reported Unhealthy so an alert still fires. Intended for Solo / leader nodes.
  • Heartbeat is now the primary staleness signal — when EnableExtendedProgressionTracking is on, the high-water agent stamps a liveness heartbeat on the HighWaterMark row every poll cycle. Heartbeat age proves the loop is cycling independent of whether the mark advances, so a quiet store is never a false positive, and a dead agent is caught even when projections are fully caught up (the exact #​4961 blind spot). The original sequence-gap heuristic is retained as the ExtendedProgression-off fallback.

Full changelog: JasperFx/marten@V9.16.1...V9.17.0

9.16.1

Async daemon data-safety release: the high water detection can no longer advance past "outstanding" event sequence numbers — sequences reserved by transactions that are still in flight — which could silently skip those events in async projections under concurrent append load (bulk imports being the classic case). Root-caused and fixed from discussion #​4953.

The four closed mechanisms:

  • The GapDetector command batched three statements, each reading its own READ COMMITTED snapshot — commits landing mid-command could defeat every gap check and silently advance the mark over an in-flight append, regardless of StaleSequenceThreshold. Detection is now a single statement / single snapshot.
  • Projection rebuilds and forced catch-up looped the gap-skipping detection toward the reserved sequence last_value, mowing through in-flight gaps. CheckNowAsync (JasperFx.Events 2.29.1) now targets the highest committed sequence and simply waits for in-flight appends to land.
  • The stale fallback could teleport the mark to reserved last_value - 32 across thousands of in-flight reservations on an idle-then-suddenly-busy store, because its gate measured staleness against mt_event_progression.last_updated. The threshold is now measured from when each specific gap was first observed.
  • Wall-clock stale skipping could not tell a slow transaction from a rolled-back one. Before skipping any stale gap, Marten now checks PostgreSQL for evidence that a transaction which could still fill the gap is alive (pg_locks on the mt_events tables, open transactions in pg_stat_activity, in-progress write xids from pg_current_snapshot()), and holds while any exists — by default Marten never knowingly skips past a live appender. Only provably-dead gaps (rolled-back appends) are skipped, bounded to the sequence ceiling observed with the gap, and every skip is logged at Warning with its exact range.

New knobs on StoreOptions.Projections: UseTransactionEvidenceForGapSkipping (default true; false restores the previous wall-clock behavior) and SkipStaleGapsDespiteLiveTransactionsAfter (default null = never skip a live appender; PostgreSQL's idle_in_transaction_session_timeout is the recommended backstop against leaked sessions).

What's Changed

Full Changelog: JasperFx/marten@V9.16.0...V9.16.1

9.16.0

Lot of CritterWatch, couple bug fixes too

What's Changed

Full Changelog: JasperFx/marten@v9.15.4...V9.16.0

9.15.4

What's Changed

New Contributors

Full Changelog: JasperFx/marten@v9.15.3...v9.15.4

9.15.3

This addresses a potential vulnerability from SQL injection via non-string constant in a LINQ Select projection

Not a common usage, but still.

What's Changed

Full Changelog: JasperFx/marten@9.15.2...v9.15.3

9.15.2

Marten 9.15.2

A patch release. Both fixes come out of the same 512-tenant-database production deployment, reported by @​erdtsieck, and both turned out to be worse than the reports described.

Bulk event insert ran a full schema apply on every batch

#​4946fixed in #​4949

The batch BulkInsertEventsAsync overloads opened with Storage.ApplyAllConfiguredChangesToDatabaseAsync() on every call.

That is not a cheap check. It calls Tenancy.BuildDatabases() and runs a full schema delta — partition introspection plus information_schema sweeps — across every database in the store. So a sharded store paid one apply per database, per batch. On the reporting deployment, each ~1,000-event batch was triggering 512 schema applies.

The measured effect: import throughput collapsed to ~17 events/s, against >3,000/s for the streaming overload. A 686k-event tenant projected to roughly 11 hours. The connection pool filled with ~370 backends whose last statement was Weasel's partition-introspection query, which fed directly into the server-wide connection pressure that deployment was already fighting.

That the streaming overload BulkInsertEventStreamAsync has no such call and is fine is the tell: the schema apply was never part of the contract. It was a leftover.

The apply is now:

  • skipped entirely when the effective AutoCreate is None — it is a no-op there by contract, so all that remained was the introspection cost; and
  • otherwise run at most once per database the import actually touches, memoized on IMartenDatabase.Identifier.

One subtlety worth recording, because it is the kind of thing that bites later: the memoized apply deliberately does not take a caller's CancellationToken. The first caller to arrive owns the single in-flight task that every concurrent caller for that database awaits — so binding that shared task to one caller's token would let a single cancelled batch fail sibling batches that were never cancelled. Each caller applies its own token at the await site instead. A schema apply is short and idempotent, so letting it run to completion is the cheaper trade.

Under AutoCreate.None, the event storage must already exist before import. That is the documented contract and it matches the streaming overload — but if you were previously relying on the per-call apply to create it for you under a non-None store, note the change.

The document bulk-insert path (BulkInsertAsync / BulkInsertDocumentsAsync) is unaffected. It routes through the ordinary per-feature EnsureStorageExistsAsync that Weasel already memoizes, not a full-store delta.

Tenant provisioning silently under-provisioned partitions

#​4944fixed in #​4950

AddPartitionToAllTables, and the tenant-provisioning paths built on it, walked the calling store's StoreOptions to decide which tables needed a list partition for a new tenant.

So any tool or host that provisions tenants from a store which doesn't register every document type silently under-provisioned. Document types unknown to the caller never got their partitions — and the tenant then failed with a Postgres 23514 check-constraint violation on first write to the missing partition. Nothing failed at provisioning time; the damage surfaced later, somewhere else.

The workaround was "the provisioning tool must register all document types," which re-creates schema knowledge in a second place and drifts as document types are added.

The sweep is now database-driven: it enumerates tenant list-partitioned tables from the Postgres catalog, so a partially-registered store still provisions every partitioned table it finds.

Scoping is enforced inside the catalog query rather than filtered in memory afterward:

  • Schema — the store's own AllSchemaNames() only. Foreign partitioned tables in a shared database are never touched.
  • Partition shape — LIST strategy, exactly one key column, and that column named tenant_id. This is the filter that matters most, and it is what keeps the sweep off Marten's own non-tenant list partitioning: UseArchivedStreamPartitioning keys mt_events on is_archived, and ByList() keys on its own field. Without it, a "helpful" sweep would start adding tenant partitions to tables partitioned on something else entirely.
  • External management — tables marked ByExternallyManagedListPartitions() are subtracted.

Opt out with SweepPartitionedTablesFromDatabase (default on). No Weasel change was required.

Known limitation, and it is a real one: a document type registered into a schema the calling store has never heard of stays invisible to the schema filter — a store cannot own a schema it does not know exists. Single-schema stores (the default, and the reporting deployment's shape) are fully covered. Closing this properly would need a persisted table list alongside mt_tenant_partitions.


... (truncated)

9.15.1

Patch release for a silent data-correctness regression. If you use ForTenant() on an identity-mapped or dirty-tracked session, upgrade.

Fixed

  • #​4947ForTenant() on an identity session stopped returning tenancy-neutral documents (reported by @​dervagabund, with a repro — thank you). A ForTenant() view of an identity- or dirty-tracked session no longer saw global (tenancy-neutral) documents tracked by the parent session. Since a global document has exactly one row per id for the whole database, LoadAsync through the ForTenant view missed the identity map, went to the database, and returned null for a document that is there. A silent wrong answer, not an error.

    Affected: 9.13.0, 9.14.x, 9.15.0. Introduced by the fix for #​4801, which tenant-scoped the identity map and version tracker for ForTenant sessions. That was correct for conjoined documents — where the same id means a different document per tenant — but it was applied per session rather than per document type, so it also isolated document types that are tenancy-neutral and must be shared.

    Sharing is now decided per document type. A nested ForTenant session shares the parent's identity-map and version-tracker entry for a type only when the storage is identity-mapped, the type is not Conjoined, and the nested session's database is the same instance as the parent's (under database-per-tenant, the same id in another tenant's database is a different document even for a tenancy-neutral type). The isolation introduced by #​4801 is preserved exactly — the Bug_4801 suite still passes, and the new tests include guard rails asserting conjoined documents stay isolated.

Full changelog: JasperFx/marten@9.15.0...9.15.1

Commits viewable in compare view.

Dependabot compatibility score

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


Dependabot commands and options

You can trigger Dependabot actions by commenting on this PR:

  • @dependabot rebase will rebase this PR
  • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
  • @dependabot show <dependency name> ignore conditions will show all of the ignore conditions of the specified dependency
  • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
  • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)

---
updated-dependencies:
- dependency-name: Marten
  dependency-version: 9.17.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
@dependabot dependabot Bot added .NET Pull requests that update .NET code dependencies Pull requests that update a dependency file labels Jul 21, 2026
@socket-security

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updatednuget/​marten@​9.15.0 ⏵ 9.17.19910090100100

View full report

@dependabot @github

dependabot Bot commented on behalf of github Jul 21, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #7.

@dependabot dependabot Bot closed this Jul 21, 2026
@dependabot
dependabot Bot deleted the dependabot/nuget/code/K9Crush-scaffold/K9Crush/Marten-9.17.1 branch July 21, 2026 18:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file .NET Pull requests that update .NET code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants