Lane

Lifecycle

Opening, closing, deployment boundaries, and what waiting for the disk costs.

Illustration for DBBench, an open benchmark of database engines

Results

Tasks and results.

These are the costs that nobody benchmarks and everybody pays. A session table opens a database, does one thing and closes it, for each kind of data the engine can hold, embedded and served. It is the cold measurement in its purest form.

A second table separates the two costs of running a database in another process: turning the answer into a wire format, and crossing a process boundary. A third deployment in between, a server inside the same process, tells them apart.

The durability table runs every timed write twice, once with the commit returning without waiting for the disk and once with the commit waiting for the log to be flushed and synced. It prints both with the ratio. Some engines have no such setting, and the table names them instead of comparing them against a setting they do not have.

A session for each kind of data, embedded and served. The embedded rows also carry what starting the process costs before the first database call, because an open measured inside an already-running process is not what someone launching a script pays.

Session cost, open to close synthetic, one structure per row
EngineModeSizeJVM start ms ↓first open ms ↓cold process ms ↓open and close ms ↓one query ms ↓one write ms ↓write, then query ms ↓peak memory GiB ↓
Dense vectorsembedded10k182.1222.7500.214.326.724.433.31.12
Dense vectorsserver10k———16.334.327.838.51.37
Documentsembedded10k177.8185.5483.312.311.120.822.00.36
Documentsserver10k———14.815.124.028.60.40
Documents, ten indexesembedded10k179.8195.6471.018.717.548.748.90.66
Documents, ten indexesserver10k———20.423.553.256.40.88
Empty databaseembedded10k179.1184.3455.111.311.620.321.10.26
Empty databaseserver10k———13.114.922.423.80.30
Graphembedded10k177.8186.2475.913.715.221.123.32.11
Graphserver10k———15.218.630.226.72.01
Sparse vectorsembedded10k183.7195.1468.512.911.932.529.80.98
Sparse vectorsserver10k———14.915.733.633.71.15
Time seriesembedded10k180.2198.4472.918.217.027.928.72.08
Time seriesserver10k———21.221.029.332.92.01
Dense vectors (SurrealDB)embedded10k—79.1207.354.759.453.661.2re-run
Documents (SurrealDB)embedded10k—29.8158.88.4711.89.7514.0re-run
Documents, ten indexes (SurrealDB)embedded10k—129.7259.5114.0116.4116.4117.7re-run
Empty database (SurrealDB)embedded10k—22.9150.72.602.554.083.96re-run
Graph (SurrealDB)embedded10k—255.7384.3205.9218.1279.1213.8re-run
Time series (SurrealDB)embedded10k—34.1163.09.8213.810.914.2re-run
Dense vectorsembedded100k186.2230.7507.622.4103.532.6111.12.88
Dense vectorsserver100k———17.4107.934.9117.74.16
Documentsembedded100k179.9180.6449.411.911.419.522.20.88
Documentsserver100k———14.015.827.827.90.94
Documents, ten indexesembedded100k177.8193.4465.418.717.950.149.12.09
Documents, ten indexesserver100k———20.721.653.657.02.06
Empty databaseembedded100k180.3184.1454.712.511.820.921.50.25
Empty databaseserver100k———12.413.922.925.50.30
Graphembedded100k180.3181.7453.513.815.024.222.02.76
Graphserver100k———16.117.930.127.92.75
Sparse vectorsembedded100k179.4195.7464.512.612.229.827.52.74
Sparse vectorsserver100k———14.116.135.133.64.11
Time seriesembedded100k181.6197.1472.917.916.824.929.22.70
Time seriesserver100k———20.421.032.730.12.69
Dense vectors (SurrealDB)embedded100k—666.9796.8730.3621.6623.4632.8re-run
Documents (SurrealDB)embedded100k—106.4237.583.0110.082.9110.7re-run
Documents, ten indexes (SurrealDB)embedded100k—1,0331,1621,2471,2511,2571,249re-run
Empty database (SurrealDB)embedded100k—20.7149.32.552.423.864.10re-run
Graph (SurrealDB)embedded100k—2,5442,6763,1563,1693,1603,166re-run
Time series (SurrealDB)embedded100k—124.9253.9118.0170.2120.6171.0re-run
Dense vectorsembedded1M178.6209.1482.698.0853.198.1872.312.2
Dense vectorsserver1M———16.2897.3103.9902.012.5
Documentsembedded1M178.2171.8436.812.111.522.422.74.19
Documentsserver1M———14.013.821.530.14.22
Documents, ten indexesembedded1M178.8194.9484.430.330.364.564.410.4
Documents, ten indexesserver1M———35.437.368.369.110.3
Empty databaseembedded1M186.9175.2447.412.112.120.721.00.47
Empty databaseserver1M———13.114.524.524.70.52
Graphembedded1M179.9172.1441.512.714.823.222.810.5
Graphserver1M———15.617.027.828.210.5
Sparse vectorsembedded1M180.3182.7451.011.812.129.728.610.5
Sparse vectorsserver1M———13.314.830.128.910.5
Time seriesembedded1M180.9190.7469.618.617.029.627.010.1
Time seriesserver1M———21.421.033.027.710.2
Dense vectors (SurrealDB)embedded1M>8h>8h>8h>8h>8h>8h>8h—
Dense vectorsembedded10M179.3206.9480.41,41218k1,42018k24.7
Dense vectorsserver10M———15.218k1,48918k24.7
Documentsembedded10M178.8171.9437.011.010.721.121.415.3
Documentsserver10M———13.913.827.228.115.3
Documents, ten indexesembedded10M180.1201.9494.224.726.964.663.522.7
Documents, ten indexesserver10M———28.432.465.862.922.1
Empty databaseembedded10M179.4174.1441.711.311.421.420.80.60
Empty databaseserver10M———14.114.826.724.90.68
Graphembedded10M180.1172.4441.512.714.520.823.019.1
Graphserver10M———14.916.828.924.019.1
Sparse vectorsembedded10M179.5179.8449.211.912.029.828.319.1
Sparse vectorsserver10M———13.014.833.827.519.1
Time seriesembedded10M180.2193.3472.118.617.428.028.115.2
Time seriesserver10M———21.021.026.531.215.3
Documents, ten indexes (SurrealDB)embedded10MOOMOOMOOMOOMOOMOOMOOM—
Graph (SurrealDB)embedded10MOOMOOMOOMOOMOOMOOMOOM—

