Skip to content

Fix #420: cap concurrent projection rebuilds per database - #425

Merged
jeremydmiller merged 1 commit into
mainfrom
fix-420-max-concurrent-rebuilds
Jun 8, 2026
Merged

Fix #420: cap concurrent projection rebuilds per database#425
jeremydmiller merged 1 commit into
mainfrom
fix-420-max-concurrent-rebuilds

Conversation

@jeremydmiller

Copy link
Copy Markdown
Member

Closes #420.

What

Adds a --max-concurrent flag to dotnet run -- projections rebuild and applies it as the MaxDegreeOfParallelism of the per-database rebuild fan-out in ProjectionHost.TryRebuildShardsAsync.

Previously the fan-out over projections within a database was an unbounded Parallel.ForEachAsync. On a wide store — many projections, especially under UseTenantPartitionedEvents where each projection further fans out per tenant — that could spawn enough concurrent rebuild agents to exhaust the connection pool, thrash the buffer cache, and contend on mt_event_progression.

  • A null / non-positive flag preserves the historical unbounded behavior (MaxDegreeOfParallelism = -1).
  • The effective cap is logged at rebuild start so operators can confirm the throttle is active.
  • ProjectionInput.ResolveMaxDegreeOfParallelism() centralizes the flag→cap mapping and is unit tested (7 cases: unset, positive, and the 0/negative degenerate cases that would otherwise throw).

Tests

ProjectionInputTests — unset → unbounded; positive cap honored; non-positive falls back to unbounded. Full solution compiles clean in Release (net9.0/net10.0).

Deliberately out of scope (Marten-side follow-up, can't run in this repo)

The issue's central config knob StoreOptions.Events.MaxConcurrentRebuildsPerDatabase and its NpgsqlConnectionPoolSize / 8 derived default live on Marten's EventGraph/StoreOptions — they depend on Npgsql/Marten types not present in JasperFx. So does the EnableExtendedProgressionTracking interaction analysis and the Postgres-backed 8-projection × 32-tenant cap regression test. This PR delivers the JasperFx-side honoring mechanism (CLI flag + fan-out cap + logging); the per-tenant layer is already parameterized via CrossTenantRebuild.maxParallelism for Marten's daemon to thread the same cap through.

🤖 Generated with Claude Code

Adds a `--max-concurrent` flag to the `projections rebuild` CLI and applies it
as the MaxDegreeOfParallelism of the per-database rebuild fan-out in
ProjectionHost.TryRebuildShardsAsync. Previously the fan-out over projections
was unbounded, so a wide store (many projections, especially under per-tenant
event partitioning) could spawn enough concurrent rebuild agents to blow the
connection pool and thrash the buffer cache.

A null / non-positive flag preserves the historical unbounded behavior, and the
effective cap is logged at rebuild start so operators can confirm the throttle.

ProjectionInput.ResolveMaxDegreeOfParallelism centralizes the flag->cap mapping
(unset/<=0 => -1 unbounded) and is unit tested.

Out of scope (Marten-side follow-up): the StoreOptions.Events
.MaxConcurrentRebuildsPerDatabase configuration knob with the
NpgsqlConnectionPoolSize/8 derived default, the EnableExtendedProgressionTracking
interaction analysis, and the Postgres-backed 8-projection/32-tenant cap
regression test all live in the Marten repo (they depend on Npgsql/Marten types
not available in JasperFx). The per-tenant rebuild layer is already
parameterized by CrossTenantRebuild.maxParallelism for the Marten daemon to pass
the same cap through.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jeremydmiller
jeremydmiller merged commit be827b2 into main Jun 8, 2026
1 check passed
@jeremydmiller
jeremydmiller deleted the fix-420-max-concurrent-rebuilds branch June 8, 2026 17:28
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.

Cap concurrent projection rebuilds per database (MaxConcurrentRebuildsPerDatabase)

1 participant