OLTP, OLAP and HTAP
Transactions, analytics and both at once. Where each Loams workload sits, and why commits stay off the bucket.
Three words, three workloads:
- OLTP (online transaction processing): many small reads and writes, each a transaction that must commit in milliseconds and never be lost. Orders, accounts, application state.
- OLAP (online analytical processing): few large queries that scan and aggregate millions of rows. Dashboards, evals, usage reports.
- HTAP (hybrid transactional and analytical processing): running analytics on transactional data while it is fresh, without a nightly copy.
Loams covers each with a different product, chosen for that workload, and plans to join them on one copy of the data.
Why we built it this way
The retrieval engine is built on the bucket: it acknowledges a write after an object PUT and a metastore commit, hundreds of milliseconds at the tail. That is fine for ingesting documents and events. It is wrong for a checkout, where a transaction must commit in single-digit milliseconds and read its own row back in the next statement.
So the first decisions were explicit: the engine is not an OLTP database (D2), and object storage as the only source of truth holds for the engine, not for every Loams product (D130). Every serious database on object storage we studied keeps the commit off the bucket: Neon and Databricks Lakebase acknowledge on a quorum of safekeepers, TiDB's cloud architecture persists its Raft log locally first, others acknowledge on a log service. The bucket holds history, branches and archives.
Our opinion: use a real transactional engine for transactions, put its history on the bucket, and bring analytics to the same copy. Do not make the retrieval engine pretend to be Postgres, and do not make your Postgres scan a billion rows.
How it works
OLTP: three products, each for a reason.
- Loams Postgres is a fork of Neon. Computes are stateless Postgres; WAL is acknowledged on a quorum before it reaches the bucket; pages live on the bucket through the pageserver. You get Postgres with branches and scale to zero.
- Loams SQL is MySQL-compatible SQL on a patched TiDB over TiKV, for applications that speak MySQL.
- Loams Live is a reactive document database (transactions, indexes and live queries with a TypeScript SDK) on TiKV.
TiKV is the shared substrate for small transactions and metadata in Loams: Raft-replicated on local disks, with Percolator transactions. A separate WAL service carries Postgres' high-volume byte log, because TiKV pays a timestamp round trip, a Raft write and an apply per append that a byte log does not need (D1057).
OLAP: Loams House. Loams House serves a declared, tested subset of the ClickHouse HTTP interface on chDB (ClickHouse as a library), with lake tables stored as Iceberg. The engine itself still speaks no ClickHouse protocol; the House is a separate service with its own network and sandbox.
Retrieval sits in the middle. The engine ingests continuously, reads fresh data through its tail, and answers search and vector queries over large collections. It is neither a transaction engine nor a warehouse, and it is the reason the other two need a shared log.
HTAP in three phases (D1051, approved direction, planned):
- CDC into Iceberg. Postgres changes flow through logical replication (pgoutput) into a Loams stream, and a House pipe writes them into an Iceberg lake table, committing the consumed offsets in the same Iceberg snapshot as the rows. That makes the path exactly-once. One command is planned to set it up per branch.
- Columnar at the storage layer. The Loams Postgres pageserver already reconstructs whole pages when it writes image layers. For a table that opts in, it also decodes the visible tuples into Parquet and commits an Iceberg snapshot tagged with the WAL position. One copy, no pipeline.
- Fresh analytics. The House and the engine's DataFusion read that Iceberg snapshot as of position L, then merge the decoded WAL tail after L by primary key, and report how fresh the result is. That is the same rule the engine already uses for its own tail, applied to Postgres.
What we give up
- Several products, not one engine. Postgres, MySQL-compatible SQL, Live and the House are different systems with different operational shapes, and TiKV is stateful. One hybrid engine would be simpler to describe. We think it would be worse at each workload.
- HTAP is a direction, not a feature yet. Today you can run the transactional products and the House side by side; joining them on one copy is planned work, phases 2 and 3 after Loams Postgres is generally available.
- Phase 2 costs pageserver work. Writing Parquet with image layers costs CPU and bucket writes, so it is off by default and per table, and its usage is reported.
How other systems do it
- One engine with a hybrid format: CedarDB, SingleStore and OceanBase store data so one engine serves both workloads.
- A synced columnar replica: TiDB's TiFlash replicates as a Raft learner in columnar form; AlloyDB and Spanner add a columnar engine beside the row store.
- Columnar at the storage layer: Databricks announced LTAP, where the pageserver transcodes pages into open columnar files and queries merge recent changes at read time. It is the same idea as phases 2 and 3; Loams' difference is that it is open source, on your bucket, behind an Iceberg REST catalog.
- CDC into a lake with Debezium, Kafka and a lakehouse writer is the common path today, with several systems to run.
Status
Available Loams Postgres, Loams SQL and Loams Live are available in Loams 0.1.0, and Loams House is experimental; check each product page for its limits. The HTAP phases are Planned.
Go deeper
- The bucket is the source of truth: the principle, and its stated exceptions.
- Metadata and TiKV.
- Products: Loams Postgres, Loams SQL, Loams Live, Loams House.
- Design: 58 Data access and HTAP (§6 and §8), 00 Pitch (§7, non-goals), decision log (D2, D130, D1051 to D1057).
- Source:
crates/loams-postgres,crates/loams-tikv,crates/loams-live.