ecluse
Safe HaskellNone
LanguageGHC2021

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

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.