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, notREFRESH MATERIALIZED VIEW, which is a full recompute every time - retention by
DROP TABLEon an expired partition — O(1), no vacuum storm, unlikeDELETE 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
@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.
Rows per INSERT, bounded by Postgres's 65,535 bind-parameter limit.
The events table name, for hand-written queries and migrations.