↑ higher is better, ↓ lower is better; the best value in each column, within one size, is bold

Measured rows: benchmarks/experiments/results/runs_paper_oct.csv

  • The SESSION is open + action + close. Reporting open and close alone hides work triggered between them.
  • Cold start is measured in a fresh subprocess and reported beside every session number, because a millisecond open inside a process that takes half a second to reach its first database call is not a millisecond to whoever launched it.
  • A clean close should be O(what was written), not O(what is stored): write nothing and closing should cost the same at 10k documents and 10M.
  • Server rows have no JVM start, first open, or cold process: the server is already running when the probe connects, so those three columns describe the embedded process only. The session columns are measured for both.
  • Graph with the analytical view is withheld: its query grew from a bounded set of seeds to an unbounded 2-hop, so it stays off this table until the next measurement bounds the query again.
  • SurrealDB's peak memory on this table is withheld, and its 10M dense-vector cell is not shown: our build fed SurrealDB from one in-memory Python list while the ArcadeDB arm streamed, so its memory reading was partly ours, and the 10M vector build was killed for our allocation, not SurrealDB's. Its session timings ran after the build and stand. Both are re-measured in the next measurement, with the input streamed.
  • An engine with no row for a situation cannot build it, and its adapter says so rather than leaving the row blank; the declared row keeps the statement the adapter tried and the engine's own answer.
  • SurrealDB (embedded) has no row for the graph with the analytical view (no graph analytical view) or sparse vectors (no sparse-vector index type).
  • The dense-vector rows' one-query and write-then-query sessions, embedded and server alike, carry a defect in the engine build measured here, ArcadeDB issue #8852 (https://github.com/ArcadeData/arcadedb/issues/8852): the first vector search after a database is reopened re-reads every vector's record on one thread, so from a million vectors up those sessions are mostly that re-read, and ArcadeDB 26.10.1 fixes it.
  • The rows each session reads back are hashed, and for documents, documents with ten indexes, the graph, and time series every embedded engine that builds the situation must return the same hash as ArcadeDB embedded, or nothing is published. Only ArcadeDB (embedded) builds sparse vectors, so there is no second answer to compare. Not compared: the empty database, which reads nothing back; and dense vectors, whose read goes through an approximate index and is checked by recall on the dense vector table instead. The server rows are compared only with other server rows, because a server session runs a different set of modes and its record counts differ by construction; ArcadeDB (server) is the only engine served here, so they are compared with nothing.
  • ArcadeDB's embedded dense-vector rows read the index's statistics just before close, to record whether a rebuild ran. The read is outside the timers, but on this engine build it raises the cost of the close that follows it, so that row's open and close figure is an upper bound for a session that touches nothing.
  • SurrealDB rows have no JVM start: the SurrealDB core is a compiled extension that the Python import loads, so there is no runtime to start apart from the import. Their first open is the first open with the SDK already imported, and their cold process is interpreter start, import, open, and close, the same span the ArcadeDB embedded rows time. SurrealDB's time-series row is a plain table of timestamped records, the footing its time-series rows elsewhere on this page run on, because the engine has no time-series type.
  • SurrealDB (embedded) at 1M: the cell for dense vectors exceeded its 8 hour budget, the same budget every engine on this table had, on its first attempt and was not retried; there is no row.
  • SurrealDB (embedded) at 10M: the cell for documents with ten indexes reached the 28g memory envelope (peak 28,672 MiB) and was killed by the kernel, the same envelope every engine on this table had; it did not run out of time, and there is no row.
  • SurrealDB (embedded) at 10M: the cell for the graph reached the 28g memory envelope (peak 28,672 MiB) and was killed by the kernel, the same envelope every engine on this table had; it did not run out of time, and there is no row.
  • In the cells: `>8h` means the cell ran past its tier's cap and was not retried. `OOM` means killed at the cell's memory envelope rather than running out of time. `re-run` means the cell was measured before a fix to our harness and stays off the table until it is measured again; the note says what was wrong. A dash is an operation the engine cannot express, never a slow one.

