Derive the detached-write in-flight caps from the resources they draw on, not per consumer
Re-measured on 2026-08-27: eleven caps, two resources
This issue opened against five in-flight caps of 64, all drawing on one Postgres pool. Eleven exist now, and they no longer draw on one resource. Each is still sized on its own, and none accounts for the others.
Every cap below is 64, acquired without blocking, and it sheds past the cap.
Postgres pool — six caps of the original shape
datastore.NewAppClient opens one *sql.DB shared by the whole app.
| Cap | Site |
|---|---|
bufferedUpdateMaxInFlight |
internal/format/npm/buffered.go:67 |
rebuildMaxInFlight |
internal/format/npm/packument_cache.go:421 |
bufferedUpdateMaxInFlight |
internal/format/maven/buffered.go:108 |
bufferedCounterUpdateMaxInFlight |
internal/managementapi/artifact_write.go:417 |
detachedBumpMaxInFlight |
internal/remote/standalone.go:465 |
downloadSignalMaxInFlight |
internal/format/oci/remote_download.go:74 |
Five of the six are reachable, which sums to 320 pooled connections under a slow datastore.
The sixth holds its draw at zero, and internal/remote/standalone.go states why: oci.RemoteManifestHandler.ServeRemoteRead dispatches its signal only after a fresh cached manifest serve, and oci.RemoteCacheStore.UpsertCacheEntry has no production caller to fill that cache.
That sixth cap becomes reachable when the Container remote cache fill lands.
#834 is the issue that adds the caller.
The figure moves from 320 to 384 at that merge, and internal/remote/standalone.go already names 384 as the number to expect.
Redis — four caps that did not exist when this issue was filed
These four bound the storage-accounting emit.
accounting.Emitter.record makes two Redis calls per emit, bounded at detachedEmitTimeout of 5 seconds.
| Cap | Site |
|---|---|
deleteCounterEmitMaxInFlight |
internal/managementapi/artifact_write.go:291 |
counterEmitMaxInFlight |
internal/format/npm/counter_emit_dispatch.go:68 |
counterEmitMaxInFlight |
internal/format/oci/emit_dispatch.go:93 |
counterEmitMaxInFlight |
internal/format/maven/upload_emit_dispatch.go:127 |
Four independent copies of one decision, one per emit site, is the symptom.
The cause is that internal/accounting states no latency or shedding contract, so every call site decides for itself.
#920 owns that decision.
This issue and work item 920 collide, and the order matters.
If work item 920 rules that internal/accounting detaches, bounds, spawns and sheds on its own behalf, these four caps collapse into one that the package owns.
Sizing four caps that a pending decision can delete is wasted work.
Settle work item 920 first, then size what survives.
Neither pool
| Cap | Site | What it bounds |
|---|---|---|
inlineBuildMaxInFlight |
internal/format/npm/inline_build.go:96 |
heap, on a distinct-key miss burst |
What needs to change
The sizing has to be derived from each resource's total capacity across every consumer that draws on it, rather than chosen per consumer. A per-consumer constant cannot bound a shared resource however carefully it is picked, and adding consumers is what makes the total grow.
That derivation belongs to the cross-format buffered-write subsystem that bufferedUpdate's TODO(buffered) already names as the owner of pool-aware backpressure.
Six of the eleven sites carry a comment saying the same thing in their own words, and three of them name this issue.
Deliberately not the fix here:
- Halving the caps. That is still a per-consumer guess, only smaller.
- One process-wide semaphore shared between two packages. That introduces a shared-instance seam of the kind AGENTS.md requires a composition test for, and neither package owns the other's primitive.
Timing
The original timing note said the total stops rising for npm remote once !1682 (merged) merges. That merge happened, and the total has risen twice since, in a way the note did not predict.
The Redis family arrived with the storage-accounting emit sites and added four caps against a resource this issue did not name. The Postgres family gains its sixth reachable cap when work item 834 lands. The figure to size against is therefore 384 on Postgres and 256 on Redis, and the Redis half is the half a pending decision can delete.
The immediate consumers also share one process-wide semaphore across column labels, so a downloads_count pile-up on a hot row can shed last_downloaded_at dispatches the pile-up did not cause.
Maven's bufferedUpdateMaxPerLabel is the only per-label sub-quota in the set.
Related
- #920 — the emit contract that decides whether the four Redis caps survive at all. Settle it before this one.
- #871 — the purger's emit, which has no cap and no bound today.
- #834 — moves the Postgres figure from 320 to 384.
- #632 — a shed write has no reconciliation of its own on the unowned columns.
Raised in review on !1291 (comment 3667539674), and widened on the !1439 (merged) review.