Engineering · 2026-05-14 · By RedDB team · 7 min read
The tradeoffs we made to fit four engines in one
Document, vector, KV and blob in the same transaction is not free. Here's the per-modality tuning surface we cut, the plugin story we sacrificed, and the cold-blob latency tail we accepted.
Picking a storage model is a constraint on every future feature. We picked four. A document store, a vector index, a KV cache and a blob keeper — all riding the same write-ahead log, all visible inside the same transaction. The pitch writes itself. The bill is harder.
This post is the bill. If you are evaluating RedDB against a stitched stack (Postgres + pgvector + S3 + Redis) or against a single best-of-breed engine, you should leave knowing exactly which of those alternatives still beats us on your workload. We would rather lose the deal than win it and own the incident.
One log to rule them all
A single WAL is shared by every modality. Document writes, vector inserts, KV updates and blob commits land in the same ordered byte stream and are made durable by the same fsync. That is the whole trick. Cross-model transactions are not a coordination protocol bolted on top of four engines — they are the natural consequence of one engine that already happens to write all four things.
Everything else in this post is a downstream consequence of that one decision. Some of the consequences are good (you only need one backup, one auth surface, one ordered cursor for change capture). Some of them are not. We will start with the not.
Tradeoff 1 — Per-modality tuning surface
A pure vector store like Qdrant or Weaviate exposes every HNSW parameter that matters: graph degree M, build-time candidate list ef_construction, query-time candidate list ef_search, the choice between cosine, dot and L2, scalar vs product quantization, segment-level rebuild scheduling. You can tune one collection for recall-at-99% on a 768-dim text embedding model and a different collection for sub-millisecond response on a 64-dim recsys model, and they coexist happily.
We did not ship that. The vector index in RedDB exposes three knobs:
index_type— HNSW or flat. Flat is for collections small enough that brute force wins.distance— cosine, dot or L2.ef_search— the query-time tradeoff between recall and latency, set per query.
We picked sensible defaults for M (16) and ef_construction (200). They are not configurable per collection. If your workload genuinely benefits from M = 48 (very high-dimensional embeddings where recall collapses at low graph degree), we are leaving recall on the table for you. We made this call on purpose: the support load of explaining HNSW parameters to teams that picked us because they did not want to learn HNSW parameters was the load we wanted to avoid. A team that wants those knobs is a team for whom a dedicated vector store is the right answer.
The same logic applies elsewhere. Blob storage does not let you pick the chunk size. The KV layer does not let you opt out of MVCC for raw byte speed. The document store does not let you choose between B-tree and LSM per collection. These are all real knobs in real specialized engines. We cut them.
Tradeoff 2 — The plugin story we did not ship
Postgres has spoiled the industry. The extension API is the reason pgvector exists at all, and it is the reason a small team can ship a new index type without forking the database. Every serious Postgres engineer has, at some point, written or read a CREATE EXTENSION shim.
We do not have that. RedDB is one binary with the four engines compiled in. You cannot drop in a third-party graph index, a new compression codec, or a domain-specific aggregate function without forking us. We considered building a plugin ABI; we decided not to. Two reasons:
- A plugin ABI is a forever commitment. The Postgres ABI is famously stable and famously load-bearing on the entire ecosystem. We are a young engine. Locking ourselves into a plugin contract today means freezing internal APIs we still want to rearrange.
- Most plugin demand is really feature demand. When we asked teams what they would build as a plugin, the answers were almost always “a feature I wish you shipped.” Building those features in tree, where they participate in the WAL and the transaction model, is strictly better than pushing them outside the trust boundary.
If you are the team that would have written the plugin — your differentiation lives in a custom index, a custom aggregate, a custom rerank — you should keep using a database that lets you build it. We will not be that database in 2026. We might be in 2028. Plan accordingly.
Tradeoff 3 — The cold-blob latency tail
Here is the one that bites in production. The WAL is shared. That means a 50 MB blob commit and a 200-byte KV update are queued behind the same fsync. On a healthy node with NVMe and a tight wal_segment_size, this is fine — the blob is striped, the small writes pipeline, and tail latency stays sane. On a node that has just absorbed a burst of large blob writes, it is not fine.
The numbers we measure on our own staging cluster:
- KV P99 under steady load: 1.8 ms.
- KV P99 during a sustained blob write burst (10 concurrent uploads, 100 MB each): 14 ms.
- KV P99.9 during the same burst: 38 ms.
For most applications, a 14 ms P99 during ingestion bursts is invisible. For a hot path doing 50 KV reads per request, it is a 700 ms request tail you did not expect. We document this. We expose a wal_priority hint that lets you mark blob commits as background, which softens the tail at the cost of slightly delayed blob durability acknowledgement (still durable, just acknowledged after the next group commit instead of with it).
If your workload is “a few hundred KV ops per second and never more than a megabyte of blob per minute,” none of this matters and you should pick us. If your workload is “ingest 4 TB of PDFs a day while serving sub-5ms cache reads on the same cluster,” put the blobs in S3, put the cache in Redis, and use us for the documents and vectors. We are not lying about the shared WAL. The shared WAL is the feature. The shared WAL is also the cost.
Tradeoff 4 — Operational maturity
We are honest about being younger than the alternatives we replace. Postgres has thirty years of operational folklore. S3 has fifteen years of corner-case documentation. Redis has the entire redis-cli ecosystem and a generation of engineers who have debugged it at 3am.
We have:
- A two-year-old engine.
- A small team.
- A growing but not encyclopedic set of postmortems.
- Documented behavior for the failure modes we have seen; less coverage for the ones we have not.
This is not a lasting tradeoff — the maturity gap closes one quarter at a time — but it is a real one today. If your team has zero appetite for being on the early side of an operational learning curve, the four-engine stack you already know is the safer choice. Buying RedDB to escape it is rational only if the seams in that stack are already costing you more than the maturity gap will.
When RedDB is wrong for you
To make this concrete, here is the shortlist. If you recognize yourself in any of these, do not buy us yet:
- You need every HNSW knob. A dedicated vector DB will give you better recall-at-latency on a single embedding workload than we will.
- Your differentiation is a custom index, aggregate or codec. Postgres + an extension is a better home until we ship a plugin ABI.
- You ingest huge blobs while serving low-latency reads on the same cluster. Split the workload. Use object storage for blobs.
- You have a strong on-call team that already loves the four-engine stack. The seams you have memorized cost less than a migration.
- You cannot tolerate a young engine. Wait a year. We will still be here.
What we kept
What we did keep is exactly the thing the bill in the previous post was buying out: cross-model transactions, one ops playbook, one auth surface, one ordered log of every change to every modality. For the teams whose pain lives in the seams rather than in single-engine tuning, that trade was obvious. For the others, the four-engine stack is still the right answer and we will say so on a call.
Honest tradeoffs are the only kind that survive contact with production. These are ours.