Skip to content

GH-5002 The leader's heartbeat reads shard names and databases, not the full event store usage - #5011

Merged
jeremydmiller merged 1 commit into
mainfrom
gh-5002-cheap-supported-agents
Oct 14, 2026
Merged

jeremydmiller merged 1 commit into
mainfrom
gh-5002-cheap-supported-agents

Conversation

@jeremydmiller

Copy link
Copy Markdown
Member

Closes #5002. Reported downstream by @uniquelau in JasperFx/marten#5652.

EventStoreAgents.SupportedAgentsAsync runs on every leader heartbeat (HealthCheckPollingTime, 10 seconds by default) for every registered store. It built the whole EventStoreUsage descriptor, which reads the highest event sequence and describes every projection, subscription, event type and tag type.

The issue says only usage.Subscriptions is used. That isn't quite right: the method also needs each database's server, database name and tenant ids, which drive the per-tenant agent fan-out. So this uses two narrower reads:

  • Async shard names: IEventStore.RegisteredShardNames(), the store's in-memory registry, with no database work. It returns the same set the usage path produced: async projections plus every subscription, and subscriptions always report Async. A store that doesn't implement it falls back to the usage.
  • Database topology: the new IEventStore.TryDescribeDatabasesAsync() from JasperFx 2.87.1 (IEventStore.TryDescribeDatabasesAsync: the database half of TryCreateUsage (wolverine#5002) jasperfx#1013). The tenant-to-database lookup, StartAllAsync and AllDaemonsAsync use it too.
  • The registered-projection rebuild keeps the full usage, because it has to match Inline and Live projections as well.

Where the saving comes from

TryDescribeDatabasesAsync() is a default interface member that falls back to TryCreateUsage()?.Database. With the store versions Wolverine currently pins, this is correct but saves nothing yet. The saving is complete with Marten 9.50.1, Polecat 5.40.1 and Fisher 1.20.2, which override it to answer from their tenancy. Those releases are on their way, and an application can take them directly.

Tests

  • Fake event stores in the agent tests now go through a shared StubUsage helper. NSubstitute intercepts default interface members too, so stubbing only TryCreateUsage would leave the new reads answering null and an empty list. This includes the test GH-5003 Resume continuous execution after a projection rebuild, even a failed one #5008 added.
  • an_evaluation_builds_each_stores_usage_once now also asserts that an evaluation never calls TryCreateUsage.
  • New: a_store_without_a_shard_registry_still_answers_its_shards_through_the_usage.

Verification

  • dotnet build wolverine.slnx -c Release -f net9.0 against JasperFx 2.87.1: clean.
  • CoreTests.Runtime.Agents (net9.0): 486 passed, 6 skipped, 0 failed.
  • MartenTests.Distribution against local Postgres: 49 of 49 passed.

🤖 Generated with Claude Code

…he full event store usage

EventStoreAgents.SupportedAgentsAsync runs on every leader heartbeat for every
store. It built the whole EventStoreUsage descriptor (the highest event
sequence, and every projection, subscription, event type and tag type) for the
async shard names and the database topology alone.

- Async shard names come from IEventStore.RegisteredShardNames(), the store's
  in-memory registry. It is the same set: async projections plus subscriptions,
  which always report Async. A store that doesn't implement it falls back to the
  usage.
- The database topology comes from the new IEventStore.TryDescribeDatabasesAsync()
  (JasperFx 2.87.1). The tenant-to-database lookup, StartAllAsync and
  AllDaemonsAsync use it too. The registered-projection rebuild keeps the full
  usage, because it matches Inline and Live projections as well.
- JasperFx family 2.87.1. The saving is complete with Marten 9.50.1, Polecat 5.40.1
  and Fisher 1.20.2, which answer it straight from their tenancy; older store
  versions fall back to TryCreateUsage inside the default member.
- Tests: fake stores go through a shared StubUsage helper (NSubstitute intercepts
  default interface members too), an evaluation is pinned to never build the full
  usage, and a store without a shard registry still answers through the usage.

Closes #5002

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

SupportedAgentsAsync builds the full EventStoreUsage every heartbeat but reads only Subscriptions

1 participant