One projection answered at a range of result sizes by three deployments: in process, an HTTP server inside the same process, and a separate container. Read a row across to see which of the two costs grows with the answer.

What the client/server split costs 200,000-document projection, one engine, three deployments
EngineSizein-process ms ↓in-process server, HTTP ms ↓separate container, HTTP ms ↓
1 documents11.202.542.73
10 documents100.941.812.02
100 documents1001.832.923.69
1,000 documents1,0003.104.704.36
10,000 documents10,00019.422.922.6
100,000 documents100,000198.9207.9258.1

↑ higher is better, ↓ lower is better; the best value in each column, within one size, is bold

Measured rows: benchmarks/experiments/results/e4decomp_417314c18

  • Every number is milliseconds. One engine build (arcadedb 26.10.1-dev · 417314c18) in all three deployments, 15 repetitions after 3 warmup, identical cpuset 0-11, memory cap 8g and heap 6g.
  • All three deployments turn the answer into Python objects the same way, so the difference is how the database was deployed and not how we read the result.
  • The separate container runs on the same machine, talking over the local network interface. It says what running the database beside your program costs, and says nothing about a database on another machine across a real network.

Every timed write at both durability settings, with the ratio, and the single-record read beneath the document writes as a control. The Size column names the operation rather than a corpus size, because the lanes that time a write do not write the same thing.

