| Safe Haskell | None |
|---|---|
| Language | GHC2021 |
Ecluse.Composition.Sizing
Description
The config-derived runtime sizings of the composition root: the serve-admission capacity, the two connection-pool sizes and the file-descriptor datapoint they are computed from, and the mirror-enqueue buffer tunables. (The byte-valued bounds are partitioned separately, from the heap ceiling: Ecluse.Composition.MemoryPlan.)
Each resolution is a pure function of the validated configuration (plus, for the
pools, the process file-descriptor limit read once by openFileSoftLimit): an
explicit config value always wins, and a computed default returns its boot-log
line alongside the number so the decision's provenance lands in the standard boot
log. Nothing here opens a socket or reads a clock; the composition root applies
the results when it builds the managers and the admission gate.
Synopsis
- connectionPoolSettings :: Int -> ManagerSettings -> ManagerSettings
- resolveServeAdmission :: Maybe Int -> Int -> (Int, Text)
- resolvePrivateConnections :: Maybe Int -> Int -> (Int, Text)
- resolvePublicConnections :: Maybe Int -> Int -> (Int, Text)
- openFileSoftLimit :: IO Int
- mirrorEnqueueBufferDepth :: Int
- mirrorEnqueueReportInterval :: Int
Connection pools and admission
connectionPoolSettings :: Int -> ManagerSettings -> ManagerSettings Source #
Apply an explicit per-host connection bound to an HTTP manager's settings.
The public and private managers call this independently after telemetry instrumentation, so changing the pool size cannot discard the instrumented request and response hooks.
resolveServeAdmission :: Maybe Int -> Int -> (Int, Text) Source #
The effective serve-admission capacity and its boot-log line: the explicit
serveMaxInFlight when configured, else __computed from the resolved capability
count__ -- max 8 (10 x capabilities).
The multiplier is empirical, not modelled. The saturation model (an admitted
metadata materialisation alternates upstream wait W and CPU work P, so
keeping C capabilities busy wants about C x (W + P) / P in flight)
suggested ~4 per capability at a round-trip W/P of 2-3, but the load bench's
measured dose-response kept climbing well past that and levelled only near 10
per capability: a slot is held across every upstream leg plus GC pauses and
scheduling delay, so the effective W/P is nearer 9-10. The floor keeps a tiny
pod admitting a useful burst should the multiplier ever drop below it. The
capability count must be the post-runtime-posture one (see Ecluse.Rts),
so callers resolve this after applyRuntimePosture has run.
The returned line carries the decision's provenance for the standard boot log,
alongside the runtime posture lines. This bounds only metadata materialisation
(whole packument requests and a tarball miss's public-metadata gate). The private
connection pool is not sized from it -- see resolvePrivateConnections: a trusted
tarball hit streams outside admission, so demand on the private pool is the inbound
hit concurrency, not the admission capacity, and tying the two would undersize that pool
under a private-hit fan-out (http-client opens throwaway connections beyond the pool,
paying a TLS handshake per overflow request).
resolvePrivateConnections :: Maybe Int -> Int -> (Int, Text) Source #
The effective private-upstream connection-pool size and its boot-log line: the
explicit privateConnectionsPerHost when configured, else __computed from the process
file-descriptor limit__ -- clamp 64 4096 (nofile / 4).
The private pool caches idle connections to the trusted upstream for __reuse across
concurrent private-hit tarball streams. Those streams are IO-bound__ and, unlike
metadata materialisation, stream outside serve admission, so their concurrency (and
thus the pool's real demand) is the inbound hit fan-out, not the CPU-saturation model
resolveServeAdmission uses -- which is exactly why this is computed from a different
datapoint and is not tied to serveMaxInFlight (see issue #634's incomplete
inference: the private pool also serves the un-admitted streaming path).
Each pooled connection is one file descriptor, so the file-descriptor limit is the pool's
real physical ceiling. The default takes a quarter of the soft RLIMIT_NOFILE as the
reuse cache, floored at privateConnectionsFloor so a small-limit host still reuses
connections across an install fan-out, and capped at privateConnectionsCap so an
enormous-limit host does not retain an absurd idle cache to a single upstream. A larger
pool never opens more sockets than the concurrency already demands (http-client opens a
connection per in-flight request regardless); it only decides how many to __retain for
reuse__ rather than re-handshake, so sizing up is safe. An operator who knows their
fan-out can override it outright.
The returned line carries the decision's provenance for the standard boot log.
resolvePublicConnections :: Maybe Int -> Int -> (Int, Text) Source #
The effective public-upstream connection-pool size and its boot-log line: the
explicit publicConnectionsPerHost when configured, else __computed from the process
file-descriptor limit__ -- clamp 32 1024 (nofile / 8).
The public pool's metadata demand is small by construction (same-key misses are
single-flight-coalesced and bounded by admission), but the pool is not
metadata-only: the onboarding fail-over's artifact streams and the mirror worker's
back-fill fetches ride the same manager, and neither coalesces. During a cold fleet's
onboarding burst the concurrent public streams track the inbound fan-out, and
managerConnCount is a keep-alive retention cap, not a
concurrency cap: overflow opens throwaway connections, each paying a TLS handshake to
the public origin per request. So the pool is sized like the private one, from the
file-descriptor budget, at half the private share (an eighth of nofile, from the
three quarters the private sizing reserves for everything else): the public leg is
transient by the traffic model -- the worker retires it artifact by artifact -- so it
earns retention for the burst, not the steady state. Sizing up is safe for the same
reason as the private pool: it never opens more sockets than the concurrency already
demands, only retains more for reuse.
The returned line carries the decision's provenance for the standard boot log.
openFileSoftLimit :: IO Int Source #
The process soft file-descriptor limit (RLIMIT_NOFILE), the datapoint
resolvePrivateConnections sizes the private pool against. An infinite or
unknown limit falls back to privateConnectionsCap x privateConnectionsFdShare, so
the computed pool lands at the cap rather than overflowing.
Mirror-enqueue buffering
mirrorEnqueueBufferDepth :: Int Source #
The depth of the producer-side hand-off buffer the composition root wraps in
front of the mirror queue (newEnqueueBuffer). Sized to absorb a
cold npm ci's enqueue burst (a lockfile fan-out enqueues one job per public-served
tarball) while bounding memory; a job dropped at the cap is re-enqueued on the next
demand for its artifact, so overflow costs a deferred mirror, never correctness.
mirrorEnqueueReportInterval :: Int Source #
How many enqueue-buffer drops or delivery failures pass between warning-log reports at the composition root (the first is always reported, then every multiple of this). The buffer's callbacks fire per event so the failure counter stays exact, while a sustained flood logs one line per this many events rather than one per job.