Skip to content

Blackout Windows

A blackout is a one-off wall-clock window during which the checks it covers are silenced — declared ahead of a maintenance window, a version upgrade, a failover test, or any other period when alerting on a system you're deliberately disrupting would just be noise. Like events, this layer is entirely opt-in: with no blackout windows declared, nothing behaves any differently.

There are two kinds of silence, chosen per window with p_mode, and one principle separates them: a run blackout is invisible to the truth layer; an alerting blackout is invisible to the reporting layer.

Mode What it means What still happens Use it for
'alerting' (the default) "Keep watching, tell no one." Collection and evaluation run completely normally — real severities, debounce streaks advance, history stays complete. Only reporting is silenced: the evaluation is stamped not-reportable and no event fires. The common maintenance window. What happened during it is exactly the data you'll want afterwards.
'run' "Don't even look." Nothing — collection is suppressed (the check is never invoked) and evaluation of anything already collected is suppressed too. The gap is intended, and recorded. Windows where the data itself would be garbage or the checks dangerous or expensive to run — mid-restore, mid-upgrade.
-- Sunday 02:00-04:00 upgrade window: keep collecting Spock data, silence the alerts
SELECT pghf.create_blackout('2026-08-30 02:00+10', '2026-08-30 04:00+10',
    'Spock 4.1 rolling upgrade', p_namespace_code => 'PGHF', p_category_code => '12');

-- Same window, but don't even run the checks (the stronger, explicit action):
SELECT pghf.create_blackout('2026-08-30 02:00+10', '2026-08-30 04:00+10',
    'Spock 4.1 rolling upgrade', p_mode => 'run',
    p_namespace_code => 'PGHF', p_category_code => '12');

Choosing the scope

One window covers exactly one of:

Scope How to declare it
Global — every check No scope argument at all
One check p_check_id => 'PGHF01-001'
One category p_namespace_code => 'PGHF', p_category_code => '12'

A check combined with a category is rejected — a check's category is already known, so that combination could only be redundant or contradictory. There is deliberately no suite scoping: like thresholds, blackouts live at check grain — suites decide what runs together, checks own what the results mean — and a suite-scoped window could never keep the "this won't alert" promise anyway, since the same check observed via another suite would still advance its one evaluation stream.

Overlapping windows simply add up, per mode: a check is silenced whenever any matching window of that mode covers the current moment. There is deliberately no "un-blackout" override that punches a hole through a wider window — if you need one check running during a category-wide blackout, declare the blackout on the other checks instead.

When it applies

Windows are half-open — a blackout is active from starts_at (inclusive) up to but not including ends_at — and the reason is mandatory, because every suppression the window produces records it.

The engines re-check per check, at the moment that check's turn comes:

  • The collection engine consults the clock as it reaches each member of the suite ('run'-mode windows only — an 'alerting' window never touches collection) — so a window opening partway through a long run suppresses the members dispatched after it opens, and leaves the ones already collected alone.
  • The evaluation engine consults it again, independently, as it reaches each result — the 'run' check deciding whether to judge at all, and the 'alerting' check deciding whether to report what it judged.

What a suppression looks like

Never a silent gap, in either mode. A 'run' suppression records a normal result row with status SKIPPED and a note of [blackout <id>] <your reason> (or, at the evaluation stage, a normal evaluation with severity skipped and a {"blackout_id": N} marker). An 'alerting' suppression records a completely normal evaluation — the real severity, message, and detail — just stamped reportable = false and tagged {"alerting_blackout_id": N}: the row itself says both what was true and why nobody was told. Runs stay complete, pghf.get_run_status() stays truthful, and six months later the history says why each silence exists.

No alert is ever lost in a window

The markers matter, and each mode's marker is excluded from exactly the layer its mode silences:

  • 'run' rows are invisible to the truth layer. Every debounce and hysteresis lookback skips them: a breach streak that was one run short when the window opened resumes exactly where it left off; a confirmed critical cannot quietly clear during the window (a blackout means not observed — the check must actually be seen healthy, the configured number of times, afterwards); and a check still breaching when the window closes does not spuriously re-fire its event.
  • 'alerting' rows are invisible to the reporting layer only. They are real observations — streaks and recovery counting use them like any other run — but they never form the "last thing anyone was told" baseline for events or for report-interval stamping. That's the passive catch-up mechanism: a check that goes critical inside an alerting window and is still critical after it fires its event exactly once, at the first evaluation after the window closes — the baseline is the last pre-window severity, so the post-window row is a genuine transition. A check that broke and fully recovered inside the window never alerts at all — which is exactly what you declared the blackout to achieve. No end-of-window job, no timer: the catch-up rides on the next run, the only honest time it can happen.

Managing windows

SELECT * FROM pghf.list_blackouts();                        -- everything, past and future
SELECT * FROM pghf.list_blackouts(p_active_at => now());    -- active right now, either mode
SELECT * FROM pghf.list_blackouts(now(), p_mode => 'run');  -- active run-mode windows only
SELECT pghf.is_blacked_out('PGHF01-001');                   -- either mode, right now
SELECT pghf.is_blacked_out('PGHF01-001', 'alerting');       -- that mode specifically

SELECT pghf.update_blackout(42, p_ends_at => now());        -- end early, keeping the row
SELECT pghf.delete_blackout(42);                            -- remove entirely

update_blackout() shifts a window or amends its reason; scope and mode are immutable (changing either is a delete + create). Deleting a window never touches the suppressions it already recorded — those are history. Only one-off absolute windows exist; for a recurring window ("every Sunday 02:00–04:00"), have the same cron or orchestrator that schedules your runs also call create_blackout().

Who can declare one: the three write functions (create/update/delete_blackout()) have EXECUTE revoked from PUBLIC, the same permission model as thresholds and events — grant them to whichever role owns your maintenance calendar. Reading (list_blackouts(), is_blacked_out(), or the pghf.blackouts table directly) is open to everyone. One difference from thresholds and events: declaring a blackout works even under an inactivated namespace or category, since "stop alerting on these checks" is exactly what someone decommissioning a scope needs to say.

The full parameter-by-parameter reference lives in the Reference Guide.

Continue to Minimum Report Intervals.