| Safe Haskell | None |
|---|---|
| Language | GHC2021 |
Ecluse.Composition.MemoryPlan
Description
The composition root's memory plan: one solver that partitions the effective heap ceiling between named tenants whose sum is bounded by the ceiling, so the budgets compose instead of each claiming an independent share of the same bytes.
With an effective ceiling H (Ecluse.Rts, what the RTS actually runs with), the
tenants are, in allocation order:
- The runtime reserve: GC copying headroom, stacks, buffers, and the RTS itself, taken off the top; everything else partitions the remainder (the application heap).
- Fixed buffers: the mirror enqueue hand-off buffer, charged whenever any mount mirrors (whatever the backend).
- The cache aggregate: ONE byte budget for all three metadata stores, split
into their named sub-budgets at
planCacheConfig(the split sums to the aggregate, so the cache can never triple its tenant). - The material aggregate: response materialisation working space. The
admission capacity is
max 1 (min A_cpu A_mem)-- the CPU-derived capacity (Ecluse.Composition.Sizing) jointly bounded by what the material share can actually hold, one envelope (packumentOriginFanoutconcurrent origins, each wire+parsed atexpandWireBytes) per admitted operation. - The publish aggregate: the total bytes concurrently buffered publish bodies may hold (the byte-admission the publish pipeline acquires before reading a body), present only when a publication target is configured.
- The queue tenant: the in-memory mirror queue's depth, charged only when the memory backend was selected (the selection precedes this plan; an SQS deployment spends no heap on queued jobs).
- The mirror-artifact tenant: the transient publish envelope one back-fill
job holds, charged whenever any mount mirrors (the same predicate as the fixed
buffer). The worker buffers a fetched tarball whole and base64-encodes it into a
strict publish document, so the tarball bytes, the base64
Text, and the serialised document coexist before GC at roughlymirrorArtifactEnvelopeMultipliertimes the tarball; the worker's per-artifact byte cap (matMaxBytes) is this share divided back down by that multiplier, so the envelope the cap admits is what the tenant charges (issue #846).
The combined invariant -- reserve + cache + material + publish + queue + fixed buffers + mirror-artifact within the ceiling -- is enforced by construction for computed shares. Explicit overrides are re-checked by attribution, described under degradation below: a pin refuses the boot only when it is what a fitting plan cannot shed around, never on mere presence.
Graceful degradation, never a refusal
A pod too small for the tenants' normal floors sheds in a documented priority order, each step a loud boot warning naming what was given up and why: first the mirror-artifact cap shrinks, ultimately to zero (the background back-fill leg gives way before the serve hot path, so a starved pod protects its cache and admission and mirrors nothing it cannot buffer safely); then the cache aggregate shrinks below its floor, ultimately to zero (the proxy serves uncached); then admission shrinks toward one in-flight operation (and, where the nursery is the real pressure, the capability count sheds with it, automating the smaller-cores recommendation); then the publish aggregate shrinks to one maximum request; then the queue depth to its floor. One operation on one capability with no cache is the irreducible minimum and __always boots__ -- if even that exceeds the ceiling, the plan says so in its loudest warning and boots anyway (the cgroup backstop is then the guard).
Only an explicit operator override refuses the boot, and only when it is the cause. The plan re-derives the override-free minimum (every pin substituted out, every computed tenant at its floor) and refuses just when that minimum fits the ceiling while the pinned plan does not, naming the pins whose individual removal would fit (all of them when only their combination overshoots). A pin at or below the value the shed ladder would compute anyway pushes the plan past nothing and never refuses; a pod too small even without the pins boots with the loud warning like any other. An override is an operator claim, and a claim that alone breaks the ceiling is a misconfiguration to fix, not to shrink around.
With no ceiling datapoint at all, every bound falls back to the shipped values that predate the plan. An explicit config value always wins its own bound, and every decision returns a provenance line for the boot log.
Synopsis
- data MemoryPlan = MemoryPlan {
- mpRuntimeReserveBytes :: Int
- mpCacheAggregateBytes :: Int
- mpCacheMaxEntries :: Int
- mpMaterialAggregateBytes :: Int
- mpMaxResponseBytes :: Int
- mpMaxRequestBytes :: Int
- mpAdmissionCapacity :: Int
- mpShedCapabilities :: Maybe Int
- mpPublishTenant :: Maybe PublishTenant
- mpMirrorArtifactTenant :: Maybe MirrorArtifactTenant
- mpQueueMemoryMaxDepth :: Int
- mpQueueTenantBytes :: Int
- mpFixedBufferBytes :: Int
- mpDegradations :: [Text]
- mpOverrideViolations :: [Text]
- newtype PublishTenant = PublishTenant {}
- newtype MirrorArtifactTenant = MirrorArtifactTenant {
- matMaxBytes :: Int
- data QueueTenantDemand
- queueTenantDemand :: MirrorRuntimePlan -> QueueTenantDemand
- resolveMemoryPlan :: CacheSettings -> LimitsSettings -> QueueSettings -> Maybe Int -> EffectiveRuntimePlan -> QueueTenantDemand -> Bool -> (MemoryPlan, [Text])
- type OverridePins = (Maybe Int, Maybe Int, Maybe Int, Maybe Int, Maybe Int, Maybe Int)
- overrideMinShedSum :: Int -> Int -> Bool -> Bool -> Bool -> OverridePins -> Int
- overrideSubstitutions :: OverridePins -> [(Text, OverridePins)]
- attributeOverrideViolations :: Int -> Int -> Int -> [(Text, Int)] -> [Text]
- planCacheConfig :: CacheSettings -> MemoryPlan -> CacheConfig
- mirrorArtifactEnvelopeMultiplier :: Int
- mirrorArtifactBytesCap :: Int
Documentation
data MemoryPlan Source #
The resolved plan: every byte-valued bound the composition root builds with, each an explicit config value or its tenant-derived default, plus the degradation warnings the solver took and any explicit-override violations (the one refusal).
Constructors
| MemoryPlan | |
Fields
| |
Instances
| Show MemoryPlan Source # | |
Defined in Ecluse.Composition.MemoryPlan Methods showsPrec :: Int -> MemoryPlan -> ShowS # show :: MemoryPlan -> String # showList :: [MemoryPlan] -> ShowS # | |
| Eq MemoryPlan Source # | |
Defined in Ecluse.Composition.MemoryPlan | |
newtype PublishTenant Source #
The publish tenant: the aggregate byte-admission for concurrently buffered bodies.
Constructors
| PublishTenant | |
Fields | |
Instances
| Show PublishTenant Source # | |
Defined in Ecluse.Composition.MemoryPlan Methods showsPrec :: Int -> PublishTenant -> ShowS # show :: PublishTenant -> String # showList :: [PublishTenant] -> ShowS # | |
| Eq PublishTenant Source # | |
Defined in Ecluse.Composition.MemoryPlan Methods (==) :: PublishTenant -> PublishTenant -> Bool # (/=) :: PublishTenant -> PublishTenant -> Bool # | |
newtype MirrorArtifactTenant Source #
The mirror-artifact tenant: the mirror worker's per-artifact byte cap. Present only when some mount mirrors.
matMaxBytes is the tarball byte cap the worker's bounded fetch enforces
(fetchArtifactBytes). The heap the combined invariant
charges for it is that cap scaled by mirrorArtifactEnvelopeMultiplier -- what the
buffered tarball, its base64 Text, and the serialised publish document may
transiently hold together.
Constructors
| MirrorArtifactTenant | |
Fields
| |
Instances
| Show MirrorArtifactTenant Source # | |
Defined in Ecluse.Composition.MemoryPlan Methods showsPrec :: Int -> MirrorArtifactTenant -> ShowS # show :: MirrorArtifactTenant -> String # showList :: [MirrorArtifactTenant] -> ShowS # | |
| Eq MirrorArtifactTenant Source # | |
Defined in Ecluse.Composition.MemoryPlan Methods (==) :: MirrorArtifactTenant -> MirrorArtifactTenant -> Bool # (/=) :: MirrorArtifactTenant -> MirrorArtifactTenant -> Bool # | |
data QueueTenantDemand Source #
Whether the memory plan owes the in-memory queue a tenant, projected from the
already-made backend selection (planMirrorRuntime):
only the memory backend spends heap on queued jobs, while any mirroring at all
charges the fixed enqueue buffer.
Constructors
| NoQueueTenant | No mount mirrors: no queue tenant, no enqueue buffer. |
| MirroringWithoutMemoryQueue | Mirroring rides a durable backend: the enqueue buffer alone is charged. |
| MemoryQueueTenant | Mirroring rides the in-memory queue: its depth is a tenant of this plan. |
Instances
| Show QueueTenantDemand Source # | |
Defined in Ecluse.Composition.MemoryPlan Methods showsPrec :: Int -> QueueTenantDemand -> ShowS # show :: QueueTenantDemand -> String # showList :: [QueueTenantDemand] -> ShowS # | |
| Eq QueueTenantDemand Source # | |
Defined in Ecluse.Composition.MemoryPlan Methods (==) :: QueueTenantDemand -> QueueTenantDemand -> Bool # (/=) :: QueueTenantDemand -> QueueTenantDemand -> Bool # | |
queueTenantDemand :: MirrorRuntimePlan -> QueueTenantDemand Source #
Project the queue-tenant demand from the resolved mirror runtime plan.
resolveMemoryPlan :: CacheSettings -> LimitsSettings -> QueueSettings -> Maybe Int -> EffectiveRuntimePlan -> QueueTenantDemand -> Bool -> (MemoryPlan, [Text]) Source #
Resolve the memory plan and its boot lines from the configuration groups, the effective runtime plan, the explicit admission override, the queue-tenant demand (the backend selection precedes this plan), and whether any mount publishes.
overrideMinShedSum :: Int -> Int -> Bool -> Bool -> Bool -> OverridePins -> Int Source #
The fully-shed minimum tenant sum for a hypothetical set of explicit pins: the
pin-independent base (runtime reserve plus fixed buffers) plus each shedable tenant
at its pinned value, or un-pinned at the floor the shed ladder would reach -- cache
to zero, admission to one operation, response to its floor, the publish aggregate to
the computed one-request floor, the queue depth to its floor, and the mirror-artifact
envelope to zero (it sheds fully). memoryBacked gates the queue tenant and mirrors
the mirror-artifact tenant: a durable or absent backend spends no heap on depth, and a
non-mirroring deployment none on the artifact envelope, whatever the pin's value.
Comparing this across pin sets attributes a residual overshoot to the pins that cause it.
overrideSubstitutions :: OverridePins -> [(Text, OverridePins)] Source #
Each explicit override present in the pin set, paired with the pin set that substitutes only it out (the value the shed ladder would reach without it) and tagged with the operator's config-key name, in the plan's allocation order. An absent override contributes no substitution.
attributeOverrideViolations :: Int -> Int -> Int -> [(Text, Int)] -> [Text] Source #
Decide the override refusal from the residual overshoots, and name the culprits.
Refuse only when the override-free minimum fits the heap ceiling (freeOvershoot is
zero) while the pinned plan does not (overriddenOvershoot is positive): the pins are
then the cause, and a pod too small even without them is a degradation the shed
ladder already warned about, never a refusal. Name the pins whose individual removal
makes the plan fit (their one-out overshoot is zero); when none alone flips the
verdict (the pins only overshoot in combination), name them all rather than
under-blame. The message reports only the overshoot the named pins are responsible
for, since the override-free minimum fits within the ceiling.
planCacheConfig :: CacheSettings -> MemoryPlan -> CacheConfig Source #
The metadata cache's tunables: the configured TTL married to the plan's cache aggregate, split into the three stores' named sub-budgets __summing exactly to the aggregate__ (the assembled share is the remainder). The shares: the full-packument store carries the decoded working set at 60%; the single-version store holds small flat entries at 15% but four times the entry count; the assembled store's encoded documents take the remaining 25%. A fully-shed (zero) aggregate yields stores that retain nothing: every value takes the oversized pass-through and the proxy serves uncached.