Per-database event-load throttle (#494 Phase A, epic #486 WS2)#495
Merged
Conversation
…on (#494 Phase A) Every subscription agent's IEventLoader.LoadAsync opens its own store session, so a daemon running (projections x tenants) agents under per-tenant event partitioning drives the connection pool's high-water mark toward the agent count even though measurement (marten ScaleTesting daemonload, 200 agents) shows peak BUSY connections of ~5. All agent loaders a daemon builds now share one SemaphoreSlim sized by DaemonSettings.MaxConcurrentEventLoadsPerDatabase (default 4; zero or negative disables), applied as a ThrottledEventLoader decorator in buildAgentForShard. The composite replay loader path is deliberately NOT throttled - it is one serial loader per rebuild, already bounded by the per-database rebuild cap. Verified against the daemonload scenario (100 tenants x 2 projections = 200 agents, continuous append): event-loader sessions observed at <= 4 via pg_stat_activity last-query attribution, 100/100 tenants caught up, append throughput unchanged. Total pool size is NOT collapsed by this alone in an all-tenants-active workload - 29 of ~35 steady-state sessions are projection batch COMMIT sessions, which is the WS3 write-governor's territory (evidence recorded on #494). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
jeremydmiller
added a commit
that referenced
this pull request
Jul 7, 2026
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.
Phase A of #494 (epic #486 WS2 — connection conservation).
What
Every subscription agent's
IEventLoader.LoadAsyncopens its own store session, so a daemon running (projections × tenants) agents under per-tenant partitioning drives the pool's high-water mark toward the agent count. All agent loaders a daemon builds now share oneSemaphoreSlimsized byDaemonSettings.MaxConcurrentEventLoadsPerDatabase(default 4; ≤0 disables), applied as aThrottledEventLoaderdecorator inbuildAgentForShard. The composite replay loader path is deliberately NOT throttled — one serial loader per rebuild, already bounded by the per-database rebuild cap.Verification
daemonload(marten ScaleTesting; 100 tenants × 2 projections = 200 agents, continuous append): event-loader sessions observed ≤ 4 viapg_stat_activitylast-query attribution; 100/100 tenants caught up, throughput unchanged.Honest finding → WS3
The throttle alone does not collapse total pool size in an all-tenants-active steady state: live sampling shows 29 of ~35 open sessions last executed
COMMIT— they're per-agent projection-batch write sessions, which are unbounded (the racy cold-start burst of 200 first-batch commits explains run-to-run peaks of ~35 vs ~99). That's the WS3 write governor's territory, now with hard evidence; measurement details on #494. Where Phase A matters most on its own: cold-start/catch-up storms, which would otherwise be 200 concurrent loads.🤖 Generated with Claude Code