Skip to main content

Handler: retention.downsample

Keeps recent raw rows, aggregates older rows into a coarser rollup table, then deletes the raw rows the rollup now represents. Classic time-series pattern — useful for gpu_metrics, future pipeline_metrics, any high-frequency append-only table where you want long history but not long-at-full-fidelity history.

Row configuration

downsample_rule JSONB shape

Allowed aggregation functions: avg, min, max, sum, count. Additional functions require extending the whitelist in services/integrations/handlers/retention_downsample.py. Allowed intervals: a positive integer and a unit from second/minute/hour/day/week/month/year (singular or plural). E.g. "30 minutes", "1 day".

Precondition: rollup table must exist

The handler does not auto-create the rollup table. Every rollup schema decision (indexes, PK on bucket_start, column types) is a deliberate choice the operator should make. Create the table explicitly before enabling the policy:
Column names must match the as aliases in aggregations. The handler’s INSERT assumes bucket_start is the unique constraint for ON CONFLICT DO NOTHING — so overlapping invocations don’t double-insert.

Operator runbook

Enabling gpu_metrics (seeded, disabled by default)

How the handler works

  1. Count raw rows older than keep_raw_days. If zero, short-circuit.
  2. Insert aggregated buckets into rollup_table using date_trunc('hour', age_column) as the bucket key. ON CONFLICT (bucket_start) DO NOTHING makes overlapping runs safe.
  3. Delete raw rows older than keep_raw_days.
Failure between steps 2 and 3 is rare but possible (DB hiccup, manual kill). If it happens, the rollup table already has the bucket from step 2; re-running the policy will ON CONFLICT DO NOTHING on the existing bucket and proceed to step 3’s delete. Idempotent.
  • Framework overview: Integrations
  • Sibling handler: retention.ttl_prune
  • GH-110 (retention framework issue)