Regarding the benchmarks themselves, they would've been more interesting with reproducible steps and more hardware variety.
I greatly appreciate when a vendor is willing to run the test and publish unfavorable information, even if it's only in one benchmark category.
For example, I see they do this for Postgres:
`let max_wal_gb = (shared_buffers_gb).clamp(2, 16);`
2-16GB of WAL is not a lot, but I have no idea how large is the data set.
Though to be honest most people won't scale enough that DB performance is important in the first place. For most people they don't even need a database, your language has built in containers that will do everything you need.
On the ecosystem side, we have also grown a lot over the last few years across the community, integrations, cloud offering, and customers. Still work to do, but we are not as far off as people might assume.
On the parameters, the relational tests use 5 million records per test. The exceptions are the key-value category, which uses 15 million records, and the embedded category, which uses 1 million records. The same dataset shape, workload, harness, and hardware are used across the engines being compared.
For WAL, the 2 to 16 GB range is not intended to be a limit based on the dataset size. For the published runs, the dataset is small enough that this should not be a bottleneck. The persistent runs are also full-durability runs, with Postgres using fsync and synchronous_commit.
We will update the benchmarks page so the versions, dataset sizes, and tuning details are easier to find without digging through the Rust source.
I think it is not so clear cut. I mean, the multi-model nature it is pretty neat. Yes, you can use pgvector on PostgreSQL, but here you also have native graph support. If you want to have both you need to also add something like apache AGE, but arguably that is also a small ecosystem (at least IMHO as I never heard it until I actually started looking for Neo4J alternatives). Also, pgvector has a hard limit on embedding size, while surrealdb does not. For instances in which you have less than 1M elements and retrieval performance matters surreal already has an advantage.
In my personal opinion is a great overall product. Probably not the best at anything, but close enough without having to fiddle with PostgreSQL extensions or adding another piece of machinery to support graph workloads.
The only thing I don't like is that they didn't use either pure SQL nor Cipher for the query(ies) language(s). They roll their own blend, meaning that you will likely need more work to move in the ecosystem and you can't fully use the muscle memory of users that worked with other DBs before.
It is worth a try for startups if you won't mind. Try to vibe code around it and give the data model a new look. I have a prototype project that combines both tree-sitter AST and converted it to JSON, then since SurrealDB accepts JSON as native input I now get free graph lookup on the control flow and easily did ancestry analysis and finding what functions potentially calls to this segement. All of it is in SurrealDB nested graph queries and the performance is alright, but is abysmal in Postgres JSONB since JSONB does not linearize the JSON data structure.
ps: I'm building a K8S operator for deploying SurrealDB with TiKV operator integration too.
The full transparency would be very helpful to know where these strengths are coming from which at a glance look to be multi-threaded in-memory processing.
> ...add something like apache AGE, but arguably that is also a small ecosystem (at least IMHO as I never heard it until I actually started looking for Neo4J alternatives)
Outside of the most trivial use cases, I've found that AGE will not get anywhere near Neo4j in terms of performance and there's a lot of edge cases that just flat out won't work. The interesting types of queries you'd want to do in the graph end up being quite limited in AGE openCypher; I could not write very complex Cypher that would otherwise work well in Neo4j.I appreciate having the option, but for most use cases on Pg, you are better off just using JOINs or switch to Neo4j for your graph workloads. I switched some workloads back to using different approaches of approximating "connectedness" in Pg (e.g. using Jaccard similarity)
If you do go down this route, the easiest way to get coding agents to figure out AGE is actually their regressions SQL tests: https://github.com/apache/age/tree/master/regress/sql
This has a lot of examples for the agent to know what will/won't work with AGE versus Neo4j Cypher.
The innovation points you spend on this should generally be spent in other areas, not seeing if someone's unproven db is your breadwinner.
Oh, that's the reason the SurrealDB operator was here in the first place because I need the full K8S lifecycle to maintain the database state such as backing up, that is not really doable with Helm.
No one should pick us because we're the new hot thing (at least I'd hope not). But at SurrealDB, we've got real enterprises in production at scale. For a lot of startups building today, having LLM/vector features, graph, auth, and the database in one place can really help you ship faster without stitching a bunch of tools together.
As a former DBA I got to see the general purpose databases bolt on a lot of shitty addons, and a lot of upstarts build just enough to get the sale done (or targeting bigger customers than I) - I hope y'all can get enough polish and reliability done and grow into something I want to use in five years :)
You can explore the full results, methodology, and per-database breakdowns at .
Database benchmarks are notoriously easy to game, and difficult to get right. Different hardware, different durability settings, different client libraries, a workload that happens to suit one engine's indexing strategy - any of those will tilt the numbers. So we did three things:
Ran every database on the same hardware - an AMD Ryzen Threadripper 9970X (32C/64T), 128 GiB DDR5, NVMe storage, Ubuntu 24.04.
Used the same open-source harness - crud-bench - with each workload translated into each engine's native query language so no database is penalised for an unfamiliar dialect.
Configured every engine for production-grade durability - fsync on, snapshot isolation, no in-memory shortcuts (except where explicitly noted for embedded comparisons).
We also went out of our way to give every database a fair shot. Rather than running each engine on its out-of-the-box defaults, we used optimised configurations across the board - the same kind of tuning a production team would apply before going live. That meant raising connection and worker pool limits to match the 128-client load, sizing buffer pools, page caches, and shared memory to take advantage of the available 128 GiB of RAM, enabling parallel query execution and prepared-statement caching where supported, setting WAL and checkpoint intervals to values recommended by each project's own performance guides, and turning on the indexes and storage engines (InnoDB, WiredTiger, RocksDB-backed stores, etc.) that each database's documentation recommends for OLTP workloads. The goal was to make sure no engine was held back by a conservative default - if a database underperforms here, it isn't because we left it on its laptop-friendly starter config.
Workloads run with 128 clients issuing 48 concurrent queries each, against datasets of a single table with 5 - 15 million rows of mixed-type records (strings, integers, floats, UUIDs, datetimes, booleans, large text fields, geospatial data, and nested objects and arrays).
We owe a word on durability. The previous round of benchmark results ran with fsync disabled for every engine - leaving each database's writes in the OS page cache rather than flushed to disk. Every database in the comparison ran with the same setting, so nothing was being "fudged" relative to the other engines, but we didn't make the setting explicit, and the headline numbers ended up describing a workload that most production deployments would not likely run.
This round is different. Every database in these benchmarks runs with full disk durability enabled - fsync on, WAL flushed on every commit, no buffered writes hiding behind the page cache. The configuration files for each engine are checked into the so anyone can audit them. The numbers above are what each engine sustains when every committed transaction is on disk before the client gets an acknowledgement. That's slower than the cache-friendly numbers you'll find in some marketing posts, ours included, but it's the only honest way to compare databases that are going to outlive a power outage.
The biggest story is internal. Across three major releases, we've fundamentally rebuilt the query, parser, and storage layers:
| Workload | SurrealDB 1.x | SurrealDB 2.x | SurrealDB 3.x |
|---|---|---|---|
| Mixed CRUD (50% write) | 78k ops/s | 107k ops/s | 141k ops/s |
| Full-table scans | 0.06 ops/s | 0.09 ops/s | 11 ops/s (164×) |
| Indexed lookups | 32 ops/s | 44 ops/s | 104 ops/s (3.2×) |
Between SurrealDB 2.x and 3.x alone:
31% faster mean CRUD throughput
58% faster batch operations
11,894% faster non-indexed full-table scans
136% faster indexed queries
Tail latency improvements of 27% (CRUD), 32% (batches), 59% (indexed), and 99% (scans)
The scan number is not a typo. The SurrealDB 3.x query planner and storage engine eliminates the per-row decoding overhead that dominated earlier versions, which is why a workload that used to take minutes now completes in seconds.
SurrealDB is a durable, transactional, multi-model database, so the comparisons that matter most are against the primary databases people actually evaluate it against - Postgres for relational, MongoDB for document, Neo4j for graph. Here's how the same workload looks across those three categories, run on the same hardware with each engine on a tuned production-grade configuration.
CRUD throughput (ops/s) and bulk-read metrics:
| Workload | SurrealDB | Postgres | MySQL |
|---|---|---|---|
| Create | 122k | 83k | 22k |
| Read | 254k | 327k | 195k |
| Update | 106k | 81k | 22k |
| Delete | 156k | 86k | 22k |
| Write throughput (C+U+D mean) | 128k | 84k | 22k |
| count(*) on 5M rows | 12 | 8 | 42 |
SurrealDB is faster than Postgres on every write operation - roughly 1.5× faster creates, 1.3× faster updates, and 1.8× faster deletes - while Postgres still edges ahead on raw single-record reads. Against MySQL the gap widens dramatically: SurrealDB is 5 - 7× faster on writes.
Averaged across creates, updates, and deletes, SurrealDB delivers ~1.5× the write throughput of Postgres - the headline number the benchmarks page leads the Relational category with - and beats Postgres by ~1.5× on full-table counts. Postgres' query planner is 30 years old and still ahead on indexed predicate filtering; we're not pretending otherwise, and we're working on closing that gap in 3.1.
CRUD throughput (ops/s) and bulk-read metrics:
| Workload | SurrealDB | MongoDB | ArangoDB |
|---|---|---|---|
| Create | 122k | 183k | 1.0k |
| Read | 254k | 200k | 255k |
| Update | 106k | 160k | 0.9k |
| Delete | 156k | 200k | 1.0k |
| Filter scan (unindexed, ops/s) | 8.3 | 3.0 | 8.5 |
This is the closest race. MongoDB still leads on single-record writes, while SurrealDB is ~1.3× faster on reads - and the picture flips on heavier workloads. On predicate filter scans against unindexed tables - the headline figure the benchmarks page uses for the Document category - SurrealDB is roughly 2.7× faster than MongoDB, with consistently lower mean and p99 latency. Against ArangoDB's document engine, SurrealDB is between 100× and 150× faster on writes.
CRUD throughput (ops/s) and bulk-read metrics:
| Workload | SurrealDB | Neo4j | ArangoDB |
|---|---|---|---|
| Create | 122k | 42k | 1.0k |
| Read | 254k | 174k | 255k |
| Update | 106k | 50k | 0.9k |
| Delete | 156k | 44k | 1.0k |
| Filter scan (indexed, mean ops/s) | 421 | 12 | - |
SurrealDB outperforms Neo4j across every CRUD operation - roughly 2 - 3.5× faster on writes and 1.5× faster on reads - while running the same graph traversals through the same engine that handles documents and tables.
The gap on filtered scans is even more dramatic. Across indexed predicate filter queries - the headline metric the benchmarks page uses for the Graph category - SurrealDB is roughly 35× faster than Neo4j. No separate database, no separate query language, no separate operational story.
For raw key-value throughput, we also ran SurrealDB's in-memory engine (with append-only persistence) against Redis and KeyDB:
| Operation | SurrealDB | Redis | KeyDB |
|---|---|---|---|
| Create | 300.8k | 85.8k | 79.5k |
| Read | 288.1k | 367.9k | 348.6k |
| Update | 300.6k | 89.0k | 85.2k |
| Delete | 279.3k | 100.6k | 100.0k |
That's roughly 3× faster than Redis on writes, updates, and deletes, while offering durable, snapshot-isolated transactions and a full query language Redis doesn't have. Redis still wins on large 1,000-record batch operations and edges ahead on single-record reads.
SurrealDB's embedded engine runs the same SurrealQL on the same disk format as the server.
| Workload | SurrealDB embedded | SQLite |
|---|---|---|
| Create | 110k | 1.3k |
| Read | 138k | 154k |
| Update | 145k | 1.3k |
| Delete | 101k | 1.3k |
| Filter scan (unindexed, ops/s) | 39 | 6 |
| Filter scan (indexed, mean ops/s) | 7.5k | 2.3k |
Against SQLite, SurrealDB is roughly 85× faster on creates, 110× faster on updates, and 75× faster on deletes, with single-record reads in the same ballpark. On predicate filter scans against unindexed tables it's around 6.5× faster than SQLite, and on indexed filter scans, around 3× faster.
The full breakdowns - including p50, p95, and p99 latencies, batch sizes from 100 to 1,000 rows, indexed and non-indexed predicate filters, and full-text search - are on the .
The numbers above are an honest snapshot, not a finish line. There are still workloads where Redis, MongoDB, and Postgres beat us - large batch operations vs. Redis, single-record writes vs. Mongo, indexed predicate filtering vs. Postgres - and we know exactly where each gap comes from. Closing those gaps is the central focus of the SurrealDB 3.1 cycle.
What we're actively working on:
Batch path rewrites to bring 100-row and 1,000-row batched ops closer to Redis throughput, including better client-side pipelining and a leaner server-side batch executor.
A smarter query planner with cost-based optimisation, predicate pushdown into the storage engine, and richer index selectivity statistics - the work that gets us to parity with Postgres on indexed filter scans.
Storage layer improvements for the document workload - more compact in-place updates, sharper write amplification, and tighter integration between the key encoding and the document path resolver - which is where Mongo currently has the edge on single-record writes.
Vector and graph traversal optimisations as those workloads land in the benchmark suite, so the multi-model story holds up at the same rigour as CRUD.
The goal isn't "fastest at one thing." It's to be fastest, or competitive, across every workload that matters, while keeping the one property no specialist engine can match: a single query language - SurrealQL - that handles relational, document, graph, key-value, time-series, vector, and full-text search data in the same database, with the same transactional guarantees, on the same disk format - and the same engine, whether you're running it embedded inside an application, on a single server, at the edge close to your users, or distributed across hundreds of nodes for horizontal scale. We don't think you should have to choose between Postgres, Mongo, Neo4j, and Redis - or between a database that runs on a developer laptop and one that runs across a global fleet. We think a single database should run all four shapes of workload at production speed, anywhere it needs to live, and the SurrealDB 3.x numbers above are the strongest evidence yet that it can.
There's a reason we keep pushing on this combination of data models, and it's not historical accident. Agent memory is multi-model by nature. A useful AI agent needs structured facts about the world (relational), semi-structured context and tool outputs (document), entity and event relationships (graph), embeddings for semantic recall (vector), keyword and BM25 retrieval over its corpus (full-text search), episodic and temporal context (time-series), and fast session and cache state (key-value) - and it needs all of that in a single transactionally consistent store, because the moment those shapes live in separate databases, you've built a glue-code problem that breaks every time the schema changes or the model is updated.
Agents also need that memory close to where they run. An agent reasoning inside a browser, a phone, an in-vehicle system, or a per-tenant edge worker can't afford a round trip to a central database for every retrieval. The fact that SurrealDB runs as an embedded engine on the same disk format as the distributed server - with the same query language and the same transactional guarantees - is what makes it usable as the memory layer for agents that move fluidly between local, edge, and centralised deployments. Faster CRUD, faster scans, and faster indexed lookups aren't just abstract benchmark wins; they're how many tools an agent can call, how much context it can recall per turn, and how many concurrent agents a single host can support. That's the workload SurrealDB 3.x is built for, and it's the workload the next round of benchmarks - covering vector search, graph traversal, and full-text retrieval - will measure head-on.
We intend to expand the benchmarks to also cover CockroachDB, TiDB, MongoDB, and Aerospike for distributed comparisons, which we'll publish in a future round. We also plan to expand the workload set to include graph traversals vector search, and full-text search, two areas where the single-engine, multi-model design of SurrealDB shows its biggest advantages.
Until then: the harness is open source, the results are reproducible, and we'd love for you to run them on your own hardware and tell us what you find.