Fix #420: cap concurrent projection rebuilds per database - #425
Merged
Conversation
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>
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.
Closes #420.
What
Adds a
--max-concurrentflag todotnet run -- projections rebuildand applies it as theMaxDegreeOfParallelismof the per-database rebuild fan-out inProjectionHost.TryRebuildShardsAsync.Previously the fan-out over projections within a database was an unbounded
Parallel.ForEachAsync. On a wide store — many projections, especially underUseTenantPartitionedEventswhere 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 onmt_event_progression.MaxDegreeOfParallelism = -1).ProjectionInput.ResolveMaxDegreeOfParallelism()centralizes the flag→cap mapping and is unit tested (7 cases: unset, positive, and the0/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.MaxConcurrentRebuildsPerDatabaseand itsNpgsqlConnectionPoolSize / 8derived default live on Marten'sEventGraph/StoreOptions— they depend on Npgsql/Marten types not present in JasperFx. So does theEnableExtendedProgressionTrackinginteraction 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 viaCrossTenantRebuild.maxParallelismfor Marten's daemon to thread the same cap through.🤖 Generated with Claude Code