Pixelex.Store.Postgres (Pixelex v0.1.0)

Copy Markdown View Source

The real store: the host application's own Postgres, and nothing else.

No extensions

Not TimescaleDB, not hll, not Citus. Verified across the hosts Phoenix apps actually run on: Neon ships TimescaleDB's Apache edition only, so continuous aggregates — the whole reason to want it — are absent; Supabase deprecated TimescaleDB at PG 17 over the licence and does not list hll; Fly MPG's extension set is the stock PG distribution. A library that needs an extension is a library most people cannot install, so this is plain SQL:

  • monthly range partitions on occurred_at
  • rollups written with INSERT … ON CONFLICT DO UPDATE, not REFRESH MATERIALIZED VIEW, which is a full recompute every time
  • retention by DROP TABLE on an expired partition — O(1), no vacuum storm, unlike DELETE FROM … WHERE occurred_at < …

Bounds

Every write is one INSERT of a whole batch. Reads live in Pixelex.Query and are date-bounded by construction; the partition key means a bounded range touches only the partitions it overlaps, and the planner prunes the rest.

Idempotency

ON CONFLICT DO NOTHING against the (id, occurred_at) primary key. A replayed batch collides instead of duplicating, which is what makes the ingest buffer safe to retry.

Summary

Functions

Rows in a bounded window. The from/to bounds are not optional — an unbounded scan of this table is the one query that must never exist.

Rows per INSERT, bounded by Postgres's 65,535 bind-parameter limit.

The events table name, for hand-written queries and migrations.

Functions

events(site_id, from, to, opts \\ [])

@spec events(String.t(), DateTime.t(), DateTime.t(), keyword()) :: Ecto.Query.t()

Rows in a bounded window. The from/to bounds are not optional — an unbounded scan of this table is the one query that must never exist.

max_rows_per_statement()

Rows per INSERT, bounded by Postgres's 65,535 bind-parameter limit.

table()

The events table name, for hand-written queries and migrations.