Skip to content

Fix #4681 — centralize high-water progression-row identity in a Marten helper#4689

Merged
jeremydmiller merged 1 commit into
masterfrom
feature/4681-highwater-shardname-compose
Jun 8, 2026
Merged

Fix #4681 — centralize high-water progression-row identity in a Marten helper#4689
jeremydmiller merged 1 commit into
masterfrom
feature/4681-highwater-shardname-compose

Conversation

@jeremydmiller

Copy link
Copy Markdown
Member

Closes #4681.

The per-tenant high-water grammar is intentionally asymmetric to the rest of JasperFx.Events' ShardName (which collapses HighWaterMark identities to the constant — the tenant slot is discarded). Marten layers per-tenant high-water tracking on top by writing one row per tenant under the convention "{HighWaterMark}:{tenantId}", but until now that convention was hand-rolled across three sites:

File Before
HighWaterDetector.loadPerTenantStatistics ShardState.HighWaterMark + ":" fed as a :prefix parameter, joined prog.name = :prefix || tenant_id in SQL
HighWaterStatisticsDetector literal '{ShardState.HighWaterMark}' baked into the SQL
BulkEventAppender.updateHighWaterMark bare 'HighWaterMark' literal in the UPSERT

Any future grammar change (separator, version segment, escape rule) would have to be hunted down across all of them.

What ships

New Marten.Events.Daemon.HighWater.HighWaterShardIdentity is the single source of truth:

Member Use
StoreGlobal the store-global progression-row name (= ShardState.HighWaterMark)
PerTenantPrefix \"HighWaterMark:\", for SQL-side concat-style joins
PerTenant(tenantId) full identity for a tenant's row

All three call sites now reference the helper. The comment in EventProgressionTable that describes the per-tenant naming convention points at the helper as the authoritative producer.

No behavior change. Byte-identical SQL output, same progression-row names, same mt_event_progression keying. JasperFx.Events' ShardName is unchanged.

Tests

  • 6 unit tests in CoreTests/Events/Daemon/HighWaterShardIdentity_round_trip.cs pin the round-trip — StoreGlobal == constant, the PerTenantPrefix shape, PerTenant() = prefix + id across several tenant-id forms, and a "matches the HighWaterDetector SQL join" pin so any future drift between the helper and the SQL is a failing test.
  • DaemonTests 188/188 (net9.0) — high-water detector + statistics paths
  • TenantPartitionedEventsTests 177/177 (net9.0) — vectorized per-tenant high-water + cross-tenant rebuild

Followups left in #4681

The issue mentions a JasperFx-side companion that would let ShardName.Compose(\"HighWaterMark\", tenantId: …) produce the per-tenant identity directly (instead of collapsing). That stays as the JasperFx-side hand-off; this PR is just the Marten-side hygiene refactor it asked for.

🤖 Generated with Claude Code

…ten helper

The high-water grammar is intentionally asymmetric to the rest of JasperFx.Events'
ShardName (which collapses HighWaterMark identities to the constant -- the tenant
slot is discarded). Marten layers per-tenant high-water tracking on top by writing
one row per tenant under the convention `"{HighWaterMark}:{tenantId}"`, but until
now the convention was hand-rolled at several SQL sites:

  * HighWaterDetector.loadPerTenantStatistics  — `ShardState.HighWaterMark + ":"`
    fed as a `:prefix` parameter and concatenated `prog.name = :prefix || tenant_id`
    inside SQL.
  * HighWaterStatisticsDetector  — the literal `'{ShardState.HighWaterMark}'` in
    SQL for the store-global progression-row name.
  * BulkEventAppender.updateHighWaterMark  — the bare literal `'HighWaterMark'`
    for the store-global UPSERT.

Any future grammar change (separator, version segment, escape rule) would have
to be hunted down across every one of these sites.

New `Marten.Events.Daemon.HighWater.HighWaterShardIdentity` is the single source
of truth for these names:

  * `StoreGlobal`        — the literal store-global name (= ShardState.HighWaterMark)
  * `PerTenantPrefix`    — `"HighWaterMark:"`, for SQL-side concat-style joins
  * `PerTenant(tenantId)` — the full identity for a tenant's row

All three call sites now reference the helper. The comment in EventProgressionTable
that describes the per-tenant naming convention now points at the helper as the
authoritative producer. JasperFx-side `ShardName` retains its existing collapsing
behavior -- no API change there, just a Marten-side hygiene refactor.

Six unit-test cases in CoreTests pin the round-trip: StoreGlobal == constant,
PerTenantPrefix shape, PerTenant() = prefix+id, and a "matches the HighWaterDetector
SQL join" pin so any future drift between the helper and the SQL gets caught by an
already-failing test.

DaemonTests (188/188) and TenantPartitionedEventsTests (177/177) pass against the
refactor on net9.0. No behavior change -- byte-identical SQL output, same
progression-row names, same `mt_event_progression` keying.

Closes #4681.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jeremydmiller jeremydmiller merged commit 0d3e74e into master Jun 8, 2026
7 of 8 checks passed
@jeremydmiller jeremydmiller deleted the feature/4681-highwater-shardname-compose branch June 8, 2026 18:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

HighWaterDetector hand-rolls per-tenant high-water identity in SQL (route through ShardName)

1 participant