Projects are prefixes
The connection URL identifies a project, resolved once at connection accept. After that it's ordinary SQL — no per-query auth, no row-scoping boilerplate. Adding a tenant writes no new heap pages and books no connection slot.
Basin is a bucket-native, multi-tenant Postgres
alternative. Projects are S3 prefixes, not databases — so operator
cost tracks bytes actually stored, not databases provisioned. One binary,
pgwire on the front, Vortex-compressed columnar files on any S3-compatible
bucket on the back. Your Postgres drivers just work.
ordinary pgwire — no driver changes
A new project doesn't fork a process, doesn't book a VM, and doesn't draw a monthly minimum. It's a bucket prefix the engine refuses to read across. That one decision is where the RAM, the cost curve, and the tenant ceiling all come from.
The connection URL identifies a project, resolved once at connection accept. After that it's ordinary SQL — no per-query auth, no row-scoping boilerplate. Adding a tenant writes no new heap pages and books no connection slot.
Data lands as Vortex-compressed columnar files (Parquet opt-in per table)
on any S3-compatible bucket, with a file-backed WAL in front. Bytes at rest
compound: 102× smaller than Postgres on real S3 at 100k rows.
pgwire, REST, auth, RLS, vector search, cron and Wasm functions are one
binary. 310 KiB of RAM per held-open connection means a
connection-heavy front end stops being the thing that sizes your box.
Basin speaks pgwire and parses with libpg_query — the actual
PostgreSQL parser, vendored. Your ORM doesn't know the difference because,
at parse time, there isn't one.
Schema migrations, connection pools and per-row scoping collapse into the basin itself. The isolation isn't an extension — it's the storage shape.
EXPLAIN, same pg_stat_statementssqlx, flyway, prismaPostgres-compatible, not Postgres. 863 of 975 SQL fragments pass on the default configuration (88.5%). Every "no" below has a written rationale and the trigger that would change our mind.
1M rows on LocalFS, no index on either side, default configuration — no non-default flags. Postgres is the right answer for microsecond point mutations, and the table says so.
| Workload | Basin | Postgres 18 | Verdict |
|---|---|---|---|
| RAM per held-open connection | 310 KiB | 8,257 KiB | ~27× less |
| Connections under 1,000-conn flood | 1,000 held | 100 held / 900 refused | structural |
| LATERAL JOIN (correlated derived table) | 6.7 ms | 3,080 ms | 462× faster |
| Star join (events ⋈ users ⋈ categories) | 11.6 ms | 3,040 ms | 261× faster |
| Correlated subquery in SELECT p50 | 49 ms | 5,510 ms | 113× faster |
| Range scan p50 (~1k rows) | 0.40 ms | 32 ms | 81× faster |
| Bulk INSERT 1,000,000 rows | 2,080 ms | 8,100 ms | 3.9× faster |
| Point query p50 (unindexed PK) | 0.50 ms | 0.002 ms | slower |
| Single-row UPDATE p50 | 1.24 ms | 0.012 ms | slower |
| COUNT(*) full table p50 | 95 ms | 29 ms | slower |
| Deep top-K sort (ORDER BY … LIMIT 1000) | 161 ms | 53 ms | slower |
| On-disk bytes (1M rows, LocalFS) | 321 MB | 306 MB | ~5% larger |
Read the losses as the shape, not the footnote. Basin trades microsecond point mutations for columnar scans and bytes-at-rest. The on-disk row is an honest flip on this card — Basin is still 1.9× smaller at 100k rows and 102× smaller on real S3, where compression compounds against block storage. Published numbers use the default configuration; the HTAP fast paths are always on.
Every database here is good at something Basin isn't. The useful question is which shape your workload actually has.
The right answer for single-project, high-frequency OLTP and anything needing microsecond point-mutation latency at 1M+ rows. Basin isn't trying to be Postgres on those shapes. Basin wins on many-isolated-projects, append-shaped data, bulk ingest, columnar analytical scans, and the RAM-per-connection economics for connection-heavy front ends.
Serverless Postgres with branching — terrific for single-DB workloads that want copy-on-write forks. Basin matches the branching story (Iceberg forks are zero-copy too) but stores on plain S3 rather than a managed page server, so per-project cost tracks bytes rather than a provisioned pool.
Postgres + Auth + Edge Functions + Storage + Realtime. Basin covers the
SQL + Auth + REST surface in one binary, with auth.uid() /
auth.role() / auth.jwt() working identically. The
difference is the data layer: Vortex/Parquet on S3 instead of a Postgres heap
on block storage. Edge Functions, Realtime and Storage are out of scope.
Same problem space, built on real PostgreSQL with per-tenant virtual databases — which buys real PG semantics, real OLTP, real JSONB, real extensions and PL/pgSQL, exactly where Basin still trails. If your workload is point-mutation-heavy and JSONB-heavy with under 1k tenants, Nile is probably the easier answer today. Basin's structural answer is substrate economics: cold or low-traffic tenants stay near-zero because cost is O(bytes-on-S3) with shared compute.
The right answer for edge-distributed apps with many tiny SQLite-class databases. Basin is for centralized apps that want Postgres SQL on cheap object storage with a wire protocol ORMs already speak.
Point Basin at a data directory and run. Local development needs no external bucket — the same binary that runs on your laptop is the one that runs on S3.
No Rust toolchain. pgwire comes up and psql connects.
Durable WAL and Vortex columnar files under your data dir; in-memory catalog for fast iteration.
Basin is being built in public — use it today to evaluate the cost shape, prototype multi-tenant patterns, or contribute. The benchmarks publish wins and losses, and the roadmap says what isn't done.