A Postgres-compatible database for heavy, multi-tenant workloads
We started with a question.
What would Postgres look like _if we designed it today?_
Our answer…
VACUUM xid wraparound process per connection PgBouncer pg_repack pageservers Patroni pg_dump for staging copies VM per tenant
A Postgres-compatible database for heavy, multi-tenant workloads.
One binary that speaks the PostgreSQL protocol, on an entirely new storage engine running on local disk or S3. Thousands of tenants per process, each one metered and capped. Raft-replicated, with read-your-writes from any replica. Instant database branching. Open source under Apache 2.0.
Multi-tenancy
Thousands of tenants per process. Each one resource-capped.
Egres runs on its own coroutine system: every query is a fiber, scheduled across the process's threads by Egres — not by the OS. Because the scheduler decides who runs next, it counts the CPU ticks each tenant uses every second — along with memory and disk IOPS — and parks a tenant the moment it spends its allowance, while everyone else on the same threads keeps going.
No VM, container or sidecar per tenant. One multi-threaded process, thousands of tenants, hard limits.
The scheduler and resource limits
Old school: a VM per tenant
vm kernel db process
vm kernel db process
vm kernel db process
The database can't tell tenants apart, so isolation is outsourced to a hypervisor. Every tenant pays a kernel, a memory floor and a boot time — and the fix for a noisy neighbour is to over-provision.
Egres: a resource cap per tenant
one egres process · 8 threads · thousands of tenants cpu mem iops
The database knows which tenant every query belongs to, so it enforces the limits itself: CPU ticks, memory and disk IOPS, measured per query and capped per tenant. No hypervisor in the loop.
Storage engine
Meet ZDB: an LSM forest, not a heap.
Every table and every index is its own LSM tree with its own SSTables. A database is a forest of them. Nothing is interleaved on disk: a scan touches only its table's files, one tenant's trees never overlap another's, compaction runs per tree — on whichever node has the headroom — and a branch is simply a new forest that points at the parent's files.
Keys are 64-bit row IDs, so secondary indexes are roaring bitmaps over those IDs — compact, and fast to intersect.
Inside ZDB at 600M rows: 46% less on disk, 23% fewer bytes written — the numbers

Storage
Local disk or remote S3. Same binary, one flag.
Run Egres on one box with NVMe, or point a fleet of stateless compute nodes at a bucket. SSTables are immutable files, so they live equally well on a local disk or on object storage — no separate product, no different build.
Operationally, that is the whole story: one binary, no sidecars, no separate metadata or coordination service to run. The same `egres` serves a laptop, a single server and a fleet.
same binary · one flag · auto-switching
`$ egres --store dir-path=./data`
egres
NVMe · /data
one process · sql, storage engine and cache on one box · the fastest path for a single node
Replication
Raft-replicated. Read your writes from any replica.
Every write goes through Raft. A transaction is on a quorum before the client hears back, leader election and failover are built in, and there is no asynchronous replica lag to reason about.
Reads can go to any replica, with read-your-writes: a session carries the commit timestamp of its last transaction, and a follower answers only once it has applied past it. Scale reads across replicas without ever seeing a state older than what you just wrote.
And physical replication, reimagined for the LSM tree: compactions ride the same log, run once on whichever replica has spare CPU, and every node lands on the identical forest without recomputing anything.

Branching
Branch a database — or a table — instantly.
Branch a whole database, or just one table, the way you branch a repo. Every SSTable in the parent's LSM trees is re-referenced by the branch, and new writes land at the top, in the branch's own memtable and L0. There is no copy-on-write: a parent file is never copied, not even on the branch's first write. The LSM tree simply layers new updates over the shared files, compacting as necessary. Preview a migration, reproduce a bug against production data, give every pull request its own database — and drop it when you're done.

Measured
600 million rows, one node, against Postgres 18.
600 million rows — 579 GiB of raw data — in one table with four single-column indexes, a 64 GiB memory cap, same box, same queries.
−46%
less on disk
376 GiB vs 693 GiB on disk, for 579 GiB of raw data
−23%
fewer bytes written
2.3 TiB vs 3.0 TiB · write amp 4.0× vs 5.2×
2.8×
faster queries
mean latency, geomean of three queries: 1.3 ms vs 3.5 ms
100×
faster two-index query
p95 at 600M rows: 3.9 ms vs 490 ms · bitmap indexes compose
Queries run with batched execution — operators work on batches of rows, not one tuple at a time — and secondary indexes are roaring bitmaps, so multi-index queries stay fast as the table outgrows memory. Postgres still loads 15% faster; the full results say where each side wins.
See the full benchmark results
Operations
Eliminate complexity. Simplify operations.
Half of operating Postgres at scale is operating the machinery around it. Egres removes that machinery rather than managing it. Things you will never run again:
VACUUM
autovacuum, dead tuples, bloat
An LSM tree has no in-place updates to clean up. Compaction drops superseded versions as it merges — there is nothing to vacuum.
transaction ID wraparound
32-bit xids, age(datfrozenxid), the 3 a.m. page
Transaction IDs are 64-bit and never recycled. There is no horizon to freeze and no counter that can run out.
process per connection
fork(), work_mem × backends, max_connections
Every connection is a fiber on a shared thread pool. Thousands of connections cost thousands of small stacks, not thousands of processes.
PgBouncer
and every other connection pooler tier
Connections are cheap and already multiplexed by the scheduler, so there is no pooler to deploy, tune, or debug prepared statements through.
pg_repack
bloat, index rebuilds, REINDEX CONCURRENTLY
Tables and indexes are their own LSM trees. Compaction keeps them dense continuously; nothing accumulates that needs a repack.
pageservers
a separate storage tier to run and scale
Storage is SSTables on a local disk or in a bucket. There is no second service between the database and its data.
Patroni
repmgr, etcd, failover scripts, split-brain
Leader election and failover are Raft, inside the binary. There is no external consensus store to run and no agent deciding who is primary.
pg_dump for staging copies
dump, restore, wait, repeat for every environment
A staging copy is a branch: instant, shares every file with production, and goes away when you drop it.
VM per tenant
hypervisor isolation, memory floors, boot times
Tenants are isolated by the scheduler and metered per query. Thousands fit in one process.
Next
Read how it works, or run it.
The design pages follow the launch post section by section: tenancy, storage, replication, garbage collection, indexes, branching and transactions. The quickstart builds the binary, starts a server and branches a database in about five minutes.