What waiting for the disk costs Every timed write, run twice, once at each durability setting
EngineModeSizeno wait ms ↓waits for the disk ms ↓cost of waiting
ArcadeDBembeddedTPC-C new-order, one transaction (documents)0.468.2118.0x
ArcadeDBserverTPC-C new-order, one transaction (documents)1.269.917.86x
ArangoDBserverTPC-C new-order, one transaction (documents)2.5613.15.12x
DuckDBembeddedTPC-C new-order, one transaction (documents)—10.7—
MongoDBserverTPC-C new-order, one transaction (documents)0.576.8311.9x
PostgreSQLserverTPC-C new-order, one transaction (documents)0.0882.8432.3x
SQLiteembeddedTPC-C new-order, one transaction (documents)0.0122.54211.8x
SurrealDBembeddedTPC-C new-order, one transaction (documents)0.168.7754.1x
SurrealDBserverTPC-C new-order, one transaction (documents)—10.1—
ArcadeDBembeddedTPC-C payment, one transaction (documents)0.198.5145.6x
ArcadeDBserverTPC-C payment, one transaction (documents)0.8810.511.9x
ArangoDBserverTPC-C payment, one transaction (documents)2.5712.95.02x
DuckDBembeddedTPC-C payment, one transaction (documents)—10.5—
MongoDBserverTPC-C payment, one transaction (documents)0.456.7615.0x
PostgreSQLserverTPC-C payment, one transaction (documents)0.0833.0636.9x
SQLiteembeddedTPC-C payment, one transaction (documents)0.0102.54254.0x
SurrealDBembeddedTPC-C payment, one transaction (documents)0.148.6460.4x
SurrealDBserverTPC-C payment, one transaction (documents)—9.88—
ArcadeDBembeddedone record inserted (documents)0.107.9880.2x
ArcadeDBserverone record inserted (documents)0.7010.214.6x
ArangoDBserverone record inserted (documents)0.5110.119.9x
DuckDBembeddedone record inserted (documents)—9.10—
MongoDBserverone record inserted (documents)0.0804.1952.4x
PostgreSQLserverone record inserted (documents)0.0433.5383.1x
SQLiteembeddedone record inserted (documents)0.00602.53421.3x
SurrealDBembeddedone record inserted (documents)0.0808.31103.9x
SurrealDBserverone record inserted (documents)—9.20—
ArcadeDBembeddedone record updated (documents)0.0848.0095.3x
ArcadeDBserverone record updated (documents)0.6410.215.8x
ArangoDBserverone record updated (documents)0.5310.219.2x
DuckDBembeddedone record updated (documents)—8.95—
MongoDBserverone record updated (documents)0.114.3338.0x
PostgreSQLserverone record updated (documents)0.0453.5579.0x
SQLiteembeddedone record updated (documents)0.00552.51456.6x
SurrealDBembeddedone record updated (documents)0.0778.38108.8x
SurrealDBserverone record updated (documents)—9.17—
ArcadeDBembeddedone record deleted (documents)0.188.5546.2x
ArcadeDBserverone record deleted (documents)0.7910.613.4x
ArangoDBserverone record deleted (documents)0.5110.119.8x
DuckDBembeddedone record deleted (documents)—8.95—
MongoDBserverone record deleted (documents)0.114.2940.1x
PostgreSQLserverone record deleted (documents)0.0393.3485.6x
SQLiteembeddedone record deleted (documents)0.00502.52503.8x
SurrealDBembeddedone record deleted (documents)0.128.3772.8x
SurrealDBserverone record deleted (documents)—10.7—
ArcadeDBembeddedone record read (documents) -- the control: a read commits nothing0.0310.0270.87x
ArcadeDBserverone record read (documents) -- the control: a read commits nothing0.610.651.07x
ArangoDBserverone record read (documents) -- the control: a read commits nothing0.470.460.97x
DuckDBembeddedone record read (documents) -- the control: a read commits nothing—0.16—
MongoDBserverone record read (documents) -- the control: a read commits nothing0.0920.0870.95x
PostgreSQLserverone record read (documents) -- the control: a read commits nothing0.0400.0431.07x
SQLiteembeddedone record read (documents) -- the control: a read commits nothing0.00200.00351.75x
SurrealDBembeddedone record read (documents) -- the control: a read commits nothing0.0490.0491.00x
SurrealDBserverone record read (documents) -- the control: a read commits nothing—0.14—
ArcadeDBembeddedone person and one edge inserted (graph)0.518.9817.7x
ArcadeDBserverone person and one edge inserted (graph)1.2811.28.78x
ArangoDBserverone person and one edge inserted (graph)0.8810.512.0x
DuckPGQembeddedone person and one edge inserted (graph)—10.4—
FalkorDBserverone person and one edge inserted (graph)0.178.8353.2x
LadybugDBembeddedone person and one edge inserted (graph)—re-run—
Memgraphserverone person and one edge inserted (graph)0.159.3161.0x
MongoDBserverone person and one edge inserted (graph)0.235.5724.7x
Neo4jserverone person and one edge inserted (graph)—7.36—
SurrealDBembeddedone person and one edge inserted (graph)0.1916.788.1x
SurrealDBserverone person and one edge inserted (graph)—17.4—
ArcadeDBembeddedone person's property updated (graph)0.208.5542.4x
ArcadeDBserverone person's property updated (graph)0.8410.512.5x
ArangoDBserverone person's property updated (graph)0.7110.414.6x
DuckPGQembeddedone person's property updated (graph)—8.96—
FalkorDBserverone person's property updated (graph)0.118.6876.4x
LadybugDBembeddedone person's property updated (graph)—re-run—
Memgraphserverone person's property updated (graph)0.149.1564.2x
MongoDBserverone person's property updated (graph)0.0864.0547.1x
Neo4jserverone person's property updated (graph)—5.98—
SurrealDBembeddedone person's property updated (graph)0.0878.5097.2x
SurrealDBserverone person's property updated (graph)—9.12—
ArcadeDBembeddedone person and its edges deleted (graph)0.489.3019.6x
ArcadeDBserverone person and its edges deleted (graph)1.0811.510.7x
ArangoDBserverone person and its edges deleted (graph)0.9010.711.9x
DuckPGQembeddedone person and its edges deleted (graph)—10.5—
FalkorDBserverone person and its edges deleted (graph)0.399.4924.1x
LadybugDBembeddedone person and its edges deleted (graph)—re-run—
Memgraphserverone person and its edges deleted (graph)0.149.1364.5x
MongoDBserverone person and its edges deleted (graph)0.325.8218.4x
Neo4jserverone person and its edges deleted (graph)—6.14—
SurrealDBembeddedone person and its edges deleted (graph)0.179.3854.2x
SurrealDBserverone person and its edges deleted (graph)—10.5—
ArcadeDB (one transaction)embeddedone transaction across three models (cross-model, 50k products)1.711.700.99x
ArcadeDB (one transaction)serverone transaction across three models (cross-model, 50k products)7.5217.02.26x
ArangoDBserverone transaction across three models (cross-model, 50k products)re-runre-runre-run
MongoDBserverone transaction across three models (cross-model, 50k products)5.6810.11.77x
Neo4j (vector index)serverone transaction across three models (cross-model, 50k products)—re-run—
PostgreSQL + pgvector + AGEserverone transaction across three models (cross-model, 50k products)4.938.451.71x
Qdrant + Neo4j (no shared transaction)serverone transaction across three models (cross-model, 50k products)—re-run—
SurrealDBembeddedone transaction across three models (cross-model, 50k products)7.8816.12.04x
SurrealDBserverone transaction across three models (cross-model, 50k products)—16.0—
ArcadeDB (one transaction)embeddedone transaction across three models (cross-model, 500k products)2.152.161.00x
ArcadeDB (one transaction)serverone transaction across three models (cross-model, 500k products)7.9218.12.29x
ArangoDBserverone transaction across three models (cross-model, 500k products)re-runre-runre-run
MongoDBserverone transaction across three models (cross-model, 500k products)6.149.581.56x
Neo4j (vector index)serverone transaction across three models (cross-model, 500k products)—re-run—
PostgreSQL + pgvector + AGEserverone transaction across three models (cross-model, 500k products)13.118.41.41x
Qdrant + Neo4j (no shared transaction)serverone transaction across three models (cross-model, 500k products)—re-run—
SurrealDBserverone transaction across three models (cross-model, 500k products)—15.4—

