Stamp the owning database onto every published ShardState#501
Merged
Conversation
An event store backed by more than one database — database-per-tenant, sharded tenancy — runs one daemon, and one ShardStateTracker, per database. Every one of them publishes the same shard names (Trip:All) and the same HighWaterMark, so a consumer keying on shard name alone collapses them into a single entry and reports whichever database published last. JasperFxAsyncDaemon already holds the IEventDatabase and takes its Tracker from it, so the stamp lands in one place and rides both the subscription agents' progress and the high water agent's marks. Marten and Polecat inherit it with no change. Additive and binary-safe: two nullable properties on classes, and PublishAsync leaves a state that already names its database alone. Reported as JasperFx/CritterWatch#678 — a database-per-tenant service showed a high water mark of 0 and every projection as "awaiting its first event" while its per-tenant daemons were actively running.
jeremydmiller
added a commit
that referenced
this pull request
Jul 9, 2026
…fier, #501 / CritterWatch#678) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HHNVFwbKb6fkn4d9G6j3v3
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
An event store backed by more than one database — database-per-tenant, sharded tenancy — runs one daemon, and one
ShardStateTracker, per database. Every one of them publishes the same shard names (Trip:All) and the sameHighWaterMark.ShardStatecarries no database identity, so a consumer keying on shard name alone collapses all of them into a single entry and reports whichever database published last.This surfaced as JasperFx/CritterWatch#678: a database-per-tenant service showed a high water mark of 0 and every projection as "awaiting its first event", while its per-tenant daemons were actively running and advancing. Wolverine's
EventStoreAgentssubscribes the same observer to each database's daemon, so the monitoring side had no way to tell one tenant database'sTrip:Allfrom another's.Change
Three additions, all in
JasperFx.Events:ShardState.DatabaseIdentifier— theIEventDatabase.Identifierof the database the state came from.ShardStateTracker.DatabaseIdentifier—PublishAsyncstamps it onto any state that doesn't already carry one.JasperFxAsyncDaemonsetsTracker.DatabaseIdentifier ??= Database.Identifierin both constructors.JasperFxAsyncDaemonalready holds theIEventDatabaseand takes itsTrackerfrom it, so the stamp lands in exactly one place and rides both the subscription agents' progress and the high water agent's marks. Marten and Polecat inherit it with no change of their own.Compatibility
Additive and binary-safe: two nullable properties on classes, no positional-record parameters touched. A publisher that predates this leaves the field null, and a state that already names its database is left alone (
??=), so a republishing consumer can't have its attribution overwritten.Verification
ShardStateTrackerTests(default null, stamped on both shard states and high-water marks, pre-set value preserved, null tracker leaves null).EventTestssuite green: 481/481 on net9.0 and net10.0.2.18.2-cw678, driving a real two-tenant-database Marten store (MultiTenantedDatabasesWithMasterDatabaseTable): the observed shard states now reporttenant-a/tenant-bwith correctly attributed high-water marks, where previously they collapsed to a single mark of 0.