Straum
Straum keeps aggregates of PostgreSQL tables in memory and answers asks about them over HTTP. Counts and sums come back in microseconds.
A dashboard runs the same GROUP BY over the same tables all day, and
PostgreSQL reads the rows again each time. Straum reads them once,
keeps each group as a cell in memory, and follows every commit. You
declare what to keep as a view, in a small subset of PostgreSQL’s
CREATE VIEW, here over an events table and the accounts it
belongs to:
CREATE VIEW usage WITH (buckets = '1 hour, 1 day, 7 days') AS
SELECT date_bin('1 hour', e.at, timestamptz 'epoch') AS bucket,
e.kind AS kind,
e.account_id AS account_id,
a.region AS region,
count(*) AS events,
sum(e.amount) AS amount,
avg(e.amount) AS avg_amount
FROM public.events AS e
LEFT OUTER JOIN public.accounts AS a ON a.id = e.account_id
GROUP BY 1, 2, 3, 4;
An ask is a SELECT over that view, with every value as a parameter.
This one asks for purchases by region on one day:
curl --json '{
"query": "SELECT region, events, amount FROM usage WHERE kind = $1 AND bucket >= $2 AND bucket < $3 GROUP BY region",
"parameters": ["purchase", "2026-11-13T00:00:00Z", "2026-11-14T00:00:00Z"]
}' http://localhost:8080/ask
{
"as_of": "2026-11-14T09:12:40.118204573Z",
"rows": [
{"region": "eu", "events": 412, "amount": 18240.50},
{"region": "us", "events": 655, "amount": 30117.25}
]
}
Every commit made before as_of is in the answer, and an ask never
touches PostgreSQL. In the benchmark counts and sums
answer in 166 to 413 µs where PostgreSQL takes 22.3 to 2,376.2 ms, and
a median fare in 32 ms where it takes 6.6 s, from 113 MiB of memory.
The target is under 1 ms for every ask. The two percentile asks over
every trip miss it.
A table owner runs the SQL straum plan prints, once. straum run
loads the tables and follows them. Apps ask over HTTP.
What it is good for
A question asked often, about tables that keep changing, whose answer is counts, sums, averages, minima or maxima by a few keys over time:
- Dashboard tiles and charts: events per hour, amounts by region, the
same
GROUP BYon every page load. - Numbers an app shows its own users, such as usage this month or orders today. Make the tenant a key and each ask filters on it.
- A summary you maintain by hand today: a materialized view refreshed on a schedule, or a rollup table kept by triggers. Straum follows inserts, updates and deletes itself, and every answer is exact.
What it is not
Straum is not a SQL engine, a replica, a cache or a durable store. It answers only what its spec declares, so it has no ad hoc queries, no rows and no sketch. It keeps nothing on disk: every start loads every table again, and a crash leaves no slot behind holding WAL. PostgreSQL stays the source of truth.
Straum supports PostgreSQL 17 and newer as shipped, with no extension
and no superuser, so managed services work. Where wal_level = logical
can’t be had, --wal physical reads the WAL replica writes
(setup). The name is Nynorsk
for a stream or current: Straum’s input is PostgreSQL’s replication
stream.
Warning
Straum is not stable yet. The spec, the asks and the flags may change.
The quick start runs Straum in Docker in four steps, on a month of New York taxi trips. After it, a spec and an ask are the two things you write.