Architecture Overview¶
This chapter is a map of the framework's moving parts — what each table and function is for, and how they connect. You don't need to memorise it before using the framework day to day, but it's a useful chapter to come back to when something isn't behaving the way you expect.
The catalog¶
This is a quick tour — the Catalog book is the full, dedicated treatment of everything in this section.
- Namespaces and categories are the top two levels of organisation. pg_health_framework seeds one namespace,
PGHF("PostgreSQL Health Framework"), with 15 categories underneath it — for example category01, "Connection & Availability." Namespaces and categories are maintained only through their own registration functions, each of which can also activate or inactivate the row it manages — see Inactivating a Namespace or Category. - Checks are the repository itself — what a check is: which function backs it, its namespace/category/run number, the metadata describing its result (see Metadata Columns Explained), and a required explanation of why it matters. Every check has two identifiers: a permanent, invisible internal ID that every other table actually points to, and the human-readable check ID (
PGHF01-006-style) computed from its namespace, category, and run number — renumbering a check recomputes its check ID automatically. Checks are maintained only through their own registration functions — direct table edits are blocked. pghf.seed_data()is the one function that writes this project's own catalog data: thePGHFnamespace, its 15 categories, all 175 built-in checks, the ready-made suites, and their default thresholds. It runs once automatically at install time, and is safe to run again yourself at any point to reset the built-in catalog back to what your installed version ships — see Resetting to Defaults.
Suites and runs¶
- Suites (internally called "check runs") are named, ordered lists of checks — the seeded cadence suites (
seeded-5m,seeded-daily, …), or one you create yourself. A suite doesn't care which namespace or category a check came from, so mixing your own checks in with built-in ones in a single suite is normal, not a special case. - Runs are one row per actual execution of a suite, together with the raw data each check collected and any extra parameters that execution was given (for example, which PostgreSQL version to check upgrade-readiness against).
- The collection engine is the single procedure that runs every member of a suite, in order, calling each one's backing function and recording what came back. Every check is committed to the database as soon as it finishes, one at a time, so an interrupted run leaves you with a complete, real result for everything collected up to that point — not a half-written mess. (A suite can also opt in to running its checks in parallel through the pg_relay extension's workers instead of sequentially inline — see Parallel Execution via pg_relay.)
Two ways a check runs: polled and on-event¶
Everything just described is the polled model — a check joins a suite, and the collection engine invokes it as that suite's ordered walk reaches it, on whatever cadence a scheduler drives. That fits "count the instances of a condition right now," which is what most monitoring is.
Some things aren't like that: a specific event that happens once, and by the time it happens there's nothing left to measure — whatever triggers it (a background job finishing with a failed status, say) already knows how bad this is and which record it's about. An on-event check is built for exactly this: it's never a suite member at all, and instead of the collection engine reaching it on a walk, something else — almost always a trigger on some other table — invokes it directly, exactly once, the moment the event occurs, handing over the severity and the offending row's key directly rather than leaving anything to be measured. Two functions do this invoking: one committing (for a person or script running a check on demand), and one that never commits (safe to call from inside a trigger, which by definition is already inside someone else's transaction). Every on-event check's runs land in one shared, permanently reserved run container rather than a suite of their own, since nothing ever needs to "poll" it the way a real suite gets polled.
Past this point of entry, an on-event check is ordinary in shape — same backing function, same thresholds, same debounce, same events — but not in timing, and not always in how its severity is decided. A shipped, check-agnostic evaluator can simply echo the severity the trigger already supplied, rather than judging anything; a check can also ignore that and measure its own data instead, judged by a real threshold like a polled check would be — both are legitimate. Either way, the trigger-safe invocation function only collects: judging a result — even just echoing an already-known one into a real evaluation — commits internally the same way collecting normally does, and a trigger can never allow that. Evaluating what a trigger collects is therefore a separate, second step — either a manual one-off call, or a short-interval scheduled sweep for anything collected automatically. Skip that second step, and an on-event check keeps recording real data while nothing ever judges it. Its current-state row is also hidden from the framework's default reporting views by default, alongside ordinary polled checks' — "this specific thing last happened at time T" means something different from "this condition is still true right now," and an opt-in flag shows it when wanted. See On-Event Checks for the full worked example, including a real trigger and the evaluation step.
Thresholds and evaluation¶
- Thresholds are how you tell the framework what a good or bad result looks like — a check-level default that applies everywhere, optionally overridden for a specific suite. Thresholds are maintained only through their own registration function.
- Evaluations are one row per check per run — the severity a check's collected data was actually judged to be. Written only by the evaluation engine described next.
- The evaluation engine is the single procedure that judges an already-collected run's data against the thresholds configured for it. It is entirely separate from collection: it never re-runs a check, only reads what an earlier collection pass already recorded.
- A convenience function runs collection and evaluation together in one call, for the common case where you always want both.
Reading results back out¶
- One function returns a run's results as a single block of structured data, meant for another piece of software to consume (a notification pipeline, a webhook, a dashboard).
- A second function returns the same data as an ordinary, human-readable result set — worst severity first — meant for a database administrator reading output directly at the command line.
- A separate function reports the raw counts that let you delete old history in bulk, once you decide you no longer need it.
What a check actually is¶
A check is nothing more than an ordinary PostgreSQL function with one fixed shape: it takes the ID of the current run, and returns a specific, framework-defined structure recording what it observed. The collection engine has no built-in knowledge of any individual check at all — it looks up which function backs a given check and calls it. This is the entire mechanism that makes the framework extensible: register a function with the right shape, and it runs, with nothing else needing to change. See Creating Your Own Checks and Thresholds in the Catalog book and the dedicated Adding Your Own Checks book for the full walkthrough.
A check that needs something specific to be true before it can collect anything — for example, a check that only makes sense on a server that's part of a Spock replication cluster — tests for that itself, inside its own function body, and reports that it was skipped if the condition isn't met. The collection engine itself has no special-case logic for any particular kind of check; every data prerequisite lives inside the check that needs it.
Continue to Running Checks End to End.