Survey — object-storage stratum

A database that lives on the bucket.

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.

RAM per connection
310 KiB
vs Postgres 18
~27× less
LATERAL JOIN
462× faster
SQL fragments pass
863/975
psql — basin
$ psql 'postgres://alice@localhost:5433/alice' psql (18.0, server basin/0.1.9) Type "help" for help. alice=> CREATE TABLE events ( id bigserial PRIMARY KEY, kind text NOT NULL, at timestamptz DEFAULT now() ); CREATE TABLE alice=> SELECT kind, count(*) FROM events GROUP BY kind ORDER BY 2 DESC; kind | count -----------+------- pageview | 41822 signup | 1190 (2 rows) -- scanned from Vortex on S3

ordinary pgwire — no driver changes

Ships today
  • pgwire v3
  • Row-Level Security
  • Vector search (HNSW)
  • Iceberg catalog
  • REST + Auth
  • Wasm functions
  • HTAP hot tier
Architecture · the shape

Isolation is the storage layout, not a policy.

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.

01 / prefix

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.

02 / columnar

Vortex on object storage

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.

03 / one binary

One process, whole surface

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.

Code · psql session

The same SQL your app already speaks.

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.

What you get for free

Schema migrations, connection pools and per-row scoping collapse into the basin itself. The isolation isn't an extension — it's the storage shape.

  • Project bound at connection accept — no per-query auth
  • No row-level security policy boilerplate
  • Same EXPLAIN, same pg_stat_statements
  • Migrations: sqlx, flyway, prisma
project_isolation.sql psql
-- Two projects, one engine, one bucket. -- The prefix is the boundary. alice=> SELECT count(*) FROM events; count ------- 43012 bob=> SELECT count(*) FROM events; count ------- 0 -- Same table name. Different prefix. -- No policy was written to make this true. bob=> ALTER TABLE events ENABLE ROW LEVEL SECURITY; ALTER TABLE
pgwire v3 · server basin/0.1.9
What we do · what we don't

Postgres-compatible, with edges.

Postgres-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.

supported your driver, unchanged
  • pgwire v3 — simple + extended query, TLS, COPY, prepared statements, binary params
  • SQL surface — CREATE/INSERT/UPDATE/DELETE, joins, ORDER BY, LIMIT, ALTER TABLE, GENERATED columns, CHECK / PRIMARY KEY / FOREIGN KEY (single-shard)
  • Row-Level Security — ENABLE ROW LEVEL SECURITY + CREATE POLICY, plan-layer enforcement
  • Native vector search — vector(N) + HNSW, pgvector-compatible operators
  • Extension equivalents — pgcrypto, uuid-ossp, pg_trgm, basin-cron, basin-net, basin-geo (PostGIS subset), basin-cv (continuous aggregates)
  • LANGUAGE sql functions + CALL procedures, planning-time inlined
not supported by design
  • Replication protocol — wrong shape for object-store storage; logical decoding / CDC out of scope.
  • PL/pgSQL, PL/Python, PL/Perl — no alt-language stored procedures. Use LANGUAGE sql + change-event reactors instead.
  • Loadable .so extensions — no upstream extension binaries. The common ones ship as Basin-flavored crates with the same SQL semantics.
  • postgres_fdw / dblink — no foreign-PG query federation. Use basin-net for HTTP-shaped cross-system reads.
  • Types — INTERVAL, MONEY, XML, full geometric (LINESTRING / POLYGON) on the wire are not shipped; basin-geo covers the Point + box subset.
  • Cross-region 2PC — Spanner-class distributed transactions are out of scope.
Benchmarks · wins and losses

Measured, including where we lose.

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.

Basin (Vortex) vs Postgres 18 · 1M rows, LocalFS, single idle box
WorkloadBasinPostgres 18Verdict
RAM per held-open connection310 KiB8,257 KiB~27× less
Connections under 1,000-conn flood1,000 held100 held / 900 refusedstructural
LATERAL JOIN (correlated derived table)6.7 ms3,080 ms462× faster
Star join (events ⋈ users ⋈ categories)11.6 ms3,040 ms261× faster
Correlated subquery in SELECT p5049 ms5,510 ms113× faster
Range scan p50 (~1k rows)0.40 ms32 ms81× faster
Bulk INSERT 1,000,000 rows2,080 ms8,100 ms3.9× faster
Point query p50 (unindexed PK)0.50 ms0.002 msslower
Single-row UPDATE p501.24 ms0.012 msslower
COUNT(*) full table p5095 ms29 msslower
Deep top-K sort (ORDER BY … LIMIT 1000)161 ms53 msslower
On-disk bytes (1M rows, LocalFS)321 MB306 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.

Compare · where each one wins

When Basin is the wrong answer.

Every database here is good at something Basin isn't. The useful question is which shape your workload actually has.

PostgresAurora · RDS

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.

Neonserverless PG

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.

SupabaseBaaS in a box

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.

Nilemulti-tenant PG

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.

TursolibSQL · edge

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.

Quickstart · self-host

One binary. No object store required to start.

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.

Docker — under five minutes

No Rust toolchain. pgwire comes up and psql connects.

docker run --rm \ -p 5432:5432 \ -v basin-data:/var/basin \ --name basin \ basin-server

From source

Durable WAL and Vortex columnar files under your data dir; in-memory catalog for fast iteration.

# pgwire on 127.0.0.1:5433 BASIN_DATA_DIR=/tmp/basin \ cargo run -p basin-server # then, from anywhere: psql postgres://localhost:5433

Pre-alpha, built in the open.

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.