MDDB vs Alternatives
Every number on this page was measured, not estimated, and each one names the command that produces it again. Where a figure depends on your corpus — and most of them do, heavily — the page says so and shows by how much.
The comparisons keep their "when to use the other thing instead" sections. Those are the most useful part: a page that recommends itself for everything is a page nobody believes.
How these numbers were produced
# start a server, then:
make bench-comparison # MDDB's own profile
make bench-comparison BENCH_ARGS="-docs 20000 -words 160"
make bench-comparison-all # cross-database (needs Docker)
| Parameter | Value |
|---|---|
| Measured | 2026-08-23 |
| MDDB | 2.12.0, release build (CGO_ENABLED=0, -ldflags="-s -w") |
| Host | linux/amd64, 32 CPUs, 16 GB RAM |
| Go | 1.27.0 |
| Corpus | generated prose, Zipfian term distribution over an 8 000-word vocabulary |
| Transport | HTTP/JSON, batches of 500 documents |
| Cross-database run | 2026-08-23, same host, containers from test/docker-compose.benchmark.yml |
| Search | 500 timed queries after 20 warm-up queries; percentiles, not averages |
The corpus generator is worth one sentence, because two earlier versions of it produced numbers that were wrong in opposite directions. A fifteen-word vocabulary gave almost no distinct terms, and the database came out ten times its content. Then 250 words drawn uniformly gave the opposite pathology — every document containing nearly every term, which is the densest an inverted index can be — and the database came out twenty-seven times its content. Neither said anything about the engine. Natural language is Zipfian, so the generator is too.
MDDB, measured
Prose documents of ~1.7 KB, full-text indexing on:
| Corpus | Ingest | Search p50 | p95 | p99 | On disk | Server RSS |
|---|---|---|---|---|---|---|
| 1 000 docs | 207 docs/s | 745 µs | 993 µs | — | 79 MiB | 223 MiB |
| 5 000 docs | 562 docs/s | 1.54 ms | 2.74 ms | 3.09 ms | 325 MiB | 274 MiB |
| 20 000 docs | 653 docs/s | 5.90 ms | 7.81 ms | 8.54 ms | 1 175 MiB | 1 030 MiB |
Two things in that table deserve explaining rather than burying.
Ingest looks slow at 1 000 documents and faster at 20 000. That is a fixed start-up cost — roughly four seconds of bucket creation and file growth — being amortised. The marginal rate is what the 20 000-document row shows.
The database is 35× the size of its content, and RSS grows with it. That is
almost entirely the full-text index, and you can watch it disappear. The same
5 000 documents, ingested with skipFts:
| Same corpus, 5 000 docs | Ingest | On disk |
|---|---|---|
| Full-text index on | 562 docs/s | 325 MiB |
"options": {"skipFts": true} |
13 883 docs/s | 16 MiB |
The index costs 25× the write throughput and 20× the storage. That is the
honest price of instant full-text search over every document, and it is a
choice you get to make per ingest — profile: "fast" names the same trade-off.
If your workload is write-heavy and searched rarely, turn it off and reindex on
demand.
It also explains a number this page used to carry. The previous version claimed 29 810 docs/s with no methodology attached. Nothing in this repository reproduces that against an indexed corpus of real prose; it is the shape of a figure measured with tiny documents and no meaningful index work. It has been removed rather than adjusted.
Binary and image: the release binary is 27.7 MB (CGO_ENABLED=0 go build -ldflags="-s -w"; a plain go build is ~40 MB, because it keeps the symbol
table). The Docker image is ~33 MB.
Against other databases, measured
Docker was unavailable when this page was first rewritten, so it carried no cross-database numbers rather than invented ones. Here they are.
The workload: 3 000 documents, inserted one at a time over each
system's normal protocol, rotating three Lorem Ipsum sizes (124 B, 707 B,
1876 B). test/compare-all-databases.sh runs it; every client lives beside it
in test/.
| System | Version | Inserts/s | Median insert |
|---|---|---|---|
| MongoDB | 8 | 2 358 | 414 µs |
| PostgreSQL | 17 | 1 368 | 712 µs |
| MDDB, no full-text index | 2.12.0 | 502 | 1 798 µs |
| MySQL | 9.1 | 454 | 2 131 µs |
| CouchDB | 3 | 202 | 4 880 µs |
| MDDB, full-text index on | 2.12.0 | 104 | 9 883 µs |
Two rows for MDDB, because the comparison is otherwise dishonest in both directions.
None of the other systems is building a full-text index in that table. A
PostgreSQL row with a GIN tsvector index would not be at 1 368 either. MDDB
indexes every document for search on write by default, and that costs it a
factor of five: 502 inserts/s becomes 104. Compare the row that matches what
you are asking the other system to do.
Single inserts are the wrong way to load a corpus into MDDB, and the table uses them only because it is the one shape every system supports identically. Through the batch API the same engine does 13 883 docs/s without the index and 562 with it — the figures in the section above. If you are loading a corpus and comparing to a bulk-load path elsewhere, those are the numbers.
Where MDDB lands honestly: an embedded single-file store doing an fsync per write sits between PostgreSQL and MySQL on raw insert rate, and last by a wide margin once you ask it to also make every document searchable. What it buys is that the search index exists at all — the systems above it in the table need a second system for that, and then a synchronisation problem between the two.
vs Relational databases
PostgreSQL / MySQL
The real difference is not performance, it is what you have to operate. MDDB is one process with one file; the comparison is against a server, a connection pool, a schema, and a migration story.
| MDDB | PostgreSQL / MySQL | |
|---|---|---|
| Deployment | one binary, one file | server process, users, schema, migrations |
| Markdown | native document type, front matter parsed | TEXT column |
| Revision history | every write, automatically | triggers and a history table you write |
| Vector search | built in | pgvector (Postgres) / none (MySQL) |
| Full-text search | built in, four algorithms | built in (Postgres), basic (MySQL) |
| Relational joins | none | the entire point |
| Multi-table transactions | no | yes |
Choose PostgreSQL or MySQL when you need joins, multi-table transactions, or the ecosystem — ORMs, migration tools, managed hosting, people who already know it. If your data is relational, a document store is the wrong shape and no benchmark will fix that.
vs Document databases
MongoDB, CouchDB
Closer in shape, and the trade is scale against footprint. These are built to shard across a cluster; MDDB is built to be one file you can copy.
| MDDB | MongoDB | CouchDB | |
|---|---|---|---|
| Horizontal sharding | consistent-hash ring, manual | automatic, mature | clustered |
| Protocols | HTTP, gRPC, GraphQL, MCP | wire protocol | HTTP |
| Change feed | webhooks, binlog replication | change streams | _changes |
| Markdown-aware | yes | no | no |
| Docker image | ~33 MB | ~400 MB | ~180 MB |
| Inserts/s, measured | 502 (no index) / 104 (indexed) | 2 358 | 202 |
Choose MongoDB when you need automatic sharding at a scale one machine cannot hold, change streams, or its geospatial features beyond what GEOSEARCH covers. Choose CouchDB when offline-first replication to clients is the requirement.
vs Search engines
Heavyweight search clusters are a different class of tool. They win on scale and on analysis depth; they cost a JVM, a cluster, and an operations budget.
| MDDB | Heavyweight search engine | |
|---|---|---|
| Deployment | one binary | cluster, JVM, coordinator nodes |
| Baseline memory | ~90 MiB with 5 000 short documents | gigabytes before any data |
| Ranking algorithms | TF-IDF, BM25, BM25F, PMISparse | BM25 plus a large analysis toolkit |
| Analyzers | stemming, stop words, synonyms, 30+ languages | far more, and pluggable |
| Vector search | built in, hybrid with keyword | built in (recent versions) |
| Distributed search | no | yes, that is the product |
The algorithm choice is measured too, on quality as well as speed: FTS Algorithm Benchmark compares all four on the same corpus, and PMISparse documents the sparse-retrieval algorithm that performs best on short technical queries here.
Choose a heavyweight search engine when you need a distributed search cluster, log aggregation, or analysis features — custom analyzers, complex aggregations, percolation — beyond what a document store should be doing.
vs Vector databases
| MDDB | Dedicated vector database | |
|---|---|---|
| Vectors | one part of the store | the whole product |
| Index types | flat, IVF, quantized (int8/int4) | HNSW, IVF-PQ, DiskANN, more |
| Hybrid keyword + vector | native, one query, alpha or RRF | usually a separate keyword system |
| The documents themselves | stored here | usually somewhere else |
| Scale ceiling | one machine's disk | designed for billions |
The honest framing: if vector search is your application and you have more than a few million vectors, use something built for that. MDDB's advantage is that the vectors, the text, the metadata and the revision history are the same records — no synchronisation between two systems, no "the vector store has a document the database deleted".
See Quantization for the memory trade-offs, including disk-only vectors.
vs RAG wrappers
A category worth naming, because it is where most "memory for my agent" searches land: tools that wrap somebody else's retrieval engine in an MCP server and a CLI.
They are a genuinely good way to start. They are also a coupling decision, and the coupling is invisible until it matters.
| MDDB | Wrapper over a third-party engine | |
|---|---|---|
| Who owns the storage format | this project | the upstream engine |
| Who decides the release cadence | this project | upstream, then the wrapper |
| Deployment | one binary | Python runtime, the engine, its dependencies |
| MCP | native — 80 tools over the same core the HTTP API uses | an adapter layer over an engine that predates MCP |
| Per-collection retrieval config | built in | whatever upstream exposes |
| Replication | binlog-based, built in | whatever upstream exposes |
| Upgrade risk | one project's decisions | two projects' decisions, in sequence |
The practical difference shows up in small requests. "Cap the context this collection returns", "make search results carry the collection's answer format", "annotate which tools are read-only so an agent cannot write" — each is a change to one codebase here. Through a wrapper, each one is either upstream's decision or a workaround layered on top of it.
Choose a wrapper when you want to try retrieval-augmented generation this afternoon, your knowledge base is small, and one person is using it. The setup cost is genuinely lower and you are not committing to anything.
Choose an engine when the knowledge base is part of your product: when you need the storage format to be stable because you have data in it, when an upstream release should not be able to change your retrieval behaviour, and when "one more service to operate" is a real cost.
vs Git and the filesystem
Storing Markdown in Git is the right answer more often than vendors like to admit. It is free, diffable, reviewable, and every developer already knows it.
| MDDB | Git + filesystem | |
|---|---|---|
| Query by metadata | indexed, instant | grep and a script |
| Full-text search | indexed, ranked | grep, unranked |
| Vector search | built in | none |
| Concurrent writers | handled | merge conflicts |
| Serving over an API | built in | you build it |
| Human review of changes | revisions, no diff UI | pull requests, the best in the world |
| Cost | a process | nothing |
Choose Git when the content is authored by developers, changes go through
review, and search means grep. Use both when content is authored in Git and
served by something that can rank it — that is what
the WordPress and SSG integrations do.
vs CMS platforms
WordPress, Strapi
Different audience. A CMS is for people who need an editing interface; MDDB is for applications that need an API. That is not a ranking, it is a different question being answered.
| MDDB | WordPress / Strapi | |
|---|---|---|
| Editing UI | admin panel, developer-oriented | full editorial workflow |
| Non-technical authors | no | yes, that is the product |
| Plugin ecosystem | integrations, not plugins | thousands |
| API-first | yes | REST bolted on (WordPress), yes (Strapi) |
| Vector search for RAG | built in | none |
| Deployment | one binary | PHP/Node + database + web server |
Choose a CMS when humans who do not write code need to publish. If that is your requirement, the WordPress plugin puts MDDB behind it — authors keep their editor, and the content becomes searchable by an agent.
When MDDB fits
- Markdown is your primary format, not a
TEXTcolumn. - You want retrieval — keyword, vector or both — without operating a second system for it.
- You are building for agents, and want MCP that was designed in rather than adapted on.
- One binary and one file is a feature, not a limitation.
- Your corpus fits comfortably on one machine.
When it does not
- Your data is relational. Use a relational database.
- You need a distributed search or vector cluster. Use one.
- Your authors are not developers and need an editorial workflow. Use a CMS — and put MDDB behind it if the content should also be searchable by an agent.
- You need more vectors than one machine's disk holds.
← Back to README | See all features → | FTS algorithm benchmark →