ArgonDB: the database that is also a lakehouse — and speaks agent
This page describes a new database system, and it is also the argument for one: what ArgonDB is for, and why it is shaped the way it is. ArgonDB is designed to run the gamut from a Kubernetes cluster down to a single binary on a MacBook — one system across that whole distance, which is a claim this page spends some time on. The box and Compose shapes are what runs today; the Kubernetes shape is the reference architecture under validation.
This page is written for technical readers who are not database specialists. Every internals term gets explained the first time it appears — not just what it is, but why it matters — because ArgonDB's whole trick happens at a layer of Postgres most engineers never look at, and ten minutes spent there makes everything else on this page obvious. The tour: what ArgonDB is and where it sits, then its three components in the order that makes them make sense — stock Postgres, then Neon, then ArgonDB itself — then what the database does in practice, then the agent half, and finally how you would run it yourself, from a laptop to a cluster.
What ArgonDB is
ArgonDB is a Postgres-compatible operational database whose storage layer is the analytics layer. There is no CDC pipeline, no connector, no nightly export job. The same write that commits to Postgres becomes — within seconds, transactionally consistent, with full change history — an Apache Iceberg table any engine can read. And the whole system is built to be operated and queried by AI agents as a first-class client: budgeted responses, pinned-consistent multi-call sessions, and answers that disclose their own freshness, gaps, and limits.
Every phrase in that paragraph gets unpacked below, from the ground up.
The lineage ArgonDB sits on
Databases evolve in visible steps, and it helps to know which step this is. Four rungs, and the middle two are real products you can go and use today:
- Postgres. One machine, one engine, transactions. The reference operational database, and the thing every rung above still is.
- Disaggregated Postgres. Storage moves out of the database process into a shared service that keeps every version of every page. Computes become stateless and cheap; "the database as of last Tuesday" becomes a metadata entry rather than a restore. Neon is the open implementation, and Component 2 below is a tour of it.
- The lakebase. Operational Postgres and the analytics lakehouse under one managed roof — Databricks shipped one in February 2026. This is where the public chain currently ends, and it ends closed: managed only, and the link between the transactional side and the lake side is still a copying pipeline, with documented lag and no consistency promise a client can name.
- The postlake. What this is. The lake is not a copy of the database kept in step by a pipeline; it is a second materialization of the database's own log. One consistency contract covers both shapes, agents are first-class callers of the whole thing, every kind of read scales out without touching the transactional machine, and the system is self-hostable from a laptop up.
The distinction between the last two rungs is the entire project, so it is worth being concrete about it. In a lakebase the lake is downstream: something reads the database and writes the lake, and the two are therefore always a little bit out of agreement — on a cadence the vendor publishes, without a coordinate you can pin a second query to. In a postlake the lake is a shape of the database — same log, same clock, same commit boundaries — so "how far behind is the lake" has an exact answer at every instant, and a reader can pick one instant and see both shapes agree.
What ArgonDB deliberately is not
Four things it is not, in case one of them is what you came for:
- Not a CDC tool. ArgonDB does not attach to a Postgres server you already run. No replication slots, no source connectors — the lake is built from ArgonDB's own storage, so there is nothing to point at an external database. If your goal is "get my existing production Postgres into a lake," the CDC products are the right answer and this is not one of them.
- Not an orchestrator. No scheduler, no bespoke deployment runtime, no custom control plane to learn. One binary, or docker compose, today; an ordinary Kubernetes workload in the cluster shape under validation.
- Not a new SQL dialect. PostgreSQL is the only SQL ArgonDB speaks. Foreign engines read the lake tables directly, in whatever dialect they like, but that is their SQL, not ours.
- Not a migration product. Moving an existing database in works the
ordinary way —
pg_dumpand restore. ArgonDB is where the data lands, not the moving van that carries it.
The components: Postgres, then Neon, then ArgonDB
ArgonDB is three systems stacked, and the only sane way to describe it is bottom-up — because each layer exists to do something the layer under it could not.
- Postgres does the database work: transactions, SQL, your data. ArgonDB does not change it.
- Neon takes storage out of the Postgres machine and turns it into a service that never forgets. ArgonDB inherits all of it.
- ArgonDB adds a second reader of that storage — one that materializes the same data in the shape analytics needs — plus an agent interface over both halves.
If words like LSN, WAL, watermark or Iceberg mean nothing to you, good: each component below starts from zero and carries its own diagram.
Component 1 — Postgres: what happens when you change one row
Postgres is the world's reference open-source database, and a casual user never has cause to look inside it. But ArgonDB's entire trick happens in machinery Postgres already has, so ten minutes here makes the rest of the page obvious. Four ideas: pages, the write-ahead log, row versions, and checkpoints.
Pages and the buffer pool: the shape of data at rest
A table is rows; but disks and memory don't move rows around, they
move fixed-size blocks. Postgres stores every table as a sequence of
pages — 8 KB blocks, each holding as many rows as fit. When a query
needs a row, Postgres loads that row's whole page into a shared area of
RAM called the buffer pool (shared_buffers) and works on it there.
Writes work the same way: an UPDATE changes the copy of the page in
RAM, not the disk.
Why it matters: RAM is orders of magnitude faster than storage, so a database that touched the disk for every row would crawl. The buffer pool is why databases are fast. But it creates the central problem of database engineering: the freshest state of your data lives in volatile memory. Pull the plug, and every page that was changed in RAM but not yet written back is gone. Something has to make writes durable before the slow work of writing pages back happens. That something is the write-ahead log.
The write-ahead log: the diary every database keeps
The write-ahead log (WAL) is an append-only file — a diary. Before
Postgres changes anything, it first appends a small record to the WAL
describing the change ("in this page, this row's balance becomes 42"),
in exact order. Only then does it touch the page in RAM. When your
transaction commits — the moment the database promises "this
happened" — Postgres forces the WAL records to disk (an fsync) and
only then tells you COMMIT succeeded. The changed pages themselves can
be written back to disk whenever convenient — minutes later is fine.
Why write the diary first, "ahead" of the data? Because appending to one sequential file is the fastest thing a disk can do, and it makes a crash survivable: after a power cut, Postgres reads the diary and replays any changes whose pages never made it to disk. Nothing committed is ever lost. And the same diary is the natural transport for replication — ship the WAL to a second machine, replay it there, and you have an exact copy of the database, kept current by the byte.
The property that matters for everything below: the WAL is a complete, ordered record of everything that ever happened to the database. If you have the WAL, you can reconstruct the database — at any moment in its history. Every position in the log has an address, the LSN (log sequence number — literally a byte offset into the diary), and an LSN is therefore a clock.
Keep that idea; ArgonDB uses LSNs as its universal clock for consistency, time travel, and provenance.
Row versions, and the cleanup that trails them
One more piece of Postgres that surprises people: an UPDATE does not
overwrite a row. It writes a new version of it and leaves the old
one in place, because transactions that started earlier are still
entitled to see the world as it was when they began. That is MVCC,
multi-version concurrency control, and it is why readers never block
writers in Postgres.
The bill arrives later. Those dead versions have to be reclaimed, which is autovacuum's job — and vacuum may only remove a version that no open transaction could still need. So a single long-running transaction holds the cleanup horizon for the whole database: while it lives, dead rows pile up behind it. Remember this one. It is the reason ArgonDB's pins are built the way they are, and it is the single most common way a well-run Postgres gets into trouble.
Checkpoints: why the diary can normally be thrown away
If the WAL only ever grew, replaying it after a crash would take hours and the disk would fill. So periodically Postgres takes a checkpoint: it writes all modified pages from the buffer pool down to disk, then records "everything up to LSN X is now safely in the data files." Recovery only needs to replay the diary since the last checkpoint, and WAL older than that can be recycled.
Here is all of that in one picture — the processes, the shared memory they work in, and the two very different files on disk:
Note what the checkpoint implies: in classic Postgres the WAL is scaffolding. It is essential for the seconds between commit and checkpoint, and then it is thrown away. Hold that thought: the two components that follow are both built on refusing to throw it away.
Where a single Postgres runs out of room
Everything so far is one machine. That machine is remarkably capable, and for most applications the story ends there. But when it doesn't, the architecture — not the code quality, the architecture — sets hard limits, and every one of them traces back to the picture above.
- One writer, and it is a single point of failure. Exactly one machine can accept a write. Scaling writes means a bigger machine, full stop. And when the primary dies, writes stop until something promotes a replica — which is orchestration you own, a decision someone has to make, and (with the default asynchronous replication) a window of acknowledged-but-unshipped commits you may lose.
- A read replica is a whole second database. It is not a cache: it is another Postgres, replaying the same WAL into its own full copy of the data, on its own disk, with its own buffer pool to warm up. Three replicas means four copies of every byte you own — and four machines to patch, monitor and pay for.
- Replicas cost time before they cost money. Creating one starts with a base backup of the entire database, so the cost of adding read capacity is proportional to how much data you have. On a large database that is hours of copying before the new replica serves a single query.
- Replicas are behind, and the lag is not yours to control. By default replication is asynchronous, so a replica is seconds — or under load, much more — behind the primary. Any read that must see your own just-committed write has to go to the primary, which quietly routes your most latency-sensitive traffic to the one machine you were trying to protect. Making replication synchronous fixes the lag by making every commit wait for the network.
- Analytics is the worst possible tenant. A query that scans a year of history evicts the working set from the buffer pool that OLTP traffic depends on. Move it to a replica and you trade one problem for another: a long-running query there either delays WAL replay or gets cancelled by it, and the settings that arbitrate that fight are a permanent tuning argument.
- Row storage is the wrong shape for those questions anyway. "The average order value per month across 40 million orders" touches two columns of every row — but pages hold whole rows, so Postgres reads all of every row to answer it.
- Storage is welded to compute. The data lives on the machine's disk, so growing storage means resizing that machine, and point-in-time recovery needs a separate archiving system bolted on beside it.
Read those together and a pattern emerges: nearly every one is a consequence of the database owning its own disk. That is precisely the assumption the next component removes.
Component 2 — Neon: taking storage out of the database
Neon is an open-source (Apache 2.0) reimplementation of Postgres storage. It became well known as a serverless-Postgres cloud, was acquired by Databricks in 2025, and its architecture is the foundation ArgonDB forked and now carries forward.
The core idea in one sentence: keep the diary forever, and let a
storage service — not the Postgres process — own it. Postgres stops
writing pages to a local disk. Instead it streams its WAL out over the
network, and when it needs a page that isn't in its buffer pool it
asks for it: "give me page N as of LSN L." That request has a name
you'll see everywhere below — GetPage@LSN.
The pieces, one at a time
- The compute — stock Postgres v17 with one extension loaded
(
neon). Two halves matter: a component that ships the WAL out to storage as it is generated, and a storage manager that fetches pages on demand instead of reading a local file. The compute is effectively stateless — its local disk is only a cache. Kill it, resize it, move it, run twenty of them; none of them holds your data. - compute_ctl — the agent that sits with each compute and configures, starts and supervises that Postgres. Unglamorous and load-bearing: it is what makes "start a database at LSN X" a single API call rather than a runbook.
- Safekeepers — a small cluster (three here) whose only job is making the WAL durable the instant it is written. A commit is acknowledged when a quorum — a majority, two of three — has the record on disk, using a consensus protocol (Paxos). Why a vote instead of one very reliable machine? Because any single machine can die mid-write; a majority of three survives any one failure with no data loss and no pause.
- The pageserver — consumes the WAL from the safekeepers and
re-indexes it: the diary, reorganized so that all changes to any
given page can be found by (page, LSN range), alongside periodic full
page images. The layered result lives on object storage — the
S3-style service that stores files cheaply, redundantly and
effectively without limit. From those layers the pageserver answers
GetPage@LSNfor any page at any point in history. It is the buffer pool's disk half plus the checkpoint machinery, relocated into a service, with one crucial upgrade: it never forgets. Pages are versioned by LSN instead of overwritten in place. - The storage broker — a small pub/sub service through which the storage components learn who holds which timeline and up to what LSN. It is how the pieces find each other without a central lock.
- The storage controller — the control plane for storage: it decides which pageserver hosts which timeline, migrates them, and fails them over when a pageserver dies. It keeps its own small Postgres for that bookkeeping.
What the separation buys — and what it does not
Every limit in Component 1 that traced back to "the database owns its disk" simply dissolves:
- A read replica is no longer a copy. It is another stateless compute pointed at the same storage. No base backup, no second copy of your bytes, no hours of waiting — it starts in seconds.
- Branching is metadata. "The database as of yesterday 3pm" is not a copy; it is an entry saying "new timeline, rooted at LSN X." Instant, and nearly free until the branch diverges.
- Historical reads are intrinsic. A read-only compute can be started at any retained LSN — a real Postgres serving the database exactly as it was at that instant. No separate archiving system, because the history was never discarded. Remember this one: ArgonDB leans on it hard.
- Compute is disposable. Restart it, resize it, shut it down when idle and cold-start it on demand.
- Storage grows on its own. It is object storage; it has no size to resize.
What Neon does not fix is just as important. There is still exactly one read-write compute per timeline, so the single-writer ceiling stands. And the pageserver stores pages — row-oriented, OLTP-shaped — and serves them back to Postgres. The shape problem is untouched: analytics is still someone else's problem, still solved by copying data out to a different system. That is where ArgonDB picks up.
What ArgonDB uses: all of it
A clarification, because it is usually assumed otherwise: ArgonDB does
not use "Neon's storage engine." It uses all of Neon — the whole
open-source system, running as designed. Every component named above is
in this deployment right now: the safekeeper quorum, the pageserver, the
storage broker, the storage controller with its own Postgres,
compute_ctl, the neon extension inside the compute, and the local
orchestration tooling the test suites drive.
Exactly one open-source component was removed: the connection proxy, which does multi-tenant connection pooling and TLS routing for Neon's cloud. A self-hosted database has no use for it. (Neon's commercial cloud control plane was never open source, so it was never on the table — the open repository is the storage system plus the storage control plane, and that is what we run.)
Since the Databricks acquisition the public Neon repository has slowed to a handful of commits a year (6 since October 2025, most recently 2026-08-31, checked 2026-09-01). ArgonDB forked it by one-time snapshot, keeps its own additions strictly separated from the inherited code, and maintains the inherited tree itself — including its own security and correctness watch. In practice it is an actively developed open continuation of this architecture.
Component 3 — ArgonDB: a second reader of the same log
Here is the situation ArgonDB starts from. Flowing through the safekeepers is a complete, ordered record of everything happening in the database. Sitting in the pageserver is every page, at every LSN, with the system catalogs — Postgres's own tables describing your tables' names, columns and types — versioned right alongside. One consumer of that stream, the pageserver's own layer store, materializes it as pages for Postgres.
ArgonDB adds a second consumer that materializes the very same stream as columnar tables for analytics. Same log, two shapes, one clock. Here is the whole system, all three layers at once:
What we kept, what we changed, what we added
The discipline that makes this maintainable is worth stating plainly,
because it is checkable. Everything in layer ② is inherited and runs
unmodified. Across the entire inherited tree, exactly eight files
carry an ARGON: marker, and only two of those change behaviour:
libs/postgres_ffi/src/walrecord.rsrecords where each WAL record's block data begins, so our decoder can find the tuple bytes. A four-line hook and an accessor.pageserver/client/src/page_service.rsstops a dropped pageserver connection from panicking the caller, so our tail reconnects instead of dying when the pageserver restarts.
The other six are build files: the proxy removal, and two test hooks that let the inherited storage tests run against local S3- and Azure-compatible endpoints. Everything else ArgonDB does lives in new code beside the inherited tree — which is what keeps upstream fixes cherry-pickable, and what lets us say "unchanged" and mean it.
What is new:
- argondb-tail — the heart. It consumes the WAL stream physically, at the storage layer: no logical replication, no replication slot, no decoding plugin, and no work at all added to the write path. Its decoder reconstructs fully typed rows from raw pages; its committer lands them in Iceberg as atomic multi-table commits aligned to Postgres commit LSNs; its delta ring serves the seconds between lake commits so reads are current to the instant. It also hosts the full-text index, the lake maintenance plane (compaction, statistics, snapshot expiry, orphan cleanup — no Spark jobs, ever), and the metrics surface.
- argondb-query — unified reads: any table, pinned at any LSN, served as Iceberg-at-watermark plus the ring's delta merged at read time.
- argondb-text — full-text search over the same tables at the same coordinate: BM25 ranking with structural scoping, so a query can ask only inside one named section across every document at once.
- argondb-mcp — the agent interface: fifteen tools mounted in every distribution, from schema orientation to search to time travel, all budget-bounded and provenance-stamped. It gets its own deep-dive section below.
- argondb-ctl — the operator CLI and supervisor: publish a table to the lake with one command, materialize, and — see below — hand out real Postgres computes at past instants.
- argondb-offload — a Postgres extension that lets an ordinary application connection hand a sweeping analytical query over to the columnar side and get the answer back as if Postgres had done the work. Opt-in, and it has its own section below.
- The lake — real Apache Iceberg in an object store behind a REST catalog. Not a proprietary format with an export path: the tables below are readable by DuckDB, Spark, Trino and Snowflake today.
- The disclosure layer — ArgonDB records what it cannot reconstruct (gap records), annotates digests that might under-count, discloses when a read is behind the live head, and durably notes every value the lake's type system cannot represent. A database that answers agents must never be confidently wrong.
The next two sections explain why the columnar half is worth building at all, and then why building it inside storage changes what it can promise.
Rows vs columns, and what a lakehouse is
Postgres pages store data row by row — perfect for transactional work ("fetch order 4711, update its status"), because each row's fields sit together. Analytics asks a different shape of question: "average order value per month over 40 million orders" touches two columns of every row. Columnar formats store each column's values together instead, so a scan reads only the columns it needs, compressed tightly — routinely orders of magnitude less I/O for analytical queries. The standard columnar file format is Apache Parquet.
A data lake is the pattern of putting those files on cheap object storage rather than inside a proprietary warehouse. But a folder of files is not a database: nothing makes a multi-file update atomic, or lets two engines agree on the current version of a table. Apache Iceberg fixes that — it is an open table format, a metadata layer over Parquet files that provides real tables: atomic commits, snapshots (every commit preserves the previous versions), schema evolution, and time travel. A small service called a catalog holds the pointer to each table's current metadata, so any number of engines can read a consistent view. Lake + table semantics = lakehouse. The point of it all is openness: an Iceberg table belongs to you, on your storage, and DuckDB, Spark, Trino, Snowflake and friends all read it natively — no export, no vendor gate. Why analysts insist on this: their tooling changes every few years, and open formats are the guarantee that data outlives tools.
The pipeline problem — and how storage-level decoding deletes it
So the operational world runs on row-shaped Postgres and the analytical world runs on column-shaped lakehouses — and connecting them is one of the most duct-taped areas in data engineering. The standard answer is CDC (change data capture): a pipeline product subscribes to the database's logical replication stream — a decoded feed of row changes Postgres computes on the primary, held open by a replication slot — and writes the changes into lake files. This works, and an entire industry (Debezium + Kafka + Spark, Fivetran, and kin) exists to do it. The costs are structural: a second stateful system to operate; lag measured in minutes to hours; per-table configuration; DDL (schema changes) handled poorly or not at all; load and bookkeeping imposed on the production primary; and no consistency contract — a transaction touching five tables arrives at the lake as five uncoordinated trickles, so the lake is perpetually a little bit wrong about the relationships between tables.
ArgonDB's observation: all of that exists only because the pipeline lives outside the database. At the storage layer, everything a pipeline struggles to reconstruct is simply there:
- argondb-decoder reads the WAL physically — raw storage records,
not the logical-replication feed — and reconstructs fully typed rows.
Needs the table's column types? The system catalogs are pages too,
readable at the same LSN as the data, so schema is always exactly
right, even mid-DDL. Needs a value that Postgres stored out-of-line
because it was large (TOAST, Postgres's mechanism for oversized
values)? Fetch those pages at the same LSN. Needs the pre-image of a
deleted row?
GetPage@LSNat the instant before. No replication slot, no decoding plugin, zero work added to the write path. - argondb-committer groups decoded changes by the transactions that produced them and lands them in Iceberg as atomic multi-table commits aligned to Postgres commit LSNs. Every commit is stamped with its watermark: the LSN through which the lake is complete.
- The delta ring covers the gap between lake commits. Iceberg commits are deliberately paced (many small files would poison the lake — more below), so the most recent seconds of changes are held decoded in memory. Freshness without file spam.
- The changelog (opt-in per table) materializes the change stream
itself as an append-only Iceberg table —
orders__changes— every row carrying the LSN, commit timestamp, and operation that produced it. History becomes data you can query with SQL, and it powers time travel and the "what changed since X?" answers below.
One write path in, two synchronized truths out — and one clock, the LSN, across both. That is the whole trick. The rest of this page is what follows from it.
Pins: a coordinate you can hand around
Because both halves are addressed by the same clock, ArgonDB can give you the clock itself. That object is a pin, and it is the most useful thing on this page, so here is exactly what it is.
A pin is a coordinate. Four fields — the branch, an LSN, an optional
list of tables it covers, and the moment it was minted — serialized,
signed with the deployment's key, and handed back to you as a short
opaque string beginning pin-. That string is the pin. You call
pin() once, then pass it to any tool; every answer you get comes from
the database as of that exact instant. The database has 40,000 new rows
by the time you ask your fourth question, and your fourth answer still
agrees with your first.
And the same coordinate is honoured by both engines. This is the distinction worth being precise about, because the two sides work differently:
- The lake side is answered in process. The query engine reads Iceberg at the watermark and merges the delta ring's changes above it, cut at your LSN. The number of valid pins is never capped — the token itself is the pin — the server just keeps the sixty-four most recently used query sessions warm; a pin beyond that pays a quick session rebuild on its next use, nothing more.
- The Postgres side is answered by a Postgres. Redeeming a pin there boots a read-only analyst compute pinned at that LSN, out of the storage engine's retained history — a real Postgres serving that past instant, with the full native SQL surface, in seconds, on demand. Your production primary is never involved.
The Postgres side is the move a shared-nothing database cannot make. Because storage keeps the history and computes are disposable, ArgonDB can build you a database at the moment you care about rather than freeze the one serving your customers. One consistency claim, two readers — an agent can check a columnar aggregate and a native Postgres query against each other at the same coordinate and expect them to agree.
Three consequences of a pin being a value rather than a handle:
- An idle pin costs nothing. Nothing is held open, so an agent can keep a view for an hour while it reasons, and the cleanup horizon from Component 1 never moves on its account.
- Pins outlive the process that minted them. Any server holding the deployment key can redeem any unexpired token, so a pin survives a restart and is honoured by a different replica. The pin belongs to the conversation, not to a socket.
- Expiry is arithmetic. The server checks the token's age against a
retention window — 48 hours by default. There is no lease to renew and
nothing to leak if an agent crashes mid-thought. Deliberately, that
same number is the floor for how long snapshots and any change
history a table keeps (the
__changeschangelog that time travel replays — opt-in per table) are retained, and how far time travel reaches — so garbage collection can never remove data that a still-valid pin could name, and a changelog can never expire out from under a pin that would need it to reconstruct its instant. Past the window, redemption refuses and says so. A tampered token is refused too — never quietly served, and never silently answered from a nearby LSN.
Two further properties are easy to miss and matter the first time you hit them:
-
A pin states what it covers. By default it covers every table, which is the strong claim — one atomic cut across the whole database. You can narrow it to a named list, and then the consistency promise is exactly that list. The rule cuts both ways: ArgonDB never claims consistency over data your pin did not name, and never refuses your pin because of a table it does not cover.
-
Asking for a pin does not fail, and the coordinate it hands you lasts. If the very newest instant is not yet servable across everything in scope — a bulk load still being digested, say — the pin lands at the newest instant where every covered table can be served, and the answer says so plainly: here is the live position, here is where you were pinned, here is the distance between them.
The default goes further than "servable right now": it lands on a coordinate that will still be servable for the pin's whole window. That distinction is real work on one kind of table, and it is worth being blunt about the limit behind it. Reading an instant below the lake watermark means reconstructing it from that table's change history — ArgonDB's changelog, a setting you turn on per published table (it is ArgonDB's, not a Postgres feature), so it is opt-in. A table without one can only be read at instants the lake actually committed, so above the watermark there is a live window that serves beautifully right up until the next commit consumes it. The default therefore lands such a table on its last committed instant rather than on the live head: you trade reading your own newest writes for a coordinate that is still good in an hour. Turn the changelog on and the trade disappears — history becomes row-exact and the default rides the head again.
You can instead demand the exact head, and get it, together with a line at mint naming the tables that coordinate will stop serving for and what to do about it. Or name an exact past instant and get that or nothing. What never happens is a quiet slide to a nearby instant — or a coordinate handed over silently that was never going to last.
A longer treatment of why this is not implemented as a held transaction, and what it costs when it has to be, is in the MCP section below.
The promises, and which ones bend
Everything so far is mechanism. Here are the promises the mechanism exists to keep — and the useful part is not the list, it is which ones are absolute and which are allowed to sag under load.
Six are absolute. A release that broke one of these would be a bug, not a tradeoff:
- Your Postgres is real Postgres. Any SQL, type, extension or driver behaviour supported by the Postgres underneath works here unchanged, because ArgonDB adds nothing to the write path. The lake half is a reader of storage; it never joins your transactions.
- A pinned read is exactly what Postgres saw at that instant. For every table the pin covers, transactions committed at or before that LSN are fully visible and every other transaction is fully invisible. No half-applied transaction, no table a moment ahead of its neighbour, and work that was rolled back or is still open never appears in any answer — lake side or fresh side.
- Nothing reaches the lake unless you publish it. Its own section below.
- Analytics never pushes back on transactions. Analytical and agent load can never slow, block or stall the write path. When the two sides have to trade, the analytics side pays — in lag, never the other way round — and the answer discloses what it paid. This is structural rather than a tuning goal: the read plane holds no connection to the primary and has no channel through which to reach back into it.
- A crash cannot lose or duplicate a change. The durable record of how far the lake is complete is the watermark stamped inside the Iceberg snapshots themselves, not a progress file beside them that could disagree. After a crash ArgonDB restarts from the lowest watermark across tables and re-applies from there; every stage recognises work it already did and skips it.
- You can leave. Stock Iceberg on your storage, readable with no ArgonDB process running. Its own section below.
Two are allowed to bend, and are written down as goals rather than guarantees:
- Freshness. Under normal operation the fresh side tracks within about a second of the database, and the lake's watermark trails by the commit cadence — targeting a minute, traded against keeping file sizes healthy. Under a load spike or a slow object store, that stretches.
- Read-your-writes. Capture the position of your commit, pin at or above it, and you see your own write. This depends on the same tail keeping up, so it degrades with freshness rather than independently.
The asymmetry is deliberate, and it is the shape of the whole contract: correctness is absolute, timeliness is negotiable. A slow tail costs you freshness, which is a state the system is built to ride out and recover from. A read that mixed instants would cost you correctness, and there is no acceptable degraded version of that — so ArgonDB refuses instead of serving it. When you find a case the rules above do not literally cover, that is the direction they resolve in.
How we know: Postgres itself is the referee
A fair question about a decoder that rebuilds your rows out of raw storage bytes: how do you know it got them right? The answer is that ArgonDB is never taken at its word. Because storage keeps history, we can boot a real read-only Postgres at the same instant, ask it for the same rows, and compare the two answers byte for byte. Postgres is the reference implementation of Postgres; making it the referee turns "is the decoder correct" from an opinion into a test that runs continuously and can fail.
That referee is the backbone of a testing system that is deliberately this project's primary engineering control, because the code is substantially AI-written, and what that calls for is not assurance, it is oracles:
- The differential oracle. Decoder output versus real-Postgres output at the same instant, canonicalized and compared exactly. Analytical results are checked the same way, against a read-only Postgres at the same LSN.
- Property-based tests. Random schemas crossed with random histories of changes and schema edits, round-tripped — rather than the handful of cases a human thought to write down.
- Deterministic fault injection. Crash and restart at every pause point in the commit path, catalog conflicts, object-store errors injected on purpose. The crash-safety and consistency promises above are asserted under fault, not under calm.
- A continuous invariant checker. A long-running mixed workload with pinned reads at random instants, checking cross-table invariants that can only hold if the consistency promise holds.
- Foreign-engine conformance. Every table shape ArgonDB writes is read back by an engine that shares none of our code — because a round-trip through your own reader hides interchange bugs by construction.
- Performance regression. Transactional overhead against the unmodified baseline, decode throughput, freshness percentiles.
ArgonDB Features
What follows is a series of features, each one something the combined Postgres, Neon and ArgonDB stack provides out of the box. They are described in the order a team meets them: getting data in, keeping the lake exact, reading it at any instant, searching it, and keeping the whole thing healthy without a maintenance job. The agent interface and the ways to run it follow in their own sections.
Ingestion without a pipeline
An application's writes land in ordinary Postgres tables; ArgonDB's decoder turns the WAL — the same write-ahead log you met above — into lake commits. The write rate and the lake watermark advance together, and the gap between them is the whole "pipeline" story — measured in seconds. Nobody configures a connector for these tables; publishing them (next section) is the entire setup.
Publish any table with one command
Nothing reaches the lake until you ask. Publishing is a deliberate,
per-table choice — the default published set is empty, because no
system can guess which of your tables are precious and which are
scratch. argondb lake publish <table> marks the table (the marker
lives in the table's own comment, so it travels with the database and
its branches), backfills the full table into Iceberg at a pinned LSN,
and keeps it current from then on — the same decode path as live
changes, so there is no separate "initial sync" machinery to distrust.
The consequences of that choice, so none of them surprise you: a new
table you create later is not automatically enrolled, because the
default is "no" and it stays "no" until you say otherwise. Stopping is
two-tier, mirroring DROP TABLE against DROP TABLE PURGE: unpublish
stops capture and keeps every lake file and all its history, which is
instant and reversible, and only an explicit purge — after showing you
what it will destroy — drops the Iceberg table and deletes its files.
Dropping the table in Postgres behaves like the gentle one: capture
stops, the lake data stays. And temporary or unlogged tables cannot be
published at all, because they generate no log records — there is
literally nothing for ArgonDB to read.
Primary keys: the one schema choice the lake cares about
ArgonDB guarantees the lake copy of a published table is 100% accurate — with or without a primary key. What the primary key decides is how much work keeping it accurate costs, and it is the one schema choice worth making deliberately before you publish.
The mechanism: Postgres identifies row versions physically — "page 47, slot 3" — and that address means nothing in a columnar lake file. When a row is updated, ArgonDB must find and replace the old copy in the lake, and a primary key is the only row identity that survives the trip from physical pages to logical columns (physical addresses are recycled by vacuum; whole-row matching cannot tell two identical rows apart). With a primary key, an update becomes a small incremental lake commit: the new row plus a "delete the row with this key" marker. Without one, ArgonDB keeps its guarantee the only way that remains: it re-scans the table and rewrites its lake copy whole at the next commit — still exactly right, at full-table cost in time and system load, each cadence in which the table changed at all.
So the guidance is simple: give every table you publish a primary key. Most production schemas already do. A table without one still publishes and the lake stays exact — you have simply chosen the expensive path, and you should choose it knowingly: a small or rarely-changing table without a key costs little, while a large, busy one will spend real rewrite work on every change.
Worth knowing how the rest of the industry handles this: conventional
CDC rides logical replication, and its no-key options all intrude on
your primary — REPLICA IDENTITY FULL logs the entire old row into
the WAL on every update (and still cannot tell duplicates apart), or
nothing, in which case Postgres rejects the application's
UPDATE/DELETE outright. Because ArgonDB reads storage, your schema,
WAL settings and application writes are untouched either way; the cost
of a missing key lands on ArgonDB's own lake-side compute, never on
your write path.
Atomic multi-table commits
A Postgres transaction touching five tables becomes ONE Iceberg commit touching five tables — cross-table consistency survives into the lake. This is the consistency contract CDC pipelines structurally cannot give you (each table trickles independently), and it falls out of commit-LSN alignment: the committer never splits a transaction across lake commits. It is visible in the catalog: when several tables jump to the same watermark at once, that is an atomic cut.
Freshness without commit spam
There is a real tension here: every Iceberg commit writes files, and a lake made of millions of tiny files becomes slow for every engine that reads it. Pipelines resolve the tension by giving up freshness (commit rarely, lag by minutes). ArgonDB resolves it with the delta ring: Iceberg commit cadence is tuned for healthy file sizes, and freshness comes from merging the in-memory ring above the watermark at read time, pinned to one LSN — so a read is both current to the instant and transactionally consistent. The measurement that settles it is end to end: write a marker row, then time how long until a consistent read sees it. The answer is seconds.
The changelog: your table's history as a table
Any published table can opt into a changelog — a sibling table
named orders__changes that records every change as a row: the new
row image, the operation (insert/update/delete — deletes carry the
key), the exact commit position (LSN), and the commit timestamp. It is
built from the same decoded stream as the table itself and lands in
the same atomic commits, so the history is never out of step with the
data. Rows are only ever added; nothing rewrites your history.
Why you'd turn it on, per table:
- Audit and debugging become SQL. "Who changed this row, when, from what to what" is a query, not a log-diving expedition.
- It is a ready-made work queue. A search index, cache, or ML
pipeline downstream of a table can ask "everything since the last
position I processed" and get exactly that — the
changes()tool is built on it. No triggers, no outbox tables, no Kafka. - It powers time travel (next section): an old snapshot plus changelog replay reconstructs the table at any instant, row-exact.
- It is a Bronze layer for free. Teams that build medallion pipelines spend real engineering landing raw change history into the lake; here it is one flag on one table.
The costs, stated plainly: it is opt-in per table because history has a storage price — a hot table's changelog grows with its write rate. History is kept for the retention window (48 hours by default, raisable per deployment; it can never be shorter than the pin window, for the reason in the pin section — time travel replays this table). And like everything in the lake, it is a plain Iceberg table any engine can read.
Time travel
Any table can be read at any LSN within the retention window — row-exact, reconstructed from a retained snapshot plus changelog replay. This is the LSN-as-clock idea made usable: "the same query, then and now" needs no snapshots anyone remembered to take, because history is a first-class coordinate. Same query, two coordinates:
Search over your own text, at the same coordinate
A lot of the data people actually want to ask about is text — filings, articles, issue threads, commit messages. The usual answer is a second system: ship the text to a search service, keep it in sync, and accept that its idea of the world disagrees with the database's. ArgonDB indexes text where the text already lives. The index is built by the same maintenance plane that keeps the lake healthy, from the same decoded change stream, and every hit comes back stamped with the position it was indexed at — so a caller can see how current the index is instead of assuming. In the multi-machine shape the index travels the same road as everything else: the worker that builds it uploads finished segments to object storage and the read replicas pick them up, which is exactly why a hit states which index position answered it. Even the search index lives on the object store.
Two things make it more useful than a search box:
- Ranking is BM25 — the classic relevance measure, which weighs how often your words appear in a document against how common those words are across the whole corpus, so a rare term counts for more than a common one. Its tuning parameters are not exposed: the library defaults are the ecosystem's defaults, and there is nothing here to get subtly wrong.
- Scoping is structural. Documents have shape, and the index keeps
it. A query can ask only inside one named section — a contract's
termination clause, a filing's risk factors — across every document at
once. Companion tools navigate that shape directly:
outlinereturns a document's section tree where every line is an address the other tools accept,readreturns one value in full with continuation offsets, andexpandwalks foreign keys to hand back the neighbourhood around a row — a pull request with its repository, author, comments and reviews in one call.
Enrollment is declarative: a configuration file names the columns to index, and how to chunk and section them — the tier that gives a document-shaped corpus its structure, and the one a serious text corpus wants. The direction of travel from there is toward the smallest declaration that can still carry the intent, a marker on a column with sensible defaults behind it, and toward optional embeddings computed under your own provider's key, never ours. BM25 stands entirely on its own for anyone who wants nothing to do with a model.
Branches and the lake: free until read, a full copy once used
You met branching in the Neon section: on the Postgres side a branch is metadata — instant, and nearly free until it diverges. The lake side works differently, and the difference is worth knowing before you branch a database with published tables. A branch's lake tables are materialized lazily: until something actually reads or publishes on the branch, the lake does no work and stores nothing for it, so creating branches freely costs nothing. Once a branch's lake side is used, its tables become full, independent copies — real storage, not shared files. That independence is deliberate: every branch's tables remain plain Iceberg any engine can read, and maintenance on one branch (compaction, cleanup, expiry) can never corrupt or slow another. Rule of thumb: branch as freely as you like for Postgres-side work; expect real storage the moment a branch's lake tables are read.
Gaps, lower bounds, and values the lake cannot hold
When ArgonDB must skip WAL (a re-baseline under overload), it records the gap durably and refuses to reconstruct history inside it — naming the range, the reason, and the remedy. Digests over a window touching a gap say "these counts are lower bounds." Reads served behind the live head say so. Values Postgres allows but Iceberg cannot represent (NaN in a bounded numeric, infinity dates) become NULLs with a durable, queryable record — never silently. This is what a database built for agents has to do: the worst answer is a confident wrong one.
None of this is theoretical. Put ArgonDB on a box that doubles as a build machine and let heavy compilation contend for its cores, and the machinery acts in real time — re-baselines with named ranges, digests that say "lower bound," reads that disclose how far behind the head they ran — instead of a stalled system pretending nothing happened. Pressure is the point: this is the behavior under load, not despite it.
That last one deserves its mechanism, because it is the only place ArgonDB knowingly does not carry a value across. The set is small and specific: Postgres will store a "not a number" in a fixed-precision numeric column and an "infinity" in a date, and the lake's type system has nowhere to put either. Ordinary floating-point NaN and infinity are not in this set — both sides use the same IEEE representation, so they pass through untouched. For the handful that do not fit, the two obvious policies are both bad: hard-erroring turns one odd row into a lake-wide outage, and quietly writing NULL loses data invisibly. So the mapping is the one our closest commercial equivalent publishes — chosen deliberately, so migration comparisons are like-for-like and any criticism of the mapping lands on them too — with one difference that is the whole point: they NULL it silently, we NULL it and record the table, the column, the position and the count, then disclose it on every read that touches the column. Very large or unbounded numerics do not lose anything at all; they are preserved exactly, as text.
"Every read" is carrying weight in that sentence, and it is worth unpacking, because ArgonDB has two engines that can answer the same pinned question — and on a column like this one they legitimately disagree. The lake path gives you NULL, because NULL is what the lake holds. The Postgres path gives you the original value, because an analyst compute reads pages rather than lake files and the value is still sitting there. Neither answer is wrong, and neither is allowed to be quiet about it: a lake-served answer discloses that this column's unrepresentable values read back as NULL, and a Postgres-served answer discloses that it is handing you the original where the lake copies are NULL-mapped. The two paths differ in value, but never in silence — which is the only version of this that an agent comparing the two answers can be trusted to handle.
When something goes wrong: errors you can act on
Ingestion failures are where pipeline products quietly hurt you: one unprocessable record and the standard choices are a stalled pipeline (everything stops until a human intervenes) or a silent skip (the lake is now wrong and nobody knows). ArgonDB does neither. A table that repeatedly fails to process is quarantined alone: it freezes at its last known-good position while every other table continues normally.
What you see is designed to be actionable rather than mysterious:
- Reads stay safe. Data up to the table's last-good position serves normally. A read that would need the missing newer data is refused with a plain answer: which table, why it is quarantined, where its good data ends, and what to do next. You can never be silently served wrong or stale data because of a failure — that is the same disclosure contract as everything above.
- The cause is stated, with a remedy. A quarantine names its
trigger (for example, a row whose value the lake cannot represent)
and the fix (repair the row, or set the per-table
argon:specialspolicy). It retries automatically once the cause clears. - An agent can ask. The diagnostic MCP surface's
healthtool reports quarantined tables and recovery activity, so an AI operator can notice and triage without a human reading logs. - Recovery is never silently expensive. When ArgonDB falls back to a heavier path to keep a table correct (a full rewrite instead of an incremental update), it logs what it did, why, and what it cost — and a circuit breaker stops any failure loop from burning resources unattended.
When a whole process dies
The failures above are the polite ones — a bad row, a value the lake cannot represent. The impolite ones end a process mid-thought: the kernel kills the biggest memory user, a DIMM starts flipping bits, a disk lies about what it wrote, or a plain bug panics. Every database faces these; the differences are in what happens next. Because ArgonDB is three layers, the full answer comes in three parts — what Postgres has always done, what Neon adds (all of which we inherit), and what ArgonDB builds on top for the lake.
What Postgres does. Crash safety is Postgres's oldest muscle, and it is the same WAL story from earlier in this page: the diary is written and made durable before the data pages it describes. After a crash, restart replays the diary since the last checkpoint, and the database comes back holding exactly the committed transactions — nothing acknowledged is lost, nothing half-done survives. Two details worth knowing because experts will ask: if any backend dies unexpectedly (an out-of-memory kill included), the postmaster deliberately restarts the whole process family and runs that same recovery, because shared memory cannot be trusted after an unexplained death — so it isn't. And power loss mid-page-write (a "torn page") is covered by writing a full image of each page to the diary the first time it changes after a checkpoint.
What Neon adds — and we inherit. Separating storage from compute turns "recover the machine" into "replace the part." The compute is stateless — its local disk is only a cache — so a crashed or OOM-killed compute is simply started again at the exact position the WAL reached; there is nothing local to salvage. The WAL itself is durable on a quorum of safekeepers the instant it is written: any one of the three can die mid-write with no data loss and no pause, which is precisely the faulty-hardware scenario a single machine cannot survive. And the pageserver's layers live on object storage, so a dead pageserver is failed over by the storage controller and rebuilt from what the safekeepers and object store already hold. Faulty hardware becomes a replacement event, not a data event.
What ArgonDB adds. The lake half follows one rule: make every step either durable or repeatable, and recovery becomes "start again" rather than "repair."
- The tail's position in the WAL is recorded per table, durably, alongside the data it commits. After a crash it re-reads the WAL from each table's own watermark — the safekeepers still have it — and replays; the commit protocol is idempotent, so rows in the overlap land once, not twice.
- A lake commit is one atomic swap in the catalog: the table moves from one complete snapshot to the next, or not at all. A crash mid-commit can strand unfinished files on disk — the garbage collector's orphan sweep removes those, after its grace window — but it can never leave a half-visible table. The multi-table atomic cut is the same mechanism, so a crash cannot leave two tables disagreeing about the watermark either.
- Processes are supervised: a dead maintenance or ingestion process is restarted with backoff, every exit is logged with its status, and the same circuit-breaker rule as above applies to restarts — a crash loop stops and alarms rather than grinding quietly forever. The restart appends to the crashed process's log rather than starting it over, so the evidence of what went wrong — the panic text itself — survives for the human or agent who investigates.
And the part we will not sweep under the rug. Robustness sections that only describe design are describing hope. A concrete case: our exact-statistics pass, computing distinct counts over a very wide text column, ran into an internal 2 GB buffer limit and panicked — and because the supervisor restarted it on cadence, it crash-looped. What the design bought: ingestion, reads, and the OLTP primary were unaffected the whole time, no data was lost or corrupted, and the blast radius was exactly "background maintenance falls behind" — compaction paused, so reads on that table slowly degraded toward the unclustered numbers above until the fix landed (the fix bounds the memory: fingerprint the values instead of holding them all). That is the shape of the promise, stated plainly: a crash costs you the freshness of background work while it is contained; it does not cost you acknowledged data, and it cannot cost you a consistent lake — the WAL quorum holds the truth, and every recovery starts from it.
Ingestion support: your extractor, and everything around it
ArgonDB Features opened with a section called "Ingestion without a pipeline," and it is worth being exact about which pipeline that is. The one that disappears is the pipeline out of the database — the connector, the queue and the transformation job a conventional stack needs to get a Postgres table into a lake. The pipeline into the database does not disappear, and it is not ours to write. The PDFs, the vendor CSVs, the partner API that changes its mind twice a year, the filings that mean nothing until somebody parses them: that code encodes what your business believes a valid record is, and nobody outside your building can write it for you. ArgonDB does not try to. What it owns is everything around that code — where the output lands, what happens to the record that will not parse, what happens when the shape upstream changes, how you re-run after you fix the extractor, and whether anyone can see any of it at three in the morning.
Every building that takes deliveries has a receiving dock, and the dock is not the truck. Whoever sends the pallets decides what is on them and how they are packed; the building owns the bay, the scales, the clipboard, and the corner where anything that arrives damaged is set down with a note on it — so the driver is not held up and nothing is quietly thrown out the back. Nobody expects the building to know how to pack a pallet. What everybody expects is that one damaged crate does not stop the delivery, and does not vanish either.
The landing zone is ordinary tables. An extractor's output target is
an INSERT; there is no ingestion runtime in front of the database with
its own storage, its own format and its own idea of consistency. What
the extractor pulled out as structure — the source, the date, the
identifier, the section it came from — lands in typed columns beside the
text column that holds the prose, and from that moment everything in
this part of the page is yours for free: publish the table and it is in
the lake, turn on the changelog and its history is a table, pin it, read
it as of an hour ago, search the text inside it, let a foreign engine
read the whole thing. Landing your data and getting the platform are the
same act, not two projects.
Typed columns are the gate. A landing table that accepts anything — one blob column, whatever shape the extractor emitted this week — tells you nothing, and drift into it is invisible by construction: a renamed field simply becomes a key nobody reads, and the report built on it quietly gets thinner. A declared column with a declared type refuses the record that no longer fits and says which column and what arrived instead, on the first record rather than in a quarterly review. The gate is not there to be strict. It is there so that a change upstream becomes an event with a timestamp instead of a slow contamination somebody finds later.
A bad record's home is a row, not an exception. The pattern this makes cheap is the one the industry calls a dead letter, and the shape of it here is that the failed record goes into a table beside the good ones, carrying the reason it failed, the position it came from, and enough of the original to fix it. Good rows land. The batch does not stop for one bad crate, and nothing is dropped where nobody can find it. Note that this is a different thing from the per-table quarantine described a few sections up: that one is ArgonDB containing its own decode failure, this one is your extractor keeping what it could not use. Both obey the same rule, which is that the system is never allowed to be quietly wrong. And because a transaction touching several tables becomes one lake commit, the good rows and the record of the rejected ones land as a single cut — the lake never shows you a batch without the evidence of what it left behind.
A branch is where a batch gets staged. Branching the Postgres side is metadata: instant, and near-free until it diverges. So the safe shape for a load that you are not sure about is to run it on a branch, check it there with ordinary SQL — counts against the source, distributions that should not have moved, the ten worst rows — and only then run it for real. A batch that turns out to be wrong is deleted along with the branch, at no cost and with the real database never having seen it, which is a considerably better answer than discovering the problem afterwards and writing a correction script under pressure.
The pipeline's health lives in the same system as its output. Row counts, rejects, the position each table has reached, the lake watermark, how far behind the text index is — these are tables and tool calls in the database that holds the data, not a separate monitoring stack with its own login and its own version of the truth. "Did last night's load go well" is a query. The diagnostic surface answers the same question to an agent, which is what makes an unattended load supervisable by something other than a person reading logs at breakfast.
The changelog is the re-ingest work queue. After you fix an extractor the question is scope: what has to be done again. The usual answers are both bad — reprocess everything, which is expensive and rewrites rows nobody asked you to touch, or trust a modified-at column that some writer forgot to set. Because every change is recorded with its exact commit position, "everything that moved since this coordinate" is an exact answer rather than an estimate, and re-driving a downstream step means replaying that list. It is the same mechanism a search index or a cache uses to stay current; a fixed extractor is just another consumer of it.
Where this shows up. Four ordinary nights and mornings:
- The malformed record at three in the morning. One row out of millions has a field the parser cannot read. The two familiar outcomes are both bad: the load aborts and somebody's phone rings for a single bad row, or the load skips it and the number on the dashboard is wrong with nothing to point at. Here the batch finishes, the good rows are queryable at breakfast, the bad record is sitting in a table with its reason beside it, and the person who fixes it does so at a civilised hour with the evidence in front of them.
- Upstream changes shape on a Monday. A vendor renames a field, widens a code, starts sending a date where a number used to be. The load that breaks is not the dangerous one — the dangerous one is the load that succeeds and is subtly wrong. A typed landing table refuses the changed record and names the column, so the conversation with the vendor starts on Monday, from the first record, rather than in the audit that finds a quarter of bad numbers.
- The re-run after the extractor is fixed. You have found the bug and corrected the parser, and now you need to know what the broken version wrote and how much of it to redo. Time travel gives you the table as it stood before the bad run; the changelog gives you exactly which rows moved since that coordinate. Between them the re-run is a bounded piece of work with a definite edge, instead of a full reload chosen because nobody could prove a smaller one was safe.
- Validating a new extractor version against the current one. A parser rewrite is a change to millions of records, and the only convincing test is the old one and the new one over the same source. Two branches, one version each, and the comparison is a query rather than a spreadsheet of spot checks: each branch's published tables are plain Iceberg, so any engine can read both sides and show you exactly which records the new parser reads differently. Then you keep the winner and delete the other branch.
The boundary is deliberate, and it cuts both ways. ArgonDB has no transformation language, no mapping UI and no library of source connectors, and does not want them: your extraction rules are business logic, and business logic is worth more living in your repository, in your language, under your tests, than expressed in somebody else's configuration format. What a database can do is make that code safe to run while everyone is asleep — give its output a typed home, refuse what ought to be refused, keep what could not be used, and stay answerable about all of it. The load itself is also the textbook case for the bulk class described below: heavy, important, and with no claim whatsoever on the front door the application is using.
Bring your own lake
Everything above works with zero lake setup: the box bundles a local object store and catalog, so the lake is just files on your disk. But the lake tier is pluggable by design — point ArgonDB at your existing object store (S3 or Azure Blob) and your own Iceberg REST catalog instead, and its tables land in the infrastructure you already run, beside the tables you already have, readable by every engine already connected to that catalog. Nothing about ArgonDB's behavior changes with the destination: same commits, same maintenance, same guarantees — your storage, your catalog, your data.
The catalog half and the object-store half are independently substitutable — bring your own catalog and keep the bundled storage, or the reverse — and the handover has a rule worth knowing, because getting it wrong quietly is the classic way this goes bad. Name none of a half's settings and you get the bundled one. Name any of them and all of them become required, and a missing one refuses at startup, naming the setting, the endpoint it was needed for, and the fix. What ArgonDB will never do is fall back to development credentials against an endpoint you supplied — that is how data lands somewhere nobody intended, with no error to notice.
Any engine reads it
The lake tables are stock Iceberg. DuckDB — a completely foreign engine, no ArgonDB code — answers real questions against the same tables ArgonDB is writing, straight through the standard REST catalog. This is the open-format promise from the lakehouse section made testable: your data's exit door is always open, even from ArgonDB itself.
The sharper version of the same test runs one count three ways at once — on Postgres (the source), on ArgonDB's own lake, and on DuckDB — all pinned to one identical coordinate, an exact LSN. Three independent engines, one frozen instant, one number apiece, all three printed, so agreement is something you check rather than something we assert.
One promise about those tables matters more the longer you keep them:
the names ArgonDB writes into your lake are frozen forever. The
__changes suffix on a changelog table, the _argon_ columns inside
it, the Argon. properties stamped on every snapshot, the argon:
markers in table comments — all of these are treated as specification
constants rather than branding. Foreign engines read them, and so will
queries you write years from now, so they are never renamed. Postgres
never renamed its pg_ catalogs for the same reason. If the product
name changes, that is our problem, not yours.
Behind the scenes: the chores you never schedule
A lakehouse silts up without maintenance: small files accumulate (every commit adds some), statistics go stale, old snapshots pile up. The usual answer is scheduled Spark maintenance jobs — one more system to run, and one more thing to forget. ArgonDB runs its own lake hygiene on a fixed cadence, coordinated with ingestion, because it is the lake's single writer and already knows, from the WAL, exactly what changed. No external ANALYZE, no Spark jobs, ever. Here is what that sweep actually does for you, in plain terms.
Keeping the files tidy (compaction). Imagine filing receipts by tossing each day's handful into a shoebox. Nothing is ever lost, but after a month, finding anything means going through every box. That is what continuous ingestion does to a lake: every commit adds a few small files, and updates add "cross-out notes" (delete files) on top. On its sweep, ArgonDB rewrites the accumulated small files into a few large, well-organized ones and folds the cross-out notes in while it is there — so readers stop paying for the mess, and the merge-on-read debt is paid down continuously instead of piling up for a weekend job.
Squeezing the files (compression). An analytical file is written once and read thousands of times, so every choice about how it is written gets made in the reader's favour. Compression is the clearest example. The lake writes Zstandard-compressed Parquet, and the setting is turned up hard rather than left at a middling default. What that costs: several times more CPU to compress, which on our bench corpus adds one to two percent to the time a compaction rewrite takes. What it buys: files about 22% smaller — and reads that get faster, not slower. A cold single-row lookup on that corpus went from 20.8 ms to 13.4 ms, because the reader's real bill is bytes fetched from storage, and there are fewer of them to fetch than there is extra decoding to do. That is the trade this product takes on purpose whenever the two sides pull apart: the write side pays, the read side gains. Nothing you already wrote is stranded by it — old files stay readable exactly as they are, and re-encode only if compaction was going to rewrite them anyway.
Keeping the shelves sorted (your primary key). A library where
books are shelved in arrival order makes you walk every aisle to find
one author. Sorted shelves, one aisle. If your published table has a
primary key, ArgonDB keeps its lake files sorted by it whenever
compaction rewrites them anyway — you do not ask for this, it is the
default. Why it matters: every lake file records each column's
smallest and largest value (and so does every block inside a file),
so a lookup can skip whole files whose range cannot contain the key.
Sorted, those ranges are tight and nearly everything is skipped;
unsorted, every file spans everything and nothing can be skipped. On
our half-million-row bench corpus this took a cold single-row lookup
from ~16 seconds to a quarter of a second — and because the sort
rides a rewrite that is already paying full write cost, it is
essentially free. Your table's natural axis isn't the key? Point the
sort somewhere else with argon:cluster-by=<column> in the table's
comment — an explicit marker always wins — or opt out entirely with
argon:cluster-by=none.
Taking out the trash (garbage collection). Postgres calls this vacuum. A lakehouse needs its own version, for a different reason: the lake never scribbles over old data — every change writes new files and keeps the old ones, like a photo album that gets a fresh page instead of a paste-over. That is precisely what makes time travel and pins work. But old pages cannot pile up forever, so the sweep does two recycling chores. Snapshot expiry retires table versions older than the retention window — the same window pins and time travel honor, so nothing anyone can still reference is ever collected. Orphan cleanup removes files on disk that no surviving snapshot names anymore (compaction's rewrites leave these behind by design). And because deleting the wrong file is the one mistake a lakehouse cannot undo, the janitor works with a checklist: a grace window so a file another writer is still finishing is never touched, an independent second derivation of what is actually safe to delete, and a bounded number of deletions per pass — slow and right beats fast and sorry.
A card catalog that stays current (statistics). Query engines plan with summaries — row counts, distinct values, ranges — and stale summaries make them pick bad plans, like navigating with last year's map. Because every change flows through ArgonDB, the sweep keeps exact statistics as it goes. There is no ANALYZE to remember and no sampling guesswork.
The search index, too. The full-text index described earlier is maintained by this same sweep, from the same change stream — it is a tenant of the maintenance plane, not a second system.
And a promise about all of it: maintenance is not optional, not scheduled, not configured — it is simply always on, in every deployment, every class of it. It runs on a fixed cadence that is a constant in the code — there is no switch to forget, no job to own, no cadence to tune, and no way to run an ArgonDB whose lake silts up because someone missed a setup step. That is an instance of a general rule, and the rule gets its own section just below.
Why there are so few knobs
You may have noticed how little of this page is configuration. That is a deliberate rule, and it is worth stating because it is the opposite of how infrastructure usually ships: ArgonDB's behaviour is on, for every customer, permanently. A behavioural setting is allowed to exist only when two real customers need opposite values — one genuine use case per position — because every option is also a new way to be misconfigured, a state that then has to be detected, documented, tested in both positions, and supported in the field by people who cannot see your machine.
The rule has a sharp corner, and the corner is the point of it. A switch that exists as insurance against our own defects — "turn the risky subsystem off if it misbehaves" — is prohibited outright. It does not bring back what was lost, it does not help you understand what happened or even establish whether that subsystem was at fault, and it advertises that the vendor does not trust its own product. The effort goes into making the operation correct, and where the operation is genuinely dangerous, into making it foolproof: recoverable grace windows before anything is destroyed, an independent second derivation of what is safe to delete, and a refusal when the blast radius looks wrong. Never into making it skippable.
None of this is about ports, paths, credentials, sizes or retention windows. Those are configuration, they are yours, and they stay. What is absent by design is the behavioural switch.
Three planes, so a bad day stays in one of them
The work ArgonDB does divides into three jobs with completely different temperaments, and the shape of the system is that each one is its own process rather than a module inside a shared one:
- The writer. The tail from Component 3: read the log, decode it, land it in the lake in atomic cuts. There is exactly one per database, because a single ordered log is a single-file job — you cannot have two processes deciding what the next lake commit contains.
- The maintenance worker. All the chores from the section above, in their own process. Bursty and heavy by nature: a compaction pass rewrites gigabytes and a statistics pass reads whole columns. It is also the one role that can afford to be late — being a cadence behind costs efficiency, never correctness.
- The read plane. As many identical, interchangeable copies as you want, answering queries and agents. They hold nothing that must be kept in step, so you can add or remove them without telling anything else.
On a laptop, or on any single machine, all three run side by side under one supervisor; in the Kubernetes shape they come apart into separate workloads. The split itself does not change — that is the point of separating them by process rather than by deployment.
Why separate processes, in the only terms that matter to you: a role having a bad day has that day alone. You already read one of these higher up the page. Our statistics pass hit an internal buffer limit and crash-looped; the blast radius was exactly "the chores fall behind." Ingestion kept ingesting, reads kept answering, and the transactional machine never noticed. Had statistics been a thread inside the writer, the same bug would have been a lake outage.
Two consequences follow from the same shape:
- Your queries cannot be starved by housekeeping. Compaction runs somewhere else, and when a sweep runs long it logs the overrun and backs off rather than immediately starting the next one — chores never queue up behind themselves and squeeze out everything else.
- Readers never touch your primary, because they have no way to. This is the mechanism under a promise made earlier: the read plane holds no connection to the transactional database at all. It reads the lake and the storage layer. There is no channel through which an expensive analytical query could reach back and slow a transaction, which is why "analytics never pushes back" is a structural fact rather than a tuning goal you have to defend.
Where two of the planes do meet is the lake catalog: the writer and the maintenance worker both change tables there. That collision is arbitrated the way the lake arbitrates everything — one commit wins, the loser sees that the table moved under it and redoes its work against the new version. The cost of a collision is a chore taking a little longer. It is never a damaged table, because a lake commit is all-or-nothing.
The same three-way split reappears at the front door, along a completely different axis — not who does the work, but what a caller is permitted to do. That one is the three agent surfaces, and it gets its own section below.
Telling the database which work matters more
The split above is by role — the writer, the chores, the readers — and what it protects you from is the system's own housekeeping. It does nothing about the other kind of contention, the kind where all the work is yours. The order a customer is waiting on and the overnight backfill of three years of history come through the same front door, look like the same kind of work, and by default the database has no idea that anybody cares more about one of them than the other.
Picture an apartment building with a bank of elevators. Most days nobody thinks about them at all. Then somebody moves in, and a crew with a truckload of furniture can hold every car for an hour — nothing is broken, nothing is even slow in a way you could point at, and yet everybody who lives there is standing around waiting. Well-run buildings do not ban moving in. They keep one car for the residents and give the crew the rest. The move still finishes; it just never gets to take the whole building.
ArgonDB manages its resources the same way, by class, assigned per database rather than per query or per session — because a class is a statement about what a body of work is for, and that is a property of the application, not of whoever happened to open the connection. Work in the protected class has a guaranteed share nobody can eat into: connection slots held back for it, and storage reads that go to the front of the queue. Postgres already does a miniature version of the first half, holding back a few connections so an administrator can always get in to fix things; this is that idea generalized to "this database always has slots nobody else can take." Work in the bulk class gets whatever is left and yields whenever protected traffic shows up. Yields, not stops — a bulk load that never finishes is its own kind of outage, so the guarantee cuts in both directions: protected work is never starved, and bulk work always makes progress.
Priority on reads is the half a conventional database cannot offer you,
and the reason traces straight back to Component 2. Postgres hands its
reads to the operating system and the filesystem, so there is no seam at
which it could say that this page request matters more than that one, and
no vocabulary in which to say it. ArgonDB does not own a disk; it owns a
storage service, and every page it reads is a discrete GetPage@LSN
request to the pageserver. A request can therefore carry the class of the
database that asked for it and be scheduled on the way in. "The checkout
path's reads never queue behind a hundred-million-row scan" stops being a
tuning hope and becomes a property of the storage layer. Pulling storage
out of the database was supposed to buy elasticity and cheap branching —
this is that same separation paying out somewhere nobody was looking.
ArgonDB's own bookkeeping rides protected by default, in every deployment, with nothing to configure: the bundled catalog, and the internal tables that record what exists, where it lives, and what was skipped and why. That is deliberate, and it is the same instinct as the gaps-and-lower-bounds section above. The moment you most need to ask the system what is going on is the moment it is busiest, and a system whose answer to "what is happening right now" is the first thing to fall over under load is a system that goes dark exactly when someone is looking at it.
Where this shows up. All ordinary operational days, none of them exotic:
- The overnight load that is still running at nine in the morning. The nightly job ran long — the upstream system was late, or the batch was three times its usual size — and it is still going when the first dashboards open and the application starts taking orders. Nothing has failed. The day's work is simply queued behind last night's, and the first anyone hears about it is that the morning feels broken. Load in bulk, application protected: the load finishes late and nobody else finds out it happened.
- One database per customer, and one customer having a big week. The standard way to keep tenants apart is a database each, which separates their data perfectly and their resource use not at all. One tenant importing a year of records is enough to make the product feel broken to every other tenant, and part of why that is hard to fix is that the database has never had anywhere to record which tenants are on the premium tier, or which one is mid-migration this week. Classes are per database, so they land on exactly the boundary you already drew.
- The migration you are doing to yourself. Adding a column to a large table, rebuilding an index, correcting six months of bad values: planned, necessary, and unpleasant mostly because the only safe way to do it is to find an hour when nobody is using the system. In the bulk class it stops being a scheduling problem — it can run in the middle of the afternoon and get out of the way on its own whenever real traffic arrives.
- The batch job that lands on the order path. A finance export, a regulatory extract, a nightly reconciliation — heavy work belonging to a different part of the business, with no claim on the part of the system that takes money. It competes for connections and for storage reads and it wins as often as not, because nothing has ever told the database which of the two the company would rather have.
- The system staying answerable while it is busy. Under heavy load the first casualty is usually the machinery you would use to find out what is wrong. Because ArgonDB's own control machinery is protected, load heavy enough to slow your queries is not heavy enough to stop the system reporting on itself.
This is one of the few places the product asks you to declare something, and it earns that by the test set out just above: two customers genuinely need opposite answers here, because only you know which of your databases is the one that must not wait. What it deliberately is not is a priority number stapled to a query — those are famously easy to set and impossible to reason about across a whole system, which is why this is a small declared set of classes instead of a dial. And it is not a way to fit twice the work onto one machine. A class decides who waits when there is not enough to go around, which is a different question from how much there is.
Your existing queries, quietly answered by the fast side
Every application talks to Postgres, which is superb at fetching one record and slow at questions that sweep a whole table. ArgonDB already maintains the places where those sweeping questions are fast — the columnar lake, and read-only Postgres computes that never touch the primary. So the obvious move: when a session that has opted in sends one of those queries to the ordinary Postgres connection it already uses, ArgonDB hands the work to the read plane and returns the answer as if Postgres had done it. Same connection string, same driver, same results, and the transactional machine does none of the work.
The safety properties are the interesting part. Your own writes stay visible, because the far side is required to serve at or beyond your session's last commit. Anything that goes wrong — the far side slow, unreachable, or unsure — falls back to running the query locally, with the cost logged and a circuit breaker to stop a failure loop from burning resources unattended. And it is off until a session or a role opts in, so nothing about your existing traffic changes on an upgrade.
What sends a query across is isolation — the deliberate choice to move a heavy query off the transactional machine, which is a property of the query's role rather than a guess about its speed. The other reason a query might ever travel — "this one would simply be faster over there" — answers to the discipline set out in the MCP section below, and it is a strict one: no query shape is trusted with a faster path until both engines have been shown to answer it identically. Speed on its own is never a reason to risk a different answer.
The MCP server: the database's other front door
Every agent read carries a pin, a watermark, its budget and any gap — the protocol is MCP, the difference is the proof. Everything above is one half of ArgonDB. The other half is the claim that the natural client of a database is increasingly not a human with a SQL prompt but an AI agent working on a human's behalf — and that agents deserve a first-class interface, not a wrapper.
What MCP is
MCP — the Model Context Protocol — is an open standard for connecting AI models to tools and data. A server (like ArgonDB) exposes tools; each tool is a name, a natural-language description, and a typed parameter schema. An agent connects, reads the tool list, and decides — from the descriptions — which tool to call, with what arguments, to accomplish its task. Any MCP-speaking agent (Claude, and the growing ecosystem of MCP clients) can use any MCP server without custom integration. Think of it as USB for AI tooling: one connector, everything interoperates.
The subtle consequence: for an agent, the tool descriptions ARE the interface. A human reads your docs once; an agent re-reads the descriptions on every task and routes its behavior by them. That makes description text an engineering artifact — something to version, test, and measure. We do exactly that: on our research bench, an agent's tool selections are scored against a known-best oracle, and editing nothing but the descriptions measurably closed most of the agent's gap to the oracle. Tool quality decides how good the answers can be; tool descriptions decide whether the agent reaches for the right tool at all. A database vendor in the agent era is a tool provider, and both halves are product surface.
Why raw SQL is the wrong interface for an agent
You can point an agent at a plain SQL connection today — one side of every comparison below does exactly that, through a well-regarded standard Postgres MCP wrapper. Watch those transcripts and the structural problems repeat:
- Unbounded answers. A model has a context window — a hard budget
of tokens (text pieces) it can hold.
SELECT * FROM editsreturns whatever it returns; a large result destroys the session. So agents defensivelyLIMITeverything and hope, or burn their window on noise. There is no way to ask for an answer that fits. - No consistency across calls. An agent's investigation is many queries over minutes, against a database changing underneath. Query three and query one can silently describe different worlds — and an agent, unlike a careful analyst, will confidently reason across the mismatch. One statement is consistent in Postgres; a conversation is not.
- No concept of change. "What happened since I last looked?" is the most natural agent question — sessions end, agents come back — and plain SQL simply cannot answer it. Nothing in the database remembers what "last time" was, so agents re-read everything, the most token-expensive possible answer.
- Expensive orientation. A schema dump for a real database is tens of thousands of tokens of catalog noise; exploratory scans compete with production traffic on the primary.
None of these is the model's fault. They're missing database primitives — and ArgonDB happens to have exactly the right internals to supply them: an LSN clock, a changelog, and an isolated read tier.
The contract: budgets, pins, and change digests
- Every response fits a declared budget. Each tool takes
budget_tokens; the server renders the answer, measures it, and trims server-side until it fits, reporting what was cut and how to continue. The full result never leaves the server. For an agent this is a payload SLA: it can afford to ask big questions. - Pins make a conversation consistent.
pin()hands back a token naming an exact LSN; every call carrying it is served from the database as of that instant — the ring and the time-travel machinery above make this cheap, and the pin survives across calls, sessions, even server restarts, for a retention window. This is the LSN clock handed to the client as a consistency contract. - changes() makes staleness a queryable quantity. Give it your old
LSN (or a timestamp, or "now-2h") and it returns a net-effect digest
— per table: created, deleted, updated — with the resolved window
echoed so the answer is exactly reproducible. The signature agent
move is
repin()+changes(since=old): bring my view current and tell me precisely what I missed, at a cost proportional to what actually changed. An agent with a bookmark never has amnesia and never re-reads the world. - Every answer carries provenance. Responses are stamped with the LSN they reflect; statistics name their source; digests over gapped windows say "lower bound"; reads behind the live head disclose it. The agent can always say how it knows — and when the database cannot know, it says that instead. The strongest case is a read replica that has lost contact with the writer and can no longer see the live position: it answers "freshness signal degraded — serving at watermark W, live position unknown" rather than reporting itself fresh. Not knowing is a fact, and facts get reported.
Why a pin is not a held transaction
A pin is a signed coordinate, as Component 3 described. This section is about the road not taken, because the obvious implementation is a trap, and the trap is instructive.
The obvious way to freeze a view is to hold a transaction open. Start a repeatable-read transaction, keep the connection, and every read inside it sees the same instant: real consistency, no new machinery. But a held transaction is a held resource, and it has the cost from Component 1 — while it lives, the database cannot reclaim row versions it might still need, so cleanup stalls behind the oldest open snapshot. The bill is proportional to how long your client thinks.
An AI agent is the worst imaginable holder of such a thing. Its pauses are not a program's microseconds; they are a model's reasoning time. An agent that pins a view, wanders off to call a model, and comes back four minutes later has been holding the cleanup horizon of a production database the entire time — and it will do that on every conversation, concurrently, forever.
So the signed-value design is not cleverness for its own sake; it is the only version of this feature that survives its intended user. ArgonDB keeps the held-transaction implementation in exactly one narrow place: the lake-switched-off shape — a laptop running ArgonDB against a plain Postgres, with no storage engine behind it (the shape the macOS release ships). You can read our opinion of it off our own defaults — that fallback genuinely caps at eight concurrent pins, because each one holds a live transaction open against your database, while the real path caps nothing at all: it merely keeps sixty-four sessions warm, and a pin is valid regardless. Nothing about the feature changes between those two models. Only what a pin physically is.
One limit, stated plainly: if a deployment runs without a stable pin key configured, the server generates a random one at startup and warns. Pins then stop working across a restart. That is an availability regression, never a correctness one — a token that cannot be verified is refused, not trusted.
One SQL surface, and a faster engine behind it
A design question worth answering out loud, because the tempting alternative is everywhere: when an agent sends SQL, which engine runs it? ArgonDB's answer is that there is exactly one SQL surface and it is PostgreSQL — the dialect, the functions, the operators, the catalog, all of it. Offering a second, subtly different dialect for the columnar side would push that difference onto the caller, and an agent is the worst imaginable auditor of a subtle dialect difference: it will not notice, and it will reason confidently over the result.
The columnar engine is still there. It is demoted from a surface to an accelerator: queries whose shape is on a verified list — counts and eligible aggregates over lake tables — run columnar and transparently, and a shape only joins that list once it passes a differential test, the same query down both paths with canonically identical results. Anything unproven takes the Postgres path. You get the speed where it has been earned, and never a different answer for having got it. The accelerator is reached through the agent tools, and the envelope belongs to the accelerator rather than to any one door, so the standard is the same wherever it answers: one list of proven shapes, one standard of proof, and no faster answer that is allowed to be a different one.
That list has a shape, and it is better to say what it is than to let you discover it. Questions that summarize accelerate; questions that hand you back raw rows are answered by Postgres — a boundary drawn on purpose, not an unfinished edge. Counts, sums, averages, grouped rollups — the queries that read a lot and return a little — are what the columnar side is for and what it is proven on. Ask instead for a page of individual rows and the request goes to Postgres, which was always excellent at exactly that.
The reason is the promise, not the effort. The accelerator's entire contract is that you cannot tell it ran, and the way that is established is brutally simple: run the query down both engines and compare the answers, character for character, before the shape is ever allowed on the list. Aggregates make that easy to hold — two engines summing the same numbers agree. Raw values are where engines quietly differ without being wrong: the same date, the same number, printed in a slightly different style. A wide envelope nobody has proven is a promise that your query was fast and correct where only the first half was ever checked, and you would learn about the second half from an answer that looked fine. That is the worst possible failure for a database an agent is reasoning over, so the envelope stays narrow, stays proven, and grows one shape at a time as each one earns its place. Where we publish performance numbers, we publish the boundary with them.
The fifteen tools
An agent works through the fifteen tools of ArgonDB's analyst surface — thirteen in four families, plus two compatibility aliases:
- Orient —
schema_overview(a ranked, budgeted map of the schema: cards grouped by relationships, not a catalog dump),describe(column-level detail),profile(statistics over millions of rows, answered from the lake tier's stored statistics — the OLTP primary never feels it),sample(a peek at real rows). - Answer exactly —
query(SQL against the unified read path, budget-bounded, pinnable to an LSN). - Time —
pin/repin(the consistency tokens above),changes(the net-effect digest),list_branches(databases have branches here; a pin names its branch). - Text & structure — the four tools from the search section:
search(BM25 ranking with structural scoping, every hit carrying table, key, path and the index's LSN),outline,readandexpand.
Two compatibility aliases (execute_sql, explain_query) mirror the
naming of the standard Postgres MCP servers, so an agent bred on those
finds familiar handles.
Three surfaces, because trust is not one thing
Those fifteen tools are one surface, and the full product has three. This is the answer to a question every operator eventually has to ask — "what is this agent allowed to do to my database?" — and the usual answer is bad: hand it every tool and curate a list of the ones it must not call, which is a promise enforced by hope.
ArgonDB splits the tools across three separate mounts, each with its own credential and its own exposure default, so the question is answered at connect time by which mounts you plugged in:
- The analyst surface — the one an agent normally holds, and the one to hand an agent by default. Its philosophy is incapable of harm by construction: it is read-only architecturally, and the mutating tools are not merely forbidden here, they are absent. There is no argument an agent can construct, no prompt injection it can swallow, that turns a tool it does not have into a write.
- The diagnostic surface — tells the truth about the system, changes nothing. Privileged in what it can see (live health, running workload, the internals an operator would look at) and incapable of changing any of it: explain a query, recommend an index, report health, report what is quarantined. This is the surface an AI operator triages with.
- The admin surface — does things, carefully. Every mutation lives here and nowhere else: publishing and unpublishing tables, retention changes, triggering maintenance, actually applying a recommended index. It defaults to loopback, defaults to dry-run with an explicit execute flag, requires an explicit confirmation parameter on anything destructive after showing what will be destroyed, and logs full arguments and results.
The line between the first two and the third is the line between understanding the data and operating the database, and it shows up identically in the tool list, the credentials, and where each surface is deployed. Composing an agent's capabilities becomes a deployment decision you can reason about once, instead of an audit you have to repeat every time the tool list changes.
That last part is worth making concrete, because it is where this section meets the three planes from ArgonDB Features. In the production shape the two splits line up.
On a single box the three are three mounts on one process, and the tool-list and credential halves of the line are exactly as real.
Running ArgonDB yourself
Three shapes, one system, and no orchestration invented by us. Deployable
ArgonDB is a single native binary: argondb serve starts Postgres, the
storage layer, the lake plane with a bundled catalog and object store,
and the agent endpoint — laptop to single server, with the lake sitting
on your disk as ordinary files, and the option to point it at your own
storage instead. docker compose is the reference environment: the
full anatomy — three safekeepers, an object store, a catalog — in one
command, and it is where every acceptance gate runs. Kubernetes is
the production shape — the reference architecture under validation — and
what it is for is the separation: the three
planes stop being three processes under one supervisor and become three
workloads — a writer, a maintenance worker, and a horizontally scaled
pool of read replicas — that schedule, fail and grow independently of one
another. The packaging is an ordinary Helm chart with a preset per cloud,
not a control plane of our own invention. The replica pool is the part
that matters most, and it is the payoff of a design decision made much
earlier on this page: a pin is a value rather than a session, so
replicas can be added or removed without telling anything else, and any
replica holding the key can honour a pin any other one issued. The box
and compose stay all-in-one.
The agent endpoint is present in every one of them — that is a deliberate rule rather than a packaging accident, because an interface that only exists in the expensive tier is not really the product's API. It even works with the lake switched off: turn the lake tier off on a laptop and the same tools answer straight out of Postgres in read-only transactions, with the lake-only ones explaining what enabling the lake would give you. That is the configuration to run beside a local model.
Using it as an analytics database only
A question that arrives as soon as people see the two halves: what if you only want the analytical one? No application to serve, no transactional traffic — just batches of data arriving, and questions asked of them afterwards. That works, and the shape it takes is worth spelling out, because one part of it surprises people: the data still goes in through Postgres. Reading is where the two halves come apart; writing never does.
Why the analytical half cannot be written to — and why that is a feature. One write path in, two synchronized truths out is a constraint as much as a slogan. The lake tables are ArgonDB's to write, and the analyst tools an agent is handed are incapable of harm by construction: read-only architecturally, with the mutating tools absent rather than merely forbidden. Anything that wrote the lake directly would be a second writer racing the committer, and the watermark — the promise that "this Iceberg snapshot equals Postgres as of LSN W" — would quietly stop being true. One write path is what makes one source of truth possible: the Postgres tables are the truth, the lake is derived from them, and every answer discloses which instant it was derived at. A single door in is not a missing feature; it is the reason the two shapes can be guaranteed to agree.
Foreign engines are worth being exact about here, because the tables are open and nothing in the Iceberg format physically stops a writer. Writing your own tables into the same catalog, beside ArgonDB's, is expected and fine — it is your catalog and your storage. Writing into a table ArgonDB manages is out of contract: our committer is that table's single writer, and a foreign commit would succeed with no error at the moment it happened while breaking the machinery that depends on ArgonDB having seen every change — the watermark, the atomic multi-table cuts, and time travel over the changelog. Read our tables with anything; write your own beside them.
What an analytics-only deployment looks like. Load through Postgres, serve from the lake, and the loading door turns out to be the good part:
- The door is a typed schema with constraints. A bad batch is rejected at load time by a type, a NOT NULL or a foreign key, rather than discovered by an analyst three weeks later. Lake-first ingestion has no such door: whatever the writer appends becomes the table.
- A batch lands completely or not at all. Load inside a transaction and readers never see a half-loaded batch — and because a Postgres transaction becomes one atomic lake commit, a load touching twelve tables reaches the lake as one cut across all twelve.
- You can rehearse a load on a branch. A branch is metadata: create one, run the load against it, query the result, and throw it away if the data is wrong — all without the real tables ever having seen it. (There is no merge to perform afterwards; you re-run the load for real once it looks right.)
- Reads run around the clock off the lake side. Agents through the analyst MCP surface, SQL through the read plane, and foreign engines straight against the Iceberg tables — none of it touching the transactional machine.
- History comes along without being asked for. The changelog, time travel and pins are the same machinery described earlier; an analytics-only deployment gets them for the same one flag per table.
What it costs, and where that points. In this shape the transactional half stays resident around the clock, including through the long quiet stretches between batches — a Postgres compute and a tail waiting for a write that arrives once an hour, or once a night. The architecture points past that, and it points there for a reason you have already met: a compute is stateless, and the tail's position in the log is durable, so neither of them is holding anything that would be lost by standing down while the log is quiet and coming back on the next write. Identity lives in the storage, never in a running process — and idle economics is simply the next thing that property buys.
Standing a write plane down would be a deliberate trade rather than a free win, though, which is why it belongs in a mode someone turns on knowingly rather than in a default. Both sides of that trade are worth understanding before anyone reaches for it:
- Waking up takes time. Seconds to start a Postgres compute, plus catch-up work proportional to how much was written while it slept. For a nightly or hourly batch that is nothing. For a writer that needs transactional write latency it is the wrong trade, and that deployment wants its write plane up.
- Single-row lookups move to the lake path. The fast path for "fetch me this exact row" rides a warm, Postgres-shaped process. With the write plane asleep those lookups take the lake path instead — the sorted-files work described above made that path far better, and it is still not the same path. Keeping one small read-only compute warm is the other side of the trade, and the two sides are what any such mode has to price.
The whole range: a laptop, a server, a cluster
"Running it yourself" above listed the three shapes you would install. This section is about the range they cover — how far down and how far up the same system goes, what stays identical across that distance, and what grows on its own when load arrives. It gets its own section because the usual answer to "we outgrew it" is a migration project, and the design described here exists so that there is not one.
The small end: one binary, analytics off
The smallest useful ArgonDB is a single native binary with the
analytical half switched off. argondb serve starts Postgres, the
storage layer underneath it, and the agent endpoint — no container
runtime, no object store, no lake files filling up an SSD. The agent
tools answer straight out of Postgres inside read-only transactions,
and the few that need the lake say what turning it on would give you
rather than failing. This is the configuration to run beside a model on
the same machine.
That is a claim about a genuinely small machine rather than a marketing floor. A 13-inch MacBook Air with 8 GB of memory runs the whole of it: the box comes up, the agent surface answers against Postgres, consistency pins hold against concurrent writes, read-only enforcement holds, and the whole process group shuts down cleanly again.
Several databases on one machine is a supported arrangement rather than a trick: every listener the box opens has its own port override, so independent boxes run side by side, each with its own data directory, none of them aware of the others.
One server, everything on
Same binary, same command, one setting. Turn the lake tier on and the box additionally supervises the ingest writer, the maintenance worker, a bundled Iceberg catalog, and a small object store that keeps the warehouse as plain files under the data directory. Nothing extra to download, and no cloud account to open in order to have a lakehouse — which is what makes the "any engine reads it" promise above true on a machine sitting under your desk.
If you already own the storage, you keep it. The lake is configured as two independent halves — a catalog and an object store — and either may be yours while the other stays bundled; ArgonDB writes into yours exactly as it writes into its own. The rule around that is deliberately strict: naming any setting of a half declares that you are supplying that half, and a missing one is refused at startup, naming the variable and the remedy, rather than being quietly defaulted to something we invented.
One number governs how large the box gets. It takes a memory budget and divides it across the Postgres compute and the storage caches itself instead of asking you to tune several interacting figures, and below its floor it refuses to start rather than overspend the number it was given.
The evaluation shape, and the production shape
The compose stack is where the anatomy comes apart onto separate
containers — three WAL servers rather than one, an object store as its
own service, the catalog as its own service — while still fitting on one
machine and, through docker compose, one command. Every acceptance
gate in this project runs
against it, which is both the reason to evaluate on it and the reason we
call it a development and evaluation tier rather than something to run a
business on. A single-host production user is better served by the box,
which has fewer moving parts; a multi-host one by Kubernetes.
Kubernetes (the reference architecture under validation) is where the three planes described earlier stop being three processes under one supervisor and become three workloads that schedule, fail and grow independently. It is the shape for a database whose analytical and agent load is the half that grows, because that is the half that comes apart onto its own machines — and nothing else about the system changes when it does.
Any of the big clouds runs the same chart, with a preset per cloud, and the differences between them are small and boring: a storage class, an object-store endpoint, and local idioms for ingress and certificates. That smallness is the design showing through. ArgonDB asks a cloud for two things — object storage and somewhere to run containers — and speaks to the first through the two interfaces the industry actually settled on, S3 and Azure Blob, with the Iceberg REST catalog on top. A cloud is a set of endpoints, not a platform to be ported to, and the chart can bring its own object store and catalog in-cluster rather than requiring the managed ones at all.
Why this is one system and not five
The anatomy never changes; only its placement does.
A laptop is not running a cut-down edition and a cluster is not running a different product, which is why the three-planes section earlier could describe the split once and have it be true everywhere.
Moving up a tier is not a migration. A database's identity lives in the storage its WAL and page layers are written to, and in the lake and its catalog — not in any running process. Growing a deployment therefore means standing the larger topology up pointed at the same storage root and the same catalog, and letting stateless computes attach. Starting from a box, the one preparatory step is to point its storage at object storage instead of local disk, which is the same step you would take to get continuous offsite backup. No dump, no restore, no change feed to re-bootstrap, and the lake with its whole history is already portable files. The reverse direction works the same way: a cluster's database can come back down onto one machine.
"Can I run the laptop version on Kubernetes?" Yes — and the two paragraphs above are the reason, because the question is really "are these the same parts against the same storage contract," and they are. Nothing in the chart assumes a large cloud or a large cluster: a single-node Kubernetes on hardware you own is a first-class target, and the point of the whole range is that the small end is not a different product wearing the same name.
What grows on its own, and what does not
"Auto-scaling" is three different mechanisms wearing one word, so here are the three, separately.
The read plane is the part built to scale out. Replicas hold nothing that has to be kept in step: a consistency pin is a signed value rather than a session, so any replica can honour any pin issued by any other, and adding or removing one requires telling nothing else. In the Kubernetes shape that becomes a pool behind a horizontal autoscaler keyed on CPU, with a floor and a ceiling you set and cloud presets that raise the ceiling. The property underneath is the part worth carrying away: coordination-free scaling was not bolted onto the read tier, it fell out of a decision made much earlier — that the consistency token would be a value rather than a handle.
On a single machine the elasticity is vertical, and one piece of it is automatic. The read-only Postgres computes that serve full-fidelity reads are a pool that sizes itself to the hardware: given plenty of memory they are kept ready, and on a small machine they start cold, spawn on the first read that needs them, and stop themselves again after an idle period. Your explicit choice overrides that heuristic in either direction. It is the mechanism by which the same software is reasonable on a laptop and on a large server without a different build.
What does not scale by itself, mostly on purpose. There is exactly one ingest writer per database — a single ordered log is a single-file job — so the write side grows by being given a bigger machine, not more of them. Transactional read replicas are a different question from analytical ones: they follow the primary's log, so their scaling axis is replication lag rather than query load, which is precisely the coupling the lake side exists to escape. The storage layer has a horizontal axis of its own — the page service can be spread across machines, which is how one very large database stops being one very large machine — and the write side keeps its single-log constraint through all of it. And the direction all of this points is the one the analytics-only section above approaches from the other end: computes that stand down to nothing while a database is idle, and many mostly-idle databases packed onto shared hardware, which is the economics that becomes available once nothing that matters lives in a running process.
The shape of all three answers is the same as the rest of the page. The elastic load — agents and analytics — is the load that scales out, and it scales out because it was separated from the transactional machine by construction rather than by tuning. Where scaling still asks something of you, what it asks is small, and the thing it never asks is that you move your data.
What the one-system approach replaces
Everything above builds the lake as a shape of the database's own log: the same write that commits to Postgres becomes an Iceberg table, with no step in between. The ordinary way to put operational data into a lake is the opposite — a change-data-capture pipeline that reads one system and writes another, assembled from separate products and kept in step by a team. It is worth being concrete about what that assembly costs, because that cost is the argument for collapsing it.
The scenario priced below: a 100M-row operational database — high-performance OLTP, complex application functionality — that also runs substantial, continuous analytics against the same data. Every alternative is priced as a full stack against that scenario: an OLTP database sized to carry it, plus the lakehouse tier that gives it continuous analytics. All AWS-managed; on-premises deployment is out of scope (it would need priced hardware, which none of these sources publish for this comparison).
Three common ways to build a CDC-driven lake next to that database are priced below, each as of August 2026, against the same job: replicate change out of an operational Postgres into Iceberg tables on object storage and keep them fresh, at the analytics volume the scenario implies. Every dollar figure names its pricing basis and its date; every range is labelled an estimate with its arithmetic shown; and none of it is invented — where a number could not be sourced, that is said rather than filled in. People figures assume a New York City market (levels.fyi and Glassdoor filtered to New York, NY) and a loaded-cost multiplier of ~1.3x base salary. One caveat from earlier shapes the whole comparison: ArgonDB is not a CDC tool for a Postgres you already run. The choice it changes is the one you make while building a system — assemble a CDC lake around a database, or run a database that already is one.
The OLTP side is the same for every managed-Postgres alternative, so it is
priced once here: Amazon RDS for PostgreSQL, Multi-AZ DB cluster (one
writer plus two readable standbys, so the "replicas" line item is real,
not aspirational), db.r6g.4xlarge (16 vCPU / 128 GB), with io2
Provisioned IOPS storage sized for a high-performance, complex-functionality
100M-row workload — 500 GB and 10,000 provisioned IOPS (est., no published
sizing guide covers "complex functionality" at this row count, so this is
a working assumption, stated so it can be challenged). Compute: 3 ×
$2.08/hour × 730 hours ≈ $4,555/month (three instances, no cluster
discount — each readable standby bills as a full separate instance).
Storage and IOPS: 500 GB × $0.125/GB-month + 10,000 × $0.10/IOPS-month ≈
$1,063/month. OLTP total: ≈ $5,620/month (est.), carried identically
into the Apache OSS, Databricks and Snowflake rows below. (RDS PostgreSQL
on-demand instance rate and Multi-AZ DB cluster billing model: AWS's own
RDS pricing page renders its tables in JavaScript and could not be read
directly, so the rate is sourced from a pricing aggregator instead — the
same limitation and workaround as the Databricks DBU rate below. io2
storage/IOPS rates likewise sourced from an aggregator that republishes
AWS's published io2 pricing. All read 2026-08-29, us-east-1.)
People figures below use New York City market salaries (levels.fyi and Glassdoor filtered to New York, NY) and a loaded-cost multiplier of 1.3× base salary — inside the widely cited 1.25×–1.4× range from MIT Sloan lecturer Joseph Hadzima's "true cost of an employee" convention, covering payroll taxes and standard benefits, not equipment, recruiting or office space.
| Approach | Platform + infra /mo | OLTP database /mo | People (upkeep) /mo | Setup (one-time) |
|---|---|---|---|---|
| Apache OSS on AWS (MSK, MSK Connect, Managed Flink, Glue ETL for compaction, S3, Glue Data Catalog) | ~$2,195 (est.) | ~$5,620 (est.) | ~$55,710 (est.) | ~$100,000 (est.) |
| Databricks | ~$16,000–$22,000 (est.) | ~$5,620 (est.) | ~$37,730 (est.) | ~$60,000–$120,000 (est.) |
| Snowflake | ~$9,550 (est.) | ~$5,620 (est.) | ~$34,110 (est.) | ~$110,000 (est.) |
| ArgonDB | ~$855 (est.) | included in Platform + infra (same instance) | ~$6,240 (est.) | ~$5,760 (est.) |
The open-source Apache stack
The all-open path wires Debezium into Kafka to capture change, lands it in Apache Iceberg through a Flink or Spark sink, and registers the tables in a managed catalog — every component here is AWS-managed, not self-hosted; on-premises is out of scope for this comparison. The software itself is free — every one of Debezium, Kafka, Flink/Spark and Iceberg is Apache 2.0 — so the bill is managed infrastructure and people, not licences.
Sized for the scenario's higher throughput rather than a small always-on
pipeline: a six-broker managed Kafka on kafka.m5.large is 6 × $0.21 per
broker-hour × 730 hours ≈ $920/month; three Debezium connectors on MSK
Connect units add 3 × $0.11/hour × 730 ≈ $241/month for higher-throughput
CDC fan-out; an eight-KPU Amazon Managed Service for Apache Flink job
(sized for continuous transform load, not the one-worker minimum) is 8 ×
$0.11/KPU-hour × 730 ≈ $642/month; Iceberg files sit in S3 Standard at
$0.023/GB-month, assumed ~5 TB for this scenario (est.) ≈ $118/month; a
periodic AWS Glue ETL job handles the Spark compaction and snapshot-expiry
maintenance this stack needs and today's version of this comparison
omitted — 10 DPU × 2 hours/day × 30 days × $0.44/DPU-hour ≈ $264/month;
and the catalog is AWS Glue Data Catalog rather than a self-hosted Nessie
or Polaris host — the first 1M objects and 1M requests per month are free,
and this scenario's table count and query volume land only modestly past
that, ≈ $10/month (est.). Platform + infra total: ≈ $2,195/month
(est.) (MSK, MSK Connect, Managed Flink and S3 rates: AWS on-demand,
us-east-1, read 2026-08-23; Glue ETL and Glue Data Catalog rates: AWS
on-demand, us-east-1, read 2026-08-29, fetched directly from
aws.amazon.com/glue/pricing, which renders as static text). A managed
Kafka from Confluent Cloud instead of MSK bills by elastic unit — a
Standard cluster starts near $385/month, with capacity at $0.75 per
eCKU-hour, storage at $0.08/GB-month and networking at $0.035–$0.050/GB
(confluent.io/pricing, read 2026-08-23); analyst breakdowns put a typical
production Standard cluster higher, around $1,000–$3,000/month.
The people are the real cost, and the roles are specific: a Kafka/streaming engineer to own the brokers, connectors and topic design (NYC senior-engineer-with-Kafka-skills base $152K–$180K, Payscale, read 2026-08-29 — midpoint $166K used below); a Spark or Flink engineer for the streaming transforms and the Iceberg sink (NYC data engineer median $170K, levels.fyi, read 2026-08-29); and a data-platform SRE to keep six moving systems alive (NYC SRE median $178,250, levels.fyi via a secondary aggregator, read 2026-08-29). Three full-time engineers, one per role — the representative point in the stack's 2–4 FTE range — loaded at 1.3×: ($166K + $170K + $178,250) × 1.3 ÷ 12 ≈ $55,710/month (est.). No vendor sells a packaged professional-services price for assembling five independent open-source projects, so setup here is internal/contracted engineering hours at the loaded rate above rather than a vendor quote: three engineers × 8 weeks × 40 hours × a ≈$107/hour blended loaded rate ≈ $100,000 (est.), standing up connectors, a Kafka cluster, an Iceberg sink, a catalog and the streaming jobs. The upkeep never ends — Kafka operations, connector schema-evolution handling, Iceberg table maintenance such as compaction and snapshot expiry, and version compatibility across all five components — which is what the People and Platform + infra lines above carry every month, on top of the one-time build.
Databricks
Databricks bills in DBUs — a normalized unit of compute, charged per second, pay-as-you-go. Continuous CDC uses Lakeflow Connect to ingest from Postgres and Lakeflow Declarative Pipelines (formerly Delta Live Tables) to transform, and the transform work needs at least the Pro tier. List rates on AWS, Premium tier, are Jobs Compute at $0.15/DBU and Declarative Pipelines at $0.20/DBU Core, $0.25/DBU Pro and $0.36/DBU Advanced; ingestion is billed in DBUs rather than per record, with the first 100 DBUs per workspace per day free. (These $/DBU figures are the published list prices as republished by Flexera's Databricks pricing guide, dated 2026-08-23; Databricks' own pricing page renders its tables in JavaScript and could not be read directly — a limitation confirmed here rather than worked around.)
At this scenario's scale — a 100M-row source feeding substantial continuous analytics, not a single small pipeline — the right comparison is the broader mid-size, multi-workload deployment a third-party breakdown put at $16,000–$22,000/month (est.) (DBU software fee plus classic-compute EC2, which routinely runs 50–70% of total Databricks spend, plus storage; dawiso.com, read 2026-08-23). CDC features additionally require the Premium tier plus Unity Catalog and serverless, and committed-use discounts apply to DBUs, not to the cloud infrastructure underneath them.
For people, a Databricks build at this scale needs a Databricks/Spark data engineer for the pipelines and schema evolution (NYC data engineer median $170K, levels.fyi, read 2026-08-29) and a platform engineer for Unity Catalog governance, cluster and cost management (no NYC-specific salary source exists for that exact title; the NYC SRE median of $178,250 above is used as a comparable platform-engineering role). Two full-time engineers, one per role, loaded at 1.3×: ($170K + $178,250) × 1.3 ÷ 12 ≈ $37,730/month (est.). Greenfield Databricks builds at this scale are routinely delivered through Databricks Professional Services or a partner SI rather than built from scratch: a mid-size migration or integration engagement runs $60,000–$120,000 (est.) as a one-time design/build cost, ahead of the FTE run-rate above (Multishoring Databricks professional-services cost guide, read 2026-08-29 — the same source notes $200–$350/hour as the underlying consulting rate for smaller fixed-scope work). The continuous work after that is pipeline tuning, serverless and cluster cost management, and handling schema drift from the source.
Snowflake
Snowflake bills in credits: a virtual warehouse burns credits per second while it runs and nothing while it is suspended. On AWS US East, on-demand credit prices are $2.00 for Standard, $3.00 for Enterprise and $4.00 for Business Critical, and storage is $23.00 per TB per month. CDC ingest is cheap in this model — Snowpipe and Snowpipe Streaming cost 0.0037 credits per GB, serverless Tasks cost 0.9 credits per compute-hour, and Openflow, the NiFi-based CDC connector, costs 0.0225 credits per vCPU-hour in the bring-your-own-cloud form (plus your own EC2). (All Snowflake rates from the Snowflake Service Consumption Table, effective 2026-08-18, AWS US East on-demand.)
The cost is the transform warehouse, and at this scenario's scale — 100M rows plus substantial, continuous analytics, not a batch job a few hours a day — the right size is Medium, run around the clock rather than suspended most of the day: Enterprise edition, Medium warehouse (4 credits/hour) × 730 hours × $3.00/credit ≈ $8,760/month. Ingest scales with it: Snowpipe Streaming at ~500 GB of change per day (est., scaled from the prior 200 GB/day estimate for this larger workload) ≈ $167/month; an Openflow connector sized to 8 vCPU continuous ≈ $394/month; storage at ~10 TB (est.) ≈ $230/month. Platform + infra total: ≈ $9,550/month (est.) (credit and storage rates sourced as above, read 2026-08-29; the monthly figures are estimates from that arithmetic). Credit runaway still lives in the warehouse line: the same Medium warehouse with disciplined auto-suspend during genuinely idle windows would cost less, so warehouse sizing and auto-suspend discipline remain the recurring cost-control job even at this scale.
The roles are a Snowflake data engineer to own ingest and the incremental models with Streams and Tasks or Dynamic Tables (NYC average $141,914, ZipRecruiter, read 2026-08-29), an analytics engineer for the transform models (NYC average $159,105, Glassdoor, read 2026-08-29), and a fractional cost and governance owner watching credit burn (using the NYC SRE median $178,250 above as the comparable rate). At roughly 2.0 FTE total (0.75, 0.75 and 0.5) — the representative point in this stack's 1.5–2 FTE range — loaded at 1.3×: ($141,914 × 0.75 + $159,105 × 0.75 + $178,250 × 0.5) × 1.3 ÷ 12 ≈ $34,110/month (est.). Snowflake does not publish a professional-services price list any more than Databricks does; no partner case study surfaced a dollar figure for a greenfield build either, so the one-time setup below uses the same consulting rate basis cited for Databricks ($200–$350/hour, midpoint ~$275/hour) applied to an estimated two engineers × 5 weeks × 40 hours (400 hours) to stand up Openflow or Snowpipe from Postgres and then Streams and Tasks or Dynamic Tables for the transforms: 400 × $275 ≈ $110,000 (est.). The continuous work after that is warehouse sizing, credit control and schema evolution.
What ArgonDB does instead
Every line item in the three sections above exists to move data between systems and to keep the copy in step. ArgonDB has none of them, because there is no copy and no second system: the lake is written by the storage layer that already wrote the database, from the same log, at the same commit boundaries. There is no CDC product to licence, no streaming cluster to run, no second compute engine to keep fed, no separate catalog to host, and no metered ingest for freshness — freshness is a property of the write path, not a job that bills. The software is self-hostable from a laptop upward; its licence and price are not yet set, so this row counts infrastructure and people only.
Priced candidly rather than at $0: the box is a cloud instance sized for
this scenario, r6i.4xlarge (16 vCPU / 128 GB — a similar class to the
20-core / 91 GB unit ArgonDB is measured on), on-demand at $1.008/hour ×
730 hours ≈ $736/month (instances.vantage.sh, read 2026-08-29; AWS's own
EC2 on-demand pricing page renders its table in JavaScript and could not
be read directly, the same limitation as the RDS and Databricks rates
above — a 1-year reserved instance at $0.667/hour ≈ $487/month is
available as the same lever every other row could also pull and none of
them price in). Object storage for the lake, same S3 Standard rate and ~5
TB assumption as the Apache OSS row ≈ $118/month. Platform + infra
total: ≈ $855/month (est.). There is no separate OLTP line — this is the
same instance, not a second one — which is the structural saving this
whole section exists to price.
People are not zero either: a DBA/operator fraction to run the database — no pipeline roles, because there is no pipeline — at 0.5 FTE, NYC general DBA average $115,271 (Glassdoor, New York City, NY, read 2026-08-29, noting SQL-specialist DBA titles in the same NYC dataset run $132K–$153K), loaded at 1.3×: $115,271 × 0.5 × 1.3 ÷ 12 ≈ $6,240/month (est.). A support-tier price would belong here if one existed; a grep of this repo's docs turned up none, so the accurate statement is that support pricing is not yet published, not a number. Setup is install-plus-load hours at the DBA's loaded rate, not a design/build engagement: roughly 80 hours (two weeks) × $115,271 × 1.3 ÷ 2,080 ≈ $5,760 (est.). No total is shown for any row. This row prices infrastructure and half a DBA; the rows above include engineering roles and 24×7 platform services; and ArgonDB's own engineering, on-call, support and vendor risk are not yet priced. Totals return when the rows compare equivalent service levels.
What that trades away, plainly. The three alternatives buy real things ArgonDB does not. Databricks is a full machine-learning and data-science platform with a large surrounding ecosystem; Snowflake offers enormous elastic scale-out SQL, a data marketplace and cross-account sharing; the open-source stack gives maximum flexibility, no vendor lock-in, and the ability to capture change from any source rather than one you host. And the caveat that opened this section still holds: if the requirement is to get an existing external Postgres into a lake, a CDC tool is the right answer and ArgonDB is not one. The saving described here is available only when ArgonDB is the operational database — when the lake can be a shape of its own log instead of a copy of someone else's.
Sources and pricing basis
- Amazon RDS for PostgreSQL on-demand instance pricing (db.r6g.4xlarge) and Multi-AZ DB cluster billing model (each readable standby billed as a full instance) — AWS's own pricing page renders its tables in JavaScript; rate read from an aggregator instead, read 2026-08-29: economize.cloud RDS db.r6g.4xlarge, AWS Database Blog: Multi-AZ with two readable standbys
- Amazon RDS io2 Provisioned IOPS storage pricing (per GB-month, per provisioned-IOPS-month) — republished from AWS's published rates, read 2026-08-29: factualminds.com AWS IOPS cost calculator
- Amazon MSK pricing — broker, storage and MSK Connect rates, us-east-1, read 2026-08-23: aws.amazon.com/msk/pricing
- Amazon Managed Service for Apache Flink pricing — KPU-hour rate and the per-application orchestration KPU, read 2026-08-23: aws.amazon.com/managed-service-apache-flink/pricing
- Amazon S3 pricing — S3 Standard per-GB-month, first 50 TB, 2026: aws.amazon.com/s3/pricing
- AWS Glue pricing — standard ETL DPU-hour rate and Data Catalog free-tier/overage rates, fetched directly from the live page (renders as static text), read 2026-08-29: aws.amazon.com/glue/pricing
- Confluent Cloud pricing — Standard cluster starting price and billing dimensions (eCKU-hour, storage, networking), read 2026-08-23: confluent.io/pricing
- Confluent Cloud typical production monthly range for a Standard cluster ($1,000–$3,000) — analyst breakdown, 2026: cloudzero.com Confluent Cloud pricing
- EC2 r6i.4xlarge on-demand and 1-year reserved pricing, us-east-1 — AWS's own EC2 pricing page renders its table in JavaScript; read from an aggregator instead, read 2026-08-29: instances.vantage.sh/aws/ec2/r6i.4xlarge
- Databricks DBU list prices (republished), dated 2026-08-23: flexera.com Databricks pricing guide
- Databricks Lakeflow Connect billing basis (DBU, not per record; classic-compute gateway for database sources): docs.databricks.com Lakeflow Connect FAQ
- Databricks broader mid-size multi-workload monthly estimate ($16K–$22K) — analyst breakdown, read 2026-08-23: dawiso.com Databricks pricing
- Databricks professional-services engagement cost range ($60K–$120K mid-size migration/integration) and the $200–$350/hour consulting-rate basis, used for both Databricks and Snowflake setup estimates, read 2026-08-29: multishoring.com Databricks consulting cost guide
- Snowflake Service Consumption Table — credit prices by edition, storage, and serverless feature rates, effective 2026-08-18: snowflake.com CreditConsumptionTable.pdf
- Snowflake Dynamic Tables cost — refreshes bill to the assigned warehouse: docs.snowflake.com dynamic-tables-cost
- Loaded-cost multiplier convention (1.25×–1.4× base salary), attributed to MIT Sloan lecturer Joseph Hadzima, read 2026-08-29: beebole.com true cost of an employee
- Kafka/streaming engineer (senior software engineer, Apache Kafka skills), New York, NY, 2026: payscale.com mid-career senior software engineer, Apache Kafka, New York NY
- Data engineer (Spark/Flink and Databricks roles), New York City, NY, 2026 (median): levels.fyi Data Engineer, New York City
- Site-reliability / data-platform engineer, New York, NY, 2026 (median, levels.fyi data via aggregator): cvcraft.roynex.com SRE salary, New York
- Snowflake data engineer, New York, NY, 2026 (average): ziprecruiter.com Data Engineer Snowflake salary, New York NY
- Analytics engineer, New York, NY, 2026 (average): glassdoor.com Analytics Engineer salary, New York NY
- Database administrator, New York City, NY, 2026 (average, general and SQL-specialist bands): glassdoor.com Database Administrator salary, New York City NY, glassdoor.com SQL Server Database Administrator salary, New York City NY
Releases, updates, and the knowledge that travels with them
The plain version first: a release is a set of files you keep. You take it, you pin the version, and it runs on your own machines with the network unplugged. There is no account to keep current, no licence server to phone, and nothing in it that stops working because something of ours stopped working. Everything below is a consequence of that one sentence.
A release is three payloads rather than one, and separating them is the whole trick, because people usually mean only the first:
- The database. ArgonDB itself — the binary, the images, the chart. What this page has been about.
- The working system around it. How a team actually gets work done on top of a database: what is in flight, which machine runs it, what has to be proven before a change lands. We built one in order to build ArgonDB, and it travels as its own payload. The next section is its shape.
- The knowledge. How to use the thing — what each agent tool is for, the query patterns that work, the operating habits, the traps. Most products leave this payload out and expect a web search to cover it. For a database whose main client is an agent, that is leaving out the manual for the reader who needs it most.
Everything is pushed by default. All three arrive as versioned releases you pin and apply on your own clock — never applied for you, never fetched behind your back, and complete on their own. An agent working against ArgonDB inside a building with no outbound network has the database, the working system, and the whole body of knowledge sitting on local disk. That is the floor, and it is deliberately a high one, because the two things it protects are the two easiest to lose by accident: nothing strands you, and nothing of yours leaves.
Pulling is an enhancement you switch on. A release-notes feed that says a new version exists and what changed in it. Knowledge updates between releases. Live questions to us, for whatever the shipped knowledge does not cover. Each is useful and each is optional, and the rule they all obey is that switching them off costs you freshness and never function. A product built on ArgonDB that breaks when our endpoint is down would make exit costs nothing untrue, so a hard dependency on us is not something we are willing to sell.
The expert runs on your hardware. The interesting half of the knowledge payload is not that it is a folder of documents but that it is served: the same agent endpoint described earlier answers questions about ArgonDB itself, seeded from the shipped knowledge and re-seeded whenever you take a newer version. So the agent building your application can ask how something works, and both the question and the code it is asking about stay on your side of the wall. We update the expert by shipping it, not by being called.
And a release carries its own proof. The checks that decide whether ArgonDB works travel inside it and are meant to be run there, on your machines, against your version — so "it works" is something you re-prove locally rather than something we assert. That is the discipline described below in "How we test what we build", pointed outward.
How an update reaches you
The plain version: we publish, you take. Nothing of ours reaches into your machines. Your side fetches a new version when it decides to, tries it against your own project, and keeps it only if that passes.
Both ends of that are run by agents, which is what makes it routine rather than a chore. Ours prepares and publishes the release. Yours receives it, tests it, merges it, and books the restart. People set the policy — how eager to be, and what needs a hand on the wheel. The agents do the moving.
Our side, when a change is ready. No step waits on someone to press a button, and one commit produces every name attached to it — the version tag, the file, the container image, the version your project pins — so those cannot drift apart and quietly mean different builds.
Your side, on your own clock. Your project keeps ours as a source it can fetch from, and a check on its own schedule notices when a new version appears and writes it down as work to do. Your coordinator picks that work up the way it picks up anything else: while the project is busy with its own priorities, the update waits its turn. Then four steps, all on your hardware:
- Fetch it. Nothing about your project has changed yet.
- Rebuild it as yours. The new version is regenerated through your project's own settings — the same ones that set it up originally — onto a branch.
- Test it against your project. Your checks, your data, your code, not ours.
- Merge when that is green. Anything you customized that we also changed arrives as a conflict for your side to resolve, never as a silent overwrite and never as a silently dropped update.
Merging files is not restarting a database. Those are deliberately two events, so the risky one gets its own schedule — and the same window cannot be started twice.
Each release states what it does to your data, in a form an agent reads rather than a paragraph it has to interpret:
- unchanged — apply it freely; going back is putting the old version back.
- forward-migrating — take the backup step first; going back needs that backup.
- breaking — never scheduled without a person.
A value the receiving agent does not recognize counts as breaking. An unreadable contract is never read as permission.
How eager to be is a setting. Follow every change as it lands, or follow named versions only. The project we run ourselves on ArgonDB takes every change; a setup that would rather batch takes versions. It is one line of configuration either way, and it is the only knob this whole path needs.
Building on it: local, CI, fleet
"Running it yourself" and "The whole range" above were about where the database runs. This is a different three: how the people building on it work. A team gets a way of working along with the database, and it comes in three setups.
Pick the one that matches you, and each adds to the one below it.
- local — one developer, one machine. One command brings up a throwaway ArgonDB with sample data and an example query; one command removes it and everything it wrote. Add the working doctrine — how a task gets written down, how a change gets checked — and that is the whole setup. It depends on nothing remote.
- CI (continuous integration — the industry's name for checks that run by themselves on every change) — local, plus what a project needs to be reproducible for more than one person on more than one clock: exact pinned versions rather than "the latest one", schema changes that are versioned and re-appliable, test helpers that stand a disposable database up and tear it down around a test, runnable client examples, and a written path from one pinned version to the next.
- fleet — CI, plus the machinery for sharing hardware between people: a coordinator that hands work out and keeps a durable record of everything in flight, arbitration so that two jobs sharing a machine never take the same slot of its capacity or the block of ports that comes with that slot, and a watcher that checks that record against reality rather than trusting what the work reports about itself.
Each setup is a strict superset of the one below it. Turning on CI does not replace the local floor, it adds to it; turning on fleet does not replace CI. There is one core to version and one body of doctrine, which is the point — two editions would be two things to keep in step, and the second one always drifts.
A setup describes one developer's environment, not the project's. That is the part worth carrying away, because it is what makes the ordinary case work. Two developers can share a big machine through a coordinator while a third works from a laptop on a plane, and all three are on the same project: same repository, same record of work in flight, same pinned ArgonDB version. "The project is on fleet" is not a sentence anyone says. What everybody shares is shared regardless of setup; what differs is where each person's work physically executes.
One laptop is the smallest fleet rather than a cut-down one. The laptop developer is a full participant that happens to run its work on itself, described by exactly the same entry as a shared build server — what it can be asked to do, how it is reached, how much it can do at once. A project's fleet is whatever its list of machines says it is: two big servers, or none, or three laptops. Nothing about the shape is assumed, which is the same property the database half of this page keeps claiming, applied to the people.
How we test what we build
The plain version: nobody's word counts for anything. Not ours, not a reviewer's, and not the word of whichever agent wrote the code. A claim is something you measure, and until it has been measured it is a guess in a confident voice. Almost everything below is an operational consequence of taking that seriously — which we have to, because this system is substantially AI-written, and the response to that is not reassurance, it is measurement.
A test's job is to refute, not to confirm. It is easy to write the test that shows the thing working, and that test tells you almost nothing. The useful one is the test that would embarrass us: the hostile input, the combination nobody would type on purpose, the ugly case sitting exactly on a boundary. When a rule says "strictly older", something checks that the equal case is not caught. When a feature has parts that can each be empty, every combination of empty gets tried rather than the two anyone thought of.
A pass counts only once the test has proven it can fail. A green light with nothing behind it is the most expensive result in software, because it buys confidence and delivers none. So a check is shown a case it must reject before its acceptance means anything, and a check nobody can make fail is treated as broken rather than as good news.
We test the way a stranger would use it, not the way we do. Our own usage is one narrow path — well-formed data, one writer, a forgiving consumer — and the bugs that surprised us have all lived outside it. So tests are written from the specification and from what a foreign caller might do, and anything we write into an open format is read back by somebody else's engine, because a round trip through your own reader hides precisely the bugs an open format exists to prevent.
Whoever did the work does not grade it. Work arrives as a set of claims, and those claims are re-run independently, on the combined result, before any of it lands. This is not suspicion; it is the only arrangement in which handing work out at scale is safe at all. It applies to a machine being described as idle exactly as much as to a test result: the way to know is to go and look.
Anything redone becomes a permanent rule. When something has to be fixed twice, the interesting question is not what broke but how the mistake got through — and the answer becomes a written rule the next piece of work has to obey. It is why the rules read oddly specific: each one is a scar. Waiting for something now always carries both a deadline and a check that whatever is being waited on is still alive, because once it wasn't, and the wait ran out the whole clock.
All of it is the same shape as the referee described earlier on this page, where a real Postgres grades ArgonDB's answers. That referee is one instance of the general rule rather than a special case: correctness against an oracle sharing none of our code, whole-system acceptance runs, the cloud shapes brought up and torn down, performance watched for regressions, security surfaces probed by something playing the attacker, and the working system itself held to the standard it holds the database to. None of that makes a system correct. What it does is make being wrong something you find out about.
Where this is going
Every direction this project takes is the same direction, which is the most useful thing to know about it. One write path in, two synchronized truths out is a constraint before it is a slogan: anything that comes later has to fit through it, and that is what keeps the two shapes of your data from ever needing to be reconciled. What grows is the amount of work the database does because it already read the log — the change feed, the history table, the search index, the statistics, the file hygiene, and the next several things a data team currently assembles by hand around a database that cannot see its own past.
The lake half deepens along the open formats rather than away from them. The Iceberg specification keeps growing — richer row lineage, better deletes — and ArgonDB follows it, because the entire point of writing your data into open tables is that the tables outlive our opinions and our company. That direction has a guarantee attached rather than a promise: nothing you write is stranded by it, because newer readers open older tables, and the names ArgonDB writes into your lake never change.
The range converges too. A laptop, a server and a cluster already run the same parts against the same storage contract; what is still a human decision at the large end — a replica floor and ceiling, a memory budget, a retention window — is exactly the class of decision the system is being pointed at making for itself, because every one of them is a measurement it is already taking. The destination is plain: one write plane, one consistency clock, no size at which you change products, and no moment at which anyone has to move the data.
And the agent half is the least settled idea in the industry rather than in this product. The claim that the natural client of a database is increasingly a program reasoning on somebody's behalf has consequences still being drawn out — answers that carry their own provenance, budgets the caller declares instead of discovering, a coordinate that makes a whole conversation consistent, an envelope of query shapes that widens only by proof. Those are database primitives, not chat features, and building them out is what the phrase speaks agent in the title is for.