↑ higher is better, ↓ lower is better; the best value in each column, within one size, is bold

  • Each row is one engine running ONE operation twice: once where a commit returns without waiting for the disk, which is the setting every other table on this page reports, and once where the commit waits for the log to be flushed and synced. Nothing else about the cell changes.
  • Every timed write on the page is here: the six document operations, the three graph writes, and the cross-model transaction. Read down the operations for one engine rather than across the engines for one operation, because what the setting costs is a property of the engine's commit and the rest of the page already compares the engines.
  • The read is the control, and it is the row that makes the rest readable: it runs in both cells like everything else and commits nothing, so it is what the writes are moving against.
  • The Size column names the operation rather than a corpus size, and for the cross-model transaction the catalog size as well: the three lanes that time a write do not write the same thing, so each engine is compared only against the engines running its own operation.
  • The setting is a property of the cell, not a second measurement inside one: it lives on the database or on the server for most of these engines, so each pair of numbers is two runs.
  • DuckDB, DuckPGQ, and Neo4j have no setting to relax: DuckDB was traced to a sync at every commit, and DuckPGQ runs on DuckDB; Neo4j forces its log at commit as it documents, and offers no setting that changes it. So each prints one number, in the column for a commit that waits. Reading it against the other column would be reading a choice the engine does not offer.
  • SurrealDB (server) ran at its default in both runs, a sync to disk at every commit: the server log of every run reads "Sync mode: every transaction commit", and a trace counts one sync per commit. So the one number printed is in the waiting column. It has a setting that does not wait, which our harness missed, and the next measurement runs both.
  • In the runs that wait for the disk, ArcadeDB was set to sync its write-ahead log's data and metadata at every commit (`txWalFlush=2`, an fsync). The next measurement uses `txWalFlush=1` instead, a data-only sync (fdatasync) like SQLite's FULL setting, which ArcadeDB's documentation recommends for production and says recovers everything the full sync does.
  • ArcadeDB (one transaction) shows almost no cost of waiting because of a defect in the engine build measured here, ArcadeDB issue #8129 (https://github.com/ArcadeData/arcadedb/issues/8129): after the vector index is built, later commits in the same session skip the write-ahead log, so there is nothing to wait for, and ArcadeDB 26.10.1 fixes it.
  • SurrealDB (embedded) and SurrealDB (server) send the graph insert's person and its edge as two statements with no transaction around them, so each commits on its own: the run that waits for the disk waits twice for each insert, and the cost of waiting on that row counts both. The next measurement wraps the two in one transaction.
  • ArangoDB, LadybugDB, Neo4j (vector index), and Qdrant + Neo4j (no shared transaction) have cells marked `re-run` here because the same writes are withheld on the tables they come from, and the note under each of those tables says why.
  • The ratio is what the strict setting costs on that engine for that operation. The document and graph operations write the same few records whatever the corpus holds, so each row pools every corpus size its lane ran it at; the cross-model transaction searches and expands over the whole catalog, so it has one row per catalog size and its ratio holds at that size. It is not a claim about any other write. A large ratio is not a slow engine: it is an engine whose relaxed path was fast, measured against a flush that costs what a flush costs.
  • The document operations here are timed from the first one after the data is loaded, so their cells include each engine's warm-up. On ArcadeDB (embedded) and ArcadeDB (server), which run on a JVM, that warm-up includes the JVM compiling the statement path. The next measurement gives every engine an untimed warm-up on separate keys first, and this table prints their warm median.
  • In the cells: `re-run` means the cell was measured before a fix to our harness and stays off the table until it is measured again; the note says what was wrong. A dash is an operation the engine cannot express, never a slow one.

The rules every result follows are in the methodology. The settings for each engine are on the engines page. If one is wrong, tell us.

DBBench

The other lanes.

  • Tables: Documents and structured query language (SQL) analytics.
  • Graphs: Interactive and analytical queries on a social network graph.
  • Vectors: Dense and sparse vector search, with recall beside every latency.
  • Time series: Monitoring-style queries over timestamped readings.
  • Cross-model: One operation across a vector, a graph edge, and a document.