\Drupal\strata\Budget EscalationLadder

What Strata gives up as a budget fills, and the order it gives it up in.

Rungs run lowest first, cheapest and most reversible first, and a reading only ever moves one rung at a time in either direction. A budget that keeps overshooting climbs at a rate an operator can watch, and one that comes back under walks down on its own instead of staying clamped until somebody notices.

  • normal: under budget, nothing changes.
  • warn: over the soft fraction. Logged and surfaced; capture and retention are untouched.
  • reduce: retention shortens, so compaction prunes more history on each pass.
  • pause: the non-critical realms stop being captured. Entity, config, state, key-value, table and schema keep going.
  • stop: nothing is captured at all and the journal stops accepting appends.

The line that matters sits between reduce and pause. Everything at or below reduce keeps every realm covered and only trades away depth of history, so the worst case is a shorter window to restore from. Pause and stop drop data that is never captured and therefore can never be restored, which is the ladder giving up the thing a backup exists to provide.

The realms that go first are the ones a site can rebuild from somewhere else: files usually exist in the media library's own storage, ephemeral state rebuilds itself, and code comes from the release artifact. What stays is what only the database holds.

Summary

Methods
Properties
Constants
rungFor
cap
escalate
decay
pausesRealm
stopsEverything
rank
No public properties found
NORMAL
WARN
REDUCE
PAUSE
STOP
RUNGS
UNRANKED
PAUSED_REALMS
WARN_AT
REDUCE_AT
PAUSE_AT
STOP_AT
No protected methods found
No protected properties found
No protected constants found
No private methods found
No private properties found
No private constants found

Constant

NORMAL

NORMAL = 'normal'

Under budget.

WARN

WARN = 'warn'

Over the soft fraction; reported and nothing else.

REDUCE

REDUCE = 'reduce'

Retention shortens and compaction prunes harder.

PAUSE

PAUSE = 'pause'

The non-critical realms stop being captured.

STOP

STOP = 'stop'

Nothing is captured and the journal refuses appends.

RUNGS

RUNGS = [self::NORMAL, self::WARN, self::REDUCE, self::PAUSE, self::STOP]

The rungs, lowest first.

UNRANKED

UNRANKED = -1

Rank returned for a rung that is not on the ladder.

PAUSED_REALMS

PAUSED_REALMS = [\Drupal\strata\Journal\Realm::FILE->value, \Drupal\strata\Journal\Realm::EPHEMERAL->value, \Drupal\strata\Journal\Realm::CODE->value]

The realms capture drops at the pause rung.

Held as realm values rather than cases so a rung read back out of state can be compared without being parsed first.

WARN_AT

WARN_AT = 0.8

Fraction of budget at which the warn rung starts.

REDUCE_AT

REDUCE_AT = 1.0

Fraction of budget at which the reduce rung starts; the ceiling itself.

PAUSE_AT

PAUSE_AT = 1.25

Fraction of budget at which the pause rung starts.

STOP_AT

STOP_AT = 1.5

Fraction of budget at which the stop rung starts.

Methods

rungFor()

rungFor(float  $usedFraction) : string

The rung a projected usage lands on.

Read with >= at every step, so a fraction above STOP_AT lands on stop rather than falling through. A negative fraction, or one that is not a number at all, lands on normal: neither is evidence of overspending, and neither should stop a backup.

Parameters

float $usedFraction

Projected usage divided by the ceiling. 1.0 is exactly the ceiling.

Returns

string —

A rung name from RUNGS.

cap()

cap(string  $measured, string  $allowed) : string

The measured rung, held to the highest one the site allows.

budget.action is what the operator chose to happen at the ceiling, and the thresholds above are what the overspend measures. Without this the two disagree: a site that asked for Warn Only has its capture stopped at 1.5x, which is the opposite of what it said. So the choice is a ceiling on the ladder rather than a separate setting.

Parameters

string $measured

The rung EscalationLadder::rungFor() returned.

string $allowed

The highest rung configuration permits. A value not on the ladder allows everything, since refusing to act on an unreadable setting would mean ignoring a real overspend.

Returns

string —

Whichever of the two is lower.

escalate()

escalate(string  $rung) : string

One rung up, saturating at the top.

Saturation rather than an exception, because escalation runs on the path where a budget has already been exceeded; throwing there adds a failure on top of the one being reported.

Parameters

string $rung

Current rung.

Returns

string —

The next rung up, the same rung at the top, or the lowest rung when $rung is not on the ladder at all.

decay()

decay(string  $rung) : string|null

One rung down, or NULL at the bottom.

NULL is the signal to stop tracking the budget state entirely rather than to store normal forever; a site that has never been near its ceiling should carry no rung at all.

Parameters

string $rung

Current rung.

Returns

string|null —

The rung below, or NULL when already at the bottom or not on the ladder.

pausesRealm()

pausesRealm(string  $rung, string  $realm) : bool

Whether capture of one realm is suspended at this rung.

An unrecognised rung suspends nothing. The ladder is driven by BudgetGuard, which only ever produces names from RUNGS, so the only way to arrive here with something else is state that has been corrupted or written by a version that disagrees; stopping a backup on the strength of that costs more than the overspend it might have prevented.

Parameters

string $rung

The rung in force.

string $realm

A realm value, as Realm stores it.

Returns

bool —

TRUE when nothing in that realm should be captured.

stopsEverything()

stopsEverything(string  $rung) : bool

Whether this rung stops capture outright.

Parameters

string $rung

The rung in force.

Returns

bool —

TRUE only at STOP.

rank()

rank(string  $rung) : int

The position of a rung on the ladder.

Parameters

string $rung

The rung name.

Returns

int —

Its index in RUNGS, or UNRANKED when the name is not a rung.