Skip to content

Percentage-of-a-Ceiling Checks

A raw count is often meaningless on its own — eight replication slots is fine on a server configured to allow twenty, and alarming on one configured to allow ten. When a check's number only means something relative to a ceiling, compute the percentage inside the check itself, rather than registering the raw count with no direction at all (a count nobody can set a sensible threshold against just sits there permanently marked as "not evaluable").

The clean case — a ceiling that's a server setting

The built-in check for replication slot count against the server's maximum allowed is the simplest real example — its "value" is the percentage, not the raw count:

CREATE OR REPLACE FUNCTION pghf.chk_c09_003(p_run_id uuid)
RETURNS pghf.check_result
LANGUAGE plpgsql
AS $$
DECLARE
    v_count int;
    v_max   int;
    v_pct   numeric;
BEGIN
    SELECT count(*) INTO v_count FROM pg_replication_slots;
    SELECT setting::int INTO v_max FROM pg_settings WHERE name = 'max_replication_slots';
    v_pct := round((v_count::numeric * 100) / GREATEST(v_max, 1), 2);
    RETURN ROW('COLLECTED', jsonb_build_object(
        'value', v_pct,
        'slot_count', v_count,
        'max_replication_slots', v_max
    ), NULL)::pghf.check_result;
END;
$$;

GREATEST(v_max, 1) guards against dividing by zero if that setting were ever misconfigured; the raw count and the ceiling both stay in the collected data as context, so nothing is lost by leading with the percentage. Registered with a numeric result type, a percent unit, and "lower is better" direction — then thresholded exactly like any other numeric check.

When the ceiling can be undefined — skip, don't fabricate a number

The built-in check for a replication slot's worst retained data against its configured limit computes the same shape of percentage, but that particular server setting is commonly left switched off (unlimited) out of the box. Dividing by "no limit" would either error or produce a technically-correct-but-meaningless zero percent, silently reporting "healthy" when there's actually nothing being measured at all. So the check reports itself as skipped instead, with a note explaining why:

SELECT setting::int INTO v_keep_mb FROM pg_settings WHERE name = 'max_slot_wal_keep_size';
IF v_keep_mb IS NULL THEN
    RETURN ROW('SKIPPED', NULL, 'max_slot_wal_keep_size requires PostgreSQL 13+; not available on this server.')::pghf.check_result;
END IF;
IF v_keep_mb = -1 THEN
    RETURN ROW('SKIPPED', NULL, 'max_slot_wal_keep_size is unlimited (-1) — no finite ceiling to compute a retained-WAL percentage against. Monitor per-slot retained_bytes directly, or set max_slot_wal_keep_size to enable this check.')::pghf.check_result;
END IF;

Same principle as any other data-prerequisite gate — a check with nothing meaningful to measure reports itself as skipped, it doesn't guess.

A ceiling that isn't a server setting — compute it too

The denominator doesn't have to come from a server setting. The built-in check for superuser login roles as a percentage of all login-capable roles — a scale-independent security-posture signal a raw count can't give you — computes both sides from the same catalog query:

SELECT count(*), jsonb_agg(rolname) FILTER (WHERE rolsuper) INTO v_total_login, v_roles
  FROM pg_roles WHERE rolcanlogin = true;
SELECT count(*) INTO v_count FROM pg_roles WHERE rolsuper = true AND rolcanlogin = true;
v_pct := round((v_count::numeric * 100) / GREATEST(v_total_login, 1), 2);

When no ceiling exists at all — don't force it

Not every count has a natural denominator. The built-in check on the server's maximum connections setting stayed an absolute threshold, because there's no "maximum for the maximum" to divide by — forcing a percentage against an arbitrary made-up reference number would just be the same absolute threshold wearing a percent sign, not a real ratio. If your own check doesn't have a natural ceiling, register it with a numeric direction and an absolute threshold instead — that's a legitimate, common case, not a fallback to apologise for.

Continue to Writing a Custom Evaluator.