NEW

The wait is over. SurrealDB 3.0 is here!

Learn more

SurrealDB 3.0 benchmarks: a new foundation for performance

Featured
Engineering

Feb 17, 202633 min read

SurrealDB

SurrealDB

Show all posts

SurrealDB 3.0 benchmarks: a new foundation for performance

Our newsletter

Explore our releases, news, events, and much more

SurrealDB 3.0 benchmarks: a new foundation for performance

When we began our benchmarking journey with SurrealDB 2.0, we set out to answer a simple but important question: how does SurrealDB perform in the real world?

Since then, SurrealDB has seen rapidly growing traction - including enterprises like Tencent and Later.com running in production at significant scale, across high-throughput, business-critical workloads. As adoption has increased, so has the importance of demonstrating not just raw performance, but predictable, production-ready performance.

Last year we shared those early results openly, along with our methodology and learnings, in our first benchmarking report.

With SurrealDB 3.0, we’re taking the next major step in that journey. Today, we’re excited to announce our new benchmarks for SurrealDB 3.0 - and more importantly, the architectural changes that make these results possible.

Benchmarking a multi-model database

As mentioned in our previous benchmarking blog, benchmarking a multi-model database is more complex than comparing single-purpose systems. SurrealDB unifies relational, document, graph, time-series, key-value, vector, geospatial, and full-text workloads in one engine, spanning everything from embedded deployments to distributed clusters.

This versatility makes fair benchmarking challenging, since different databases vary widely in durability guarantees, disk flushing behaviour, and configuration trade-offs. For our benchmarks, we’ve done our best to keep comparisons as balanced and transparent as possible - and we always welcome feedback on how to improve the methodology further.

Because SurrealDB can replace entire stacks of databases and supporting tools, the best way to benchmark SurrealDB is often through a like-for-like proof of concept against multiple existing systems (we have customers like Tencent who reduced nine separate tools down to one with SurrealDB).

Our open-source benchmarking tool

We built our own internal benchmarking tool - crud-bench - in Rust to properly evaluate SurrealDB across the wide range of workloads it supports. Unlike many generic benchmarking suites, crud-bench is designed to test not just basic CRUD operations, but also the broader feature set that makes SurrealDB unique, across embedded, networked, and remote deployments. It allows us to compare SQL, NoSQL, key-value, and embedded systems in a consistent way, while continuously measuring the performance impact of changes inside SurrealDB itself. For more details on how the tool work, check the crud-bench GitHub repo and the explanation in the previous 2.0 benchmark blog.

SurrealDB 3.0: a new execution engine

The foundation

SurrealDB 3.0 has been largely focused on stability and getting the right foundations in place. We rearchitected the internals in ways that would have been nearly impossible under the previous design, and the performance results we're seeing now are a direct consequence of that work.

The biggest change is the query execution engine. We took inspiration from the broader database community and moved SurrealQL's execution model to a more standard pipeline: AST → LogicalPlan → ExecutionPlan, with basic query optimisations applied throughout. The new engine is fully streaming internally, and in a future minor release we will make it stream end-to-end.

Right now, the new execution engine covers read-only statements. In the coming minor releases we'll expand it to handle all of our workloads.

TL;DR Now let’s get to the benchmarks themselves. The tl;dr for the numbers below are as follows:

  • Graph queries are between 8-22x faster.

  • Large table scans with LIMIT, START, and START + LIMIT are 3-6x faster.

  • The query planner is smarter. Queries like SELECT * FROM table WHERE id = record:42 that previously triggered full table scans now resolve in sub-millisecond time, a 4000x+ improvement. This is especially important for a query language like SurrealQL which is very close to standard SQL in which WHERE id = is one of the most common patterns.

  • ORDER BY queries are 3-4x faster across all storage engines.

  • HNSW vector search is up to 8x faster. Indexed vector similarity queries dropped from ~35 seconds to ~4.5 seconds.

  • The new execution model enables significantly improved concurrency and throughput.

  • Complex GROUP BY queries with multiple aggregations are up to 55% faster, with the biggest gains on multi-aggregation and deduplication workloads.

Benchmarks

In-Memory

TestCategorySurrealDB 2SurrealDB 2SurrealDB 2SurrealDB 2SurrealDB 3SurrealDB 3SurrealDB 3SurrealDB 3Mean % v3 vs. v2OPS % v3. vs v2Query
Mean (ms)P50 (ms)P99 (ms)Operations Per Second (OPS)Mean (ms)P50 (ms)P99 (ms)Operations Per Second (OPS)
[C]reateCRUD1.941.952.97147538.60.720.681.73396504.5-62.9%+168.7%
[R]eadCRUD0.370.341.06757976.60.580.541.48488252.1+56.8%-35.6%
[U]pdateCRUD2.582.563.25111466.60.730.701.72389693.8-71.7%+249.6%
[S]can::count_all (2000)Scans204.97187.65602.111277.8113.9963.65695.292100.1-44.4%+64.3%
[S]can::limit_id (2000)Scans5.323.8218.4547510.81.731.683.60126780.2-67.5%+166.8%
[S]can::limit_all (2000)Scans17.454.5083.3315053.52.462.395.3897982.9-85.9%+550.9%
[S]can::limit_start_id (2000)Scans7.453.3339.1333909.710.8111.5720.2924360.7+45.1%-28.2%
[S]can::limit_start_all (2000)Scans18.116.6185.5014419.511.1410.8427.9523733.8-38.5%+64.6%
[S]can::select_where_id (2000)Scans0.490.381.15401422.20.660.601.42319614.6+34.7%-20.4%SELECT * FROM record:42
[S]can::select_where_id_eq (2000)Scans3935.653792.897335.9470.90.680.651.46309409.5-100.0%+436179.7%SELECT * FROM record WHERE id = record:42
[S]can::select_where_gt (2000)Scans5242.395062.659895.9352.96235.402277.3837093.3843.8+18.9%-17.2%SELECT * FROM record WHERE number > 4625
[S]can::select_where_in (2000)Scans9745.809388.0317924.1028.46221.911434.6230867.4644.3-36.2%+55.8%SELECT * FROM record WHERE age IN [5, 15, 25, 35, 45, 55, 65]
[S]can::select_where_multi_and (2000)Scans6065.925787.6512107.7745.76238.461468.4125018.3744.2+2.8%-3.2%SELECT * FROM record WHERE city = 'london' AND age > 50
[S]can::select_where_order_limit (2000)Scans5196.364997.129936.9053.35762.221320.9640730.6248.7+10.9%-8.7%SELECT * FROM record WHERE number > 4625 ORDER BY age LIMIT 50
[S]can::select_where_order_desc_limit (2000)Scans5191.974980.7310117.1253.55842.422734.0842303.4947.1+12.5%-11.8%SELECT * FROM record WHERE score < 7.5 ORDER BY number DESC LIMIT 50
[S]can::select_where_multi_order_limit (2000)Scans6271.616008.8311698.1744.35830.962605.0540992.7748.4-7.0%+9.2%SELECT * FROM record WHERE age > 85 AND active = true ORDER BY city, score DESC LIMIT 50
[S]can::select_omit_limit (2000)Scans18.097.0194.0814490.02.832.646.5181951.6-84.4%+465.6%SELECT * OMIT words, embedding, tags FROM record LIMIT 100
[S]can::select_fields_where_limit (2000)Scans16.156.3976.1616178.0111.41110.53253.822305.7+589.8%-85.7%SELECT name, city, age, number FROM record WHERE score > 50.0 LIMIT 100
[S]can::select_order_by (2000)Scans17270.9016482.3033013.7616.15274.941211.3937027.8453.5-69.5%+231.8%SELECT * FROM record ORDER BY number LIMIT 100
[S]can::select_order_by_multi (2000)Scans17387.9216793.6032702.4616.05192.821178.6237158.9153.7-70.1%+235.3%SELECT * FROM record ORDER BY city, number DESC LIMIT 100
[S]can::select_group_count (2000)Scans5989.635799.9411632.6446.45436.811871.8732440.3250.4-9.2%+8.7%SELECT city, count() as total FROM record GROUP BY city
[S]can::select_group_sum (2000)Scans6754.216520.8312361.7341.15635.442951.1724936.4549.2-16.6%+19.8%SELECT city, math::sum(number) as total FROM record GROUP BY city
[S]can::select_group_avg (2000)Scans6766.856553.6012582.9140.95529.881335.3031604.7449.4-18.3%+20.9%SELECT city, math::mean(age) as avg_age FROM record GROUP BY city
[S]can::select_group_multi_agg (2000)Scans9738.269363.4518513.9228.56203.124567.0428491.7843.8-36.3%+53.4%SELECT city, math::sum(number) as s1, math::sum(number) as s2, math::mean(age) as avg_age FROM record GROUP BY city
[S]can::select_group_all (2000)Scans7492.087221.2513910.0137.15561.682719.7421889.0249.5-25.8%+33.7%SELECT count() as c, math::sum(number) as total, math::mean(age) as avg_age FROM record GROUP ALL
[S]can::select_group_order_limit (2000)Scans7603.127278.5914917.6336.55798.601380.3540796.1648.1-23.7%+31.6%SELECT city, count() as total, math::mean(score) as avg_score FROM record GROUP BY city ORDER BY total DESC LIMIT 5
[S]can::select_group_where (2000)Scans7411.377159.8113770.7537.56459.822660.3528786.6942.7-12.8%+13.8%SELECT city, count() as total, math::sum(number) as sum FROM record WHERE age > 50 GROUP BY city
[S]can::select_group_dedup_agg (2000)Scans9735.389388.0317743.8728.56278.254079.6120692.9943.9-35.5%+54.0%SELECT city, math::sum(number) as total, math::sum(number) as dup1, math::sum(number) as dup2 FROM record GROUP BY city
[S]can::select_split (2000)Scans19.718.3883.0713415.28.856.6832.9628351.9-55.1%+111.3%SELECT * FROM record SPLIT tags LIMIT 100
[S]can::select_fetch (2000)Scans39.0711.35212.486802.67.587.4715.5434134.4-80.6%+401.8%SELECT * FROM record LIMIT 100 FETCH friend
[S]can::select_fetch_where_limit (2000)Scans32.1614.37152.458366.1125.95120.00283.902146.3+291.6%-74.3%SELECT * FROM record WHERE city = 'london' LIMIT 50 FETCH friend
[S]can::graph_out_depth1 (2000)Scans2.301.649.50105137.80.920.862.13235676.0-60.0%+124.2%SELECT ->knows->record FROM record:1
[S]can::graph_out_depth2 (2000)Scans6.162.4244.3242125.81.671.603.43135637.0-72.9%+222.0%SELECT ->knows->record->knows->record FROM record:1
[S]can::graph_out_depth3 (2000)Scans18.027.12130.6215001.73.022.697.8282363.1-83.2%+449.0%SELECT ->knows->record->knows->record->knows->record FROM record:1
[S]can::graph_in_depth1 (2000)Scans2.721.7610.3490892.61.121.052.50208621.0-58.8%+129.5%SELECT <-knows<-record FROM record:1
[S]can::graph_bidirectional (2000)Scans4.222.5419.8657673.01.391.163.68169188.7-67.1%+193.4%SELECT knowsrecord FROM record:1
[S]can::graph_edge_filter (2000)Scans1.191.103.33197478.81.120.983.61189149.0-5.9%-4.2%SELECT ->knows[WHERE weight > 50]->record FROM record:1
[S]can::graph_multi_out (2000)Scans148.6866.241050.621886.323.3221.8459.5811405.3-84.3%+504.6%SELECT id, ->knows->record.name FROM record LIMIT 100
[S]can::graph_multi_out_where (2000)Scans51.3831.45357.125401.814.4011.6258.0218555.1-72.0%+243.5%SELECT id, ->knows->(? WHERE number > 2500) FROM record LIMIT 50
[S]can::graph_multi_count (2000)Scans33.748.24239.628098.39.329.0219.8628157.6-72.4%+247.7%SELECT id, count(->knows) as edge_count FROM record LIMIT 100
[S]can::graph_depth2_limit (2000)Scans78.8335.71558.593529.79.418.6327.0928104.7-88.1%+696.2%SELECT id, ->knows->record->knows->record FROM record LIMIT 20
[S]can::graph_sub_where (2000)Scans2.672.259.0991237.92.011.874.53120503.8-24.7%+32.1%SELECT name, ->knows->(SELECT * FROM record WHERE number > 2500) FROM record:1
[S]can::graph_sub_group_all (2000)Scans3.361.6312.3974807.01.931.894.13126560.9-42.6%+69.2%SELECT ->knows->(SELECT COUNT() as c, math::mean(number) as avg_num FROM record GROUP ALL) FROM record:1
[S]can::graph_sub_group_by (2000)Scans3.251.8317.5578504.31.881.784.21126496.6-42.2%+61.1%SELECT ->knows->(SELECT COUNT() as c, math::mean(number) as avg_num, city FROM record GROUP BY city) FROM record:1
[S]can::subquery_inline (2000)Scans724.85171.014681.73377.8368.70364.80747.01738.3-49.1%+95.4%SELECT *, (SELECT * FROM small_table) as sub FROM record LIMIT 100
[S]can::subquery_count (2000)Scans28.496.78192.009262.461.7260.73117.504406.4+116.6%-52.4%SELECT *, (SELECT count() FROM small_table GROUP ALL) as sub_count FROM record LIMIT 100
[S]can::subquery_from (2000)Scans2918.722549.767938.0594.61801.281751.044151.30152.2-38.3%+60.9%SELECT city, math::mean(number) as avg FROM (SELECT * FROM record WHERE age > 50 LIMIT 10000) GROUP BY city
[S]can::pipeline_filter_group_order (2000)Scans9341.458806.4018595.8429.76613.751528.8347284.2242.0-29.2%+41.6%SELECT city, count() as c, math::mean(age) as avg_age FROM record WHERE number > 1000 GROUP BY city ORDER BY c DESC LIMIT 5
[S]can::index_standard (2000)Scans3910.213764.227561.2271.05241.543006.4619890.1751.9+34.0%-26.9%number = 42
[I]ndex::index_standardIndex Build/Remove1003.261003.521003.521.0504.19504.32504.322.0-49.7%+98.0%number = 42
[S]can::index_standard::indexed (2000)Indexed Scans4.112.8315.3861150.42.361.369.5498303.1-42.6%+60.8%number = 42
[R]emoveIndex::index_standardIndex Build/Remove380.54380.67380.672.680.6780.7080.7012.4-78.8%+370.7%number = 42
[S]can::index_composite (2000)Scans6101.195812.2211747.3345.76171.272127.8732555.0143.8+1.1%-4.2%city = 'london' AND age > 50
[I]ndex::index_compositeIndex Build/Remove502.14502.27502.272.0506.50506.62506.622.0+0.9%-1.0%city = 'london' AND age > 50
[S]can::index_composite::indexed (2000)Indexed Scans1504.21469.258138.75176.7745.47352.772779.14366.3-50.4%+107.2%city = 'london' AND age > 50
[R]emoveIndex::index_compositeIndex Build/Remove338.82338.94338.943.089.5089.5389.5311.2-73.6%+278.3%city = 'london' AND age > 50
[S]can::index_range_merged (2000)Scans7595.907274.4914712.8336.55641.661303.5535946.5048.7-25.7%+33.5%number > 100 AND number < 200
[I]ndex::index_range_mergedIndex Build/Remove1004.291004.541004.541.0502.14502.27502.272.0-50.0%+99.0%number > 100 AND number < 200
[S]can::index_range_merged::indexed (2000)Indexed Scans478.07187.012099.20555.2227.8098.501127.421190.4-52.4%+114.4%number > 100 AND number < 200
[R]emoveIndex::index_range_mergedIndex Build/Remove360.32360.45360.452.892.1992.2292.2210.8-74.4%+289.6%number > 100 AND number < 200
[S]can::index_in (2000)Scans7232.846938.6213795.3338.45404.261276.9338699.0151.5-25.3%+34.1%number IN [42, 100, 200, 300, 400]
[I]ndex::index_inIndex Build/Remove1004.291004.541004.541.0502.40502.53502.532.0-50.0%+99.0%number IN [42, 100, 200, 300, 400]
[S]can::index_in::indexed (2000)Indexed Scans25.358.71113.9810437.89.739.1825.3127169.0-61.6%+160.3%number IN [42, 100, 200, 300, 400]
[R]emoveIndex::index_inIndex Build/Remove373.89374.01374.012.782.7882.8182.8112.1-77.9%+352.1%number IN [42, 100, 200, 300, 400]
[I]ndex::index_fulltextIndex Build/Remove51134.4651150.8551150.850.03511.303512.323512.320.3-93.1%+1300.0%words @@ 'hello'
[S]can::index_fulltext::indexed (2000)Indexed Scans225.31212.48508.161216.5760.70709.631723.39373.8+237.6%-69.3%words @@ 'hello'
[R]emoveIndex::index_fulltextIndex Build/Remove845.06845.31845.311.21679.871680.381680.380.6+98.8%-49.2%words @@ 'hello'
[I]ndex::index_hnswIndex Build/Remove18604.0318612.2218612.220.112529.6612533.7612533.760.1-32.7%+60.0%SELECT * FROM record WHERE embedding [0.5, 0.3, 0.7, 0.1]
[S]can::index_hnsw::indexed (2000)Indexed Scans38581.6138731.7846137.347.34847.092347.0116023.5556.3-87.4%+671.0%SELECT * FROM record WHERE embedding [0.5, 0.3, 0.7, 0.1]
[R]emoveIndex::index_hnswIndex Build/Remove2190.342191.362191.360.5390.78390.91390.912.6-82.2%+456.5%SELECT * FROM record WHERE embedding [0.5, 0.3, 0.7, 0.1]

SurrealKV

TestCategorySurrealDB 2SurrealDB 2SurrealDB 2SurrealDB 2SurrealDB 3SurrealDB 3SurrealDB 3SurrealDB 3Mean %OPS %Query
MeanP50P99OPSMeanP50P99OPS
[C]reateCRUD221.27221.44227.581299.78.258.0114.3334830.7-96.3%+2579.9%
[R]eadCRUD0.340.311.11828781.50.600.561.45477557.1+76.5%-42.4%
[U]pdateCRUD222.01222.85229.251295.38.378.1414.2434330.5-96.2%+2550.3%
[S]can::count_all (2000)Scans303.53300.54392.70917.2142.7373.79889.341738.6-53.0%+89.6%
[S]can::limit_id (2000)Scans6.515.7419.4140313.32.071.934.33108861.9-68.2%+170.0%
[S]can::limit_all (2000)Scans16.518.02103.1716063.42.562.494.9889964.2-84.5%+460.1%
[S]can::limit_start_id (2000)Scans14.2713.7022.2218977.05.825.6113.6144194.4-59.2%+132.9%
[S]can::limit_start_all (2000)Scans21.4820.5636.9012560.36.105.6316.0842894.7-71.6%+241.5%
[S]can::select_where_id (2000)Scans0.510.481.02400808.00.710.641.62299184.1+39.2%-25.4%SELECT * FROM record:42
[S]can::select_where_id_eq (2000)Scans3478.943497.983784.7081.60.690.651.58320960.5-100.0%+393137.5%SELECT * FROM record WHERE id = record:42
[S]can::select_where_gt (2000)Scans4666.154681.735169.1560.86071.581438.7237355.5245.0+30.1%-25.9%SELECT * FROM record WHERE number > 4625
[S]can::select_where_in (2000)Scans9275.429306.1111386.8830.56071.932686.9725214.9745.3-34.5%+48.6%SELECT * FROM record WHERE age IN [5, 15, 25, 35, 45, 55, 65]
[S]can::select_where_multi_and (2000)Scans5509.795570.566885.3851.26044.501395.7129261.8245.5+9.7%-11.1%SELECT * FROM record WHERE city = 'london' AND age > 50
[S]can::select_where_order_limit (2000)Scans4650.074677.635210.1161.05504.011259.5239682.0550.3+18.4%-17.6%SELECT * FROM record WHERE number > 4625 ORDER BY age LIMIT 50
[S]can::select_where_order_desc_limit (2000)Scans4623.384640.775144.5761.45777.772637.8229245.4448.3+25.0%-21.4%SELECT * FROM record WHERE score < 7.5 ORDER BY number DESC LIMIT 50
[S]can::select_where_multi_order_limit (2000)Scans5692.975726.216410.2449.85590.581268.7339518.2150.1-1.8%+0.5%SELECT * FROM record WHERE age > 85 AND active = true ORDER BY city, score DESC LIMIT 50
[S]can::select_omit_limit (2000)Scans17.2010.7569.5715569.43.973.1311.8964774.7-76.9%+316.0%SELECT * OMIT words, embedding, tags FROM record LIMIT 100
[S]can::select_fields_where_limit (2000)Scans16.0615.8732.0916653.9103.59102.59230.532450.0+545.0%-85.3%SELECT name, city, age, number FROM record WHERE score > 50.0 LIMIT 100
[S]can::select_order_by (2000)Scans15714.2415343.6126968.0617.95016.711245.1835815.4255.5-68.1%+210.7%SELECT * FROM record ORDER BY number LIMIT 100
[S]can::select_order_by_multi (2000)Scans15943.9115663.1025149.4417.65055.991144.8335782.6555.3-68.3%+214.1%SELECT * FROM record ORDER BY city, number DESC LIMIT 100
[S]can::select_group_count (2000)Scans4391.264411.394841.4764.75236.941296.3824690.6953.0+19.3%-18.1%SELECT city, count() as total FROM record GROUP BY city
[S]can::select_group_sum (2000)Scans5024.475050.375595.1456.55485.561305.6021790.7250.4+9.2%-10.9%SELECT city, math::sum(number) as total FROM record GROUP BY city
[S]can::select_group_avg (2000)Scans5023.775042.185591.0456.55437.212476.0320905.9850.5+8.2%-10.6%SELECT city, math::mean(age) as avg_age FROM record GROUP BY city
[S]can::select_group_multi_agg (2000)Scans7942.627962.6210067.9735.66081.361482.7534471.9345.0-23.4%+26.5%SELECT city, math::sum(number) as s1, math::sum(number) as s2, math::mean(age) as avg_age FROM record GROUP BY city
[S]can::select_group_all (2000)Scans5757.575779.456496.2649.25418.852301.9526050.5651.0-5.9%+3.6%SELECT count() as c, math::sum(number) as total, math::mean(age) as avg_age FROM record GROUP ALL
[S]can::select_group_order_limit (2000)Scans5832.725853.186516.7348.65534.301387.5240239.1049.6-5.1%+2.0%SELECT city, count() as total, math::mean(score) as avg_score FROM record GROUP BY city ORDER BY total DESC LIMIT 5
[S]can::select_group_where (2000)Scans6046.346070.276877.1846.96354.953026.9442303.4943.5+5.1%-7.2%SELECT city, count() as total, math::sum(number) as sum FROM record WHERE age > 50 GROUP BY city
[S]can::select_group_dedup_agg (2000)Scans7959.497987.209388.0335.56055.392525.1824264.7045.0-23.9%+26.7%SELECT city, math::sum(number) as total, math::sum(number) as dup1, math::sum(number) as dup2 FROM record GROUP BY city
[S]can::select_split (2000)Scans18.0010.8479.1014812.08.667.4929.5428485.5-51.9%+92.3%SELECT * FROM record SPLIT tags LIMIT 100
[S]can::select_fetch (2000)Scans35.9915.23158.347481.38.968.9416.7229309.5-75.1%+291.8%SELECT * FROM record LIMIT 100 FETCH friend
[S]can::select_fetch_where_limit (2000)Scans30.7019.18225.798690.9114.08109.06249.732375.0+271.6%-72.7%SELECT * FROM record WHERE city = 'london' LIMIT 50 FETCH friend
[S]can::graph_out_depth1 (2000)Scans5.004.4715.5452630.31.551.394.02148160.1-69.0%+181.5%SELECT ->knows->record FROM record:1
[S]can::graph_out_depth2 (2000)Scans14.7012.6739.9418594.43.293.167.6277306.4-77.6%+315.8%SELECT ->knows->record->knows->record FROM record:1
[S]can::graph_out_depth3 (2000)Scans42.0441.4173.286604.08.308.2117.1531839.6-80.3%+382.1%SELECT ->knows->record->knows->record->knows->record FROM record:1
[S]can::graph_in_depth1 (2000)Scans4.953.7419.1253273.61.431.402.81161031.4-71.1%+202.3%SELECT <-knows<-record FROM record:1
[S]can::graph_bidirectional (2000)Scans11.149.7934.4624621.82.542.545.1097824.2-77.2%+297.3%SELECT knowsrecord FROM record:1
[S]can::graph_edge_filter (2000)Scans2.411.689.48100491.21.231.213.08188866.9-49.0%+87.9%SELECT ->knows[WHERE weight > 50]->record FROM record:1
[S]can::graph_multi_out (2000)Scans436.45437.76549.38643.770.4846.37350.983918.3-83.9%+508.7%SELECT id, ->knows->record.name FROM record LIMIT 100
[S]can::graph_multi_out_where (2000)Scans173.91173.44235.391610.236.8330.30136.327433.1-78.8%+361.6%SELECT id, ->knows->(? WHERE number > 2500) FROM record LIMIT 50
[S]can::graph_multi_count (2000)Scans94.2094.33127.232968.216.7016.8334.3015934.7-82.3%+436.8%SELECT id, count(->knows) as edge_count FROM record LIMIT 100
[S]can::graph_depth2_limit (2000)Scans246.73247.42315.131139.247.0740.61126.975844.6-80.9%+413.0%SELECT id, ->knows->record->knows->record FROM record LIMIT 20
[S]can::graph_sub_where (2000)Scans5.504.2122.6148232.82.102.014.43115625.9-61.8%+139.7%SELECT name, ->knows->(SELECT * FROM record WHERE number > 2500) FROM record:1
[S]can::graph_sub_group_all (2000)Scans5.844.9822.1145269.52.272.165.96109630.7-61.1%+142.2%SELECT ->knows->(SELECT COUNT() as c, math::mean(number) as avg_num FROM record GROUP ALL) FROM record:1
[S]can::graph_sub_group_by (2000)Scans5.904.6023.8944952.62.212.125.83108263.4-62.5%+140.8%SELECT ->knows->(SELECT COUNT() as c, math::mean(number) as avg_num, city FROM record GROUP BY city) FROM record:1
[S]can::subquery_inline (2000)Scans596.32601.60757.76471.7366.11367.36721.41744.9-38.6%+57.9%SELECT *, (SELECT * FROM small_table) as sub FROM record LIMIT 100
[S]can::subquery_count (2000)Scans75.3575.5291.523716.042.7331.77160.006505.7-43.3%+75.1%SELECT *, (SELECT count() FROM small_table GROUP ALL) as sub_count FROM record LIMIT 100
[S]can::subquery_from (2000)Scans2723.412576.386225.92102.01730.691651.714349.95157.1-36.5%+54.1%SELECT city, math::mean(number) as avg FROM (SELECT * FROM record WHERE age > 50 LIMIT 10000) GROUP BY city
[S]can::pipeline_filter_group_order (2000)Scans7624.097667.719846.7837.06318.852078.7245940.7443.4-17.1%+17.3%SELECT city, count() as c, math::mean(age) as avg_age FROM record WHERE number > 1000 GROUP BY city ORDER BY c DESC LIMIT 5
[S]can::index_standard (2000)Scans3454.123471.363756.0382.35071.342295.8118661.3853.8+46.8%-34.6%number = 42
[I]ndex::index_standardIndex Build/Remove1513.981514.491514.490.71008.381008.641008.641.0-33.4%+50.0%number = 42
[S]can::index_standard::indexed (2000)Indexed Scans6.043.6030.4543641.02.642.336.5683230.9-56.3%+90.7%number = 42
[R]emoveIndex::index_standardIndex Build/Remove445.31445.44445.442.2102.82102.85102.859.7-76.9%+332.6%number = 42
[S]can::index_composite (2000)Scans5489.275513.226172.6751.66131.842908.1625247.7444.7+11.7%-13.5%city = 'london' AND age > 50
[I]ndex::index_compositeIndex Build/Remove1513.981514.491514.490.71011.971012.221012.221.0-33.2%+50.0%city = 'london' AND age > 50
[S]can::index_composite::indexed (2000)Indexed Scans1509.671138.696754.30183.21976.731980.412918.40141.0+30.9%-23.1%city = 'london' AND age > 50
[R]emoveIndex::index_compositeIndex Build/Remove421.76421.89421.892.4148.54148.61148.616.7-64.8%+184.0%city = 'london' AND age > 50
[S]can::index_range_merged (2000)Scans7044.307139.3311755.5239.85572.122281.4728049.4149.1-20.9%+23.4%number > 100 AND number < 200
[I]ndex::index_range_mergedIndex Build/Remove1513.981514.491514.490.71012.481012.741012.741.0-33.1%+50.0%number > 100 AND number < 200
[S]can::index_range_merged::indexed (2000)Indexed Scans453.72293.383262.46608.9608.72602.621054.72457.5+34.2%-24.9%number > 100 AND number < 200
[R]emoveIndex::index_range_mergedIndex Build/Remove445.06445.18445.182.2146.75146.81146.816.8-67.0%+202.7%number > 100 AND number < 200
[S]can::index_in (2000)Scans6744.016819.849224.1941.85253.104562.9413754.3752.5-22.1%+25.5%number IN [42, 100, 200, 300, 400]
[I]ndex::index_inIndex Build/Remove1512.961513.471513.470.71011.971012.221012.221.0-33.1%+50.0%number IN [42, 100, 200, 300, 400]
[S]can::index_in::indexed (2000)Indexed Scans30.9226.13146.308937.512.7311.1934.2721115.7-58.8%+136.3%number IN [42, 100, 200, 300, 400]
[R]emoveIndex::index_inIndex Build/Remove456.32456.45456.452.2131.52131.58131.587.6-71.2%+247.0%number IN [42, 100, 200, 300, 400]
[I]ndex::index_fulltextIndex Build/Remove55689.2255705.6055705.600.081756.1681788.9381788.930.0+46.8%-50.0%words @@ 'hello'
[S]can::index_fulltext::indexed (2000)Indexed Scans204.39192.25460.541343.2296.32289.79470.27942.0+45.0%-29.9%words @@ 'hello'
[R]emoveIndex::index_fulltextIndex Build/Remove969.98970.24970.241.02239.492240.512240.510.5+130.9%-56.3%words @@ 'hello'
[I]ndex::index_hnswIndex Build/Remove19587.0719595.2619595.260.113545.4713549.5713549.570.1-30.8%+40.0%SELECT * FROM record WHERE embedding [0.5, 0.3, 0.7, 0.1]
[S]can::index_hnsw::indexed (2000)Indexed Scans35710.9135880.9642565.637.94622.452022.4020660.2259.4-87.1%+654.4%SELECT * FROM record WHERE embedding [0.5, 0.3, 0.7, 0.1]
[R]emoveIndex::index_hnswIndex Build/Remove2509.822510.852510.850.4820.48820.74820.741.2-67.3%+205.0%SELECT * FROM record WHERE embedding [0.5, 0.3, 0.7, 0.1]

RocksDB

TestCategorySurrealDB v2SurrealDB v3Mean % v3 vs. v2OPS % v3. vs v2Query
Mean (ms)P50 (ms)P99 (ms)Operations Per Second (OPS)Mean (ms)P50 (ms)P99 (ms)Operations Per Second (OPS)
[C]reateCRUD9.997.4027.2628778.28.528.1215.0533768.3-14.7%+17.3%
[R]eadCRUD0.370.350.96757343.30.590.561.32484147.0+59.5%-36.1%
[U]pdateCRUD8.347.3724.2134370.88.508.0715.2833828.1+1.9%-1.6%
[S]can::count_all (2001)Scans153.00152.57206.721821.8132.7479.17836.101771.7-13.2%-2.7%
[S]can::limit_id (2001)Scans5.323.5019.3848199.91.941.874.31123328.7-63.5%+155.9%
[S]can::limit_all (2001)Scans16.295.7493.2516194.52.452.375.1795975.1-85.0%+492.6%
[S]can::limit_start_id (2001)Scans12.0811.4519.8622395.36.075.8913.2742606.3-49.8%+90.2%
[S]can::limit_start_all (2001)Scans20.4519.5236.6713197.66.286.3511.6540970.0-69.3%+210.4%
[S]can::select_where_id (2001)Scans0.630.601.45334303.50.750.721.62291755.1+19.0%-12.7%SELECT * FROM record:42
[S]can::select_where_id_eq (2001)Scans3313.503328.003667.9785.70.720.681.43302257.9-100.0%+352510.7%SELECT * FROM record WHERE id = record:42
[S]can::select_where_gt (2001)Scans4576.384595.715132.2962.05955.451940.4834865.1545.2+30.1%-27.0%SELECT * FROM record WHERE number > 4625
[S]can::select_where_in (2001)Scans9134.119035.7714049.2830.75944.411398.7826902.5346.0-34.9%+50.0%SELECT * FROM record WHERE age IN [5, 15, 25, 35, 45, 55, 65]
[S]can::select_where_multi_and (2001)Scans5394.015439.496299.6552.46047.012850.8229048.8345.5+12.1%-13.1%SELECT * FROM record WHERE city = 'london' AND age > 50
[S]can::select_where_order_limit (2001)Scans4543.694571.145328.9062.45556.692512.8939550.9750.3+22.3%-19.4%SELECT * FROM record WHERE number > 4625 ORDER BY age LIMIT 50
[S]can::select_where_order_desc_limit (2001)Scans4505.674526.085054.4663.05695.881316.8640271.8749.1+26.4%-22.0%SELECT * FROM record WHERE score < 7.5 ORDER BY number DESC LIMIT 50
[S]can::select_where_multi_order_limit (2001)Scans5628.405632.006496.2650.45495.202529.2839354.3750.4-2.4%+0.0%SELECT * FROM record WHERE age > 85 AND active = true ORDER BY city, score DESC LIMIT 50
[S]can::select_omit_limit (2001)Scans16.688.0266.9416042.63.853.558.4166553.8-76.9%+314.9%SELECT * OMIT words, embedding, tags FROM record LIMIT 100
[S]can::select_fields_where_limit (2001)Scans14.198.23104.0018728.2104.58105.79229.252460.3+637.0%-86.9%SELECT name, city, age, number FROM record WHERE score > 50.0 LIMIT 100
[S]can::select_order_by (2001)Scans15667.4115466.5024051.7117.94932.921131.5235454.9756.2-68.5%+214.1%SELECT * FROM record ORDER BY number LIMIT 100
[S]can::select_order_by_multi (2001)Scans15902.2715859.7123724.0317.65037.951140.7335782.6555.7-68.3%+215.8%SELECT * FROM record ORDER BY city, number DESC LIMIT 100
[S]can::select_group_count (2001)Scans4235.104255.744685.8267.05091.892222.0828442.6253.6+20.2%-20.1%SELECT city, count() as total FROM record GROUP BY city
[S]can::select_group_sum (2001)Scans4881.454902.915513.2258.15309.392457.6024150.0151.4+8.8%-11.6%SELECT city, math::sum(number) as total FROM record GROUP BY city
[S]can::select_group_avg (2001)Scans4900.754927.495853.1857.85371.332373.6323150.5951.3+9.6%-11.3%SELECT city, math::mean(age) as avg_age FROM record GROUP BY city
[S]can::select_group_multi_agg (2001)Scans7782.857692.2911689.9836.06111.422177.0243122.6945.4-21.5%+25.9%SELECT city, math::sum(number) as s1, math::sum(number) as s2, math::mean(age) as avg_age FROM record GROUP BY city
[S]can::select_group_all (2001)Scans5624.715648.386807.5550.35421.681243.1338567.9351.5-3.6%+2.5%SELECT count() as c, math::sum(number) as total, math::mean(age) as avg_age FROM record GROUP ALL
[S]can::select_group_order_limit (2001)Scans5721.245746.696877.1849.55529.631285.1239813.1250.2-3.3%+1.5%SELECT city, count() as total, math::mean(score) as avg_score FROM record GROUP BY city ORDER BY total DESC LIMIT 5
[S]can::select_group_where (2001)Scans5949.825980.167020.5447.56311.331435.6544924.9344.0+6.1%-7.3%SELECT city, count() as total, math::sum(number) as sum FROM record WHERE age > 50 GROUP BY city
[S]can::select_group_dedup_agg (2001)Scans7813.477782.4012566.5335.86125.321422.3443352.0645.6-21.6%+27.4%SELECT city, math::sum(number) as total, math::sum(number) as dup1, math::sum(number) as dup2 FROM record GROUP BY city
[S]can::select_split (2001)Scans17.458.5276.6715307.98.707.9026.9627658.5-50.1%+80.7%SELECT * FROM record SPLIT tags LIMIT 100
[S]can::select_fetch (2001)Scans35.1918.22154.247564.18.067.6517.2532357.4-77.1%+327.8%SELECT * FROM record LIMIT 100 FETCH friend
[S]can::select_fetch_where_limit (2001)Scans30.1117.47157.828892.6113.41112.96241.532388.4+276.7%-73.1%SELECT * FROM record WHERE city = 'london' LIMIT 50 FETCH friend
[S]can::graph_out_depth1 (2001)Scans4.783.4723.9754273.41.000.902.71235728.9-79.1%+334.3%SELECT ->knows->record FROM record:1
[S]can::graph_out_depth2 (2001)Scans14.3913.4041.3819118.81.731.643.96136232.2-88.0%+612.6%SELECT ->knows->record->knows->record FROM record:1
[S]can::graph_out_depth3 (2001)Scans41.4240.6470.856724.32.782.666.2289577.1-93.3%+1232.1%SELECT ->knows->record->knows->record->knows->record FROM record:1
[S]can::graph_in_depth1 (2001)Scans4.903.2631.6854000.11.121.062.17191369.2-77.1%+254.4%SELECT <-knows<-record FROM record:1
[S]can::graph_bidirectional (2001)Scans10.628.9637.0525771.51.141.122.48194426.5-89.3%+654.4%SELECT knowsrecord FROM record:1
[S]can::graph_edge_filter (2001)Scans2.371.6210.97102130.81.021.022.23227036.8-57.0%+122.3%SELECT ->knows[WHERE weight > 50]->record FROM record:1
[S]can::graph_multi_out (2001)Scans426.98427.26556.54659.423.0222.2457.7011551.7-94.6%+1651.9%SELECT id, ->knows->record.name FROM record LIMIT 100
[S]can::graph_multi_out_where (2001)Scans170.00169.98229.631648.713.9912.9043.0419133.9-91.8%+1060.6%SELECT id, ->knows->(? WHERE number > 2500) FROM record LIMIT 50
[S]can::graph_multi_count (2001)Scans90.2690.11127.303098.78.968.5719.9028929.8-90.1%+833.6%SELECT id, count(->knows) as edge_count FROM record LIMIT 100
[S]can::graph_depth2_limit (2001)Scans238.54238.21314.881176.59.828.5827.3427000.0-95.9%+2195.0%SELECT id, ->knows->record->knows->record FROM record LIMIT 20
[S]can::graph_sub_where (2001)Scans5.404.3320.8649395.52.171.975.13111209.4-59.8%+125.1%SELECT name, ->knows->(SELECT * FROM record WHERE number > 2500) FROM record:1
[S]can::graph_sub_group_all (2001)Scans5.724.2918.5646438.12.122.006.71113671.0-62.9%+144.8%SELECT ->knows->(SELECT COUNT() as c, math::mean(number) as avg_num FROM record GROUP ALL) FROM record:1
[S]can::graph_sub_group_by (2001)Scans5.654.1425.3447104.91.981.864.98121859.2-65.0%+158.7%SELECT ->knows->(SELECT COUNT() as c, math::mean(number) as avg_num, city FROM record GROUP BY city) FROM record:1
[S]can::subquery_inline (2001)Scans585.17592.381038.85476.7352.05215.171627.13774.7-39.8%+62.5%SELECT *, (SELECT * FROM small_table) as sub FROM record LIMIT 100
[S]can::subquery_count (2001)Scans71.0271.3685.823947.063.5066.9475.844299.9-10.6%+8.9%SELECT *, (SELECT count() FROM small_table GROUP ALL) as sub_count FROM record LIMIT 100
[S]can::subquery_from (2001)Scans2708.902523.146467.58102.41731.461722.374065.28157.7-36.1%+53.9%SELECT city, math::mean(number) as avg FROM (SELECT * FROM record WHERE age > 50 LIMIT 10000) GROUP BY city
[S]can::pipeline_filter_group_order (2001)Scans7471.207467.019805.8237.66498.494476.9337322.7542.9-13.0%+13.8%SELECT city, count() as c, math::mean(age) as avg_age FROM record WHERE number > 1000 GROUP BY city ORDER BY c DESC LIMIT 5
[S]can::index_standard (2001)Scans3307.643323.903678.2185.95077.274583.4213803.5253.8+53.5%-37.3%number = 42
[I]ndex::index_standardIndex Build/Remove1010.431010.691010.691.01011.971012.221012.221.0+0.2%+0.0%number = 42
[S]can::index_standard::indexed (2001)Indexed Scans6.953.8545.4138213.82.221.329.04107603.5-68.1%+181.6%number = 42
[R]emoveIndex::index_standardIndex Build/Remove275.84275.97275.973.6217.79217.85217.854.6-21.0%+26.8%number = 42
[S]can::index_composite (2001)Scans5403.805427.206381.5752.45945.421396.7325198.5945.9+10.0%-12.3%city = 'london' AND age > 50
[I]ndex::index_compositeIndex Build/Remove1006.341006.591006.591.01018.111018.371018.371.0+1.2%-1.0%city = 'london' AND age > 50
[S]can::index_composite::indexed (2001)Indexed Scans1494.501143.815808.13184.7762.39325.633713.02362.3-49.0%+96.1%city = 'london' AND age > 50
[R]emoveIndex::index_compositeIndex Build/Remove769.28769.53769.531.3223.81223.87223.874.5-70.9%+243.8%city = 'london' AND age > 50
[S]can::index_range_merged (2001)Scans6974.036995.979388.0340.35446.441481.7328508.1650.5-21.9%+25.3%number > 100 AND number < 200
[I]ndex::index_range_mergedIndex Build/Remove1012.481012.741012.741.01014.531014.781014.781.0+0.2%+0.0%number > 100 AND number < 200
[S]can::index_range_merged::indexed (2001)Indexed Scans456.57231.422619.39595.9237.8686.461260.541148.8-47.9%+92.8%number > 100 AND number < 200
[R]emoveIndex::index_range_mergedIndex Build/Remove271.74271.87271.873.7216.13216.19216.194.6-20.5%+25.5%number > 100 AND number < 200
[S]can::index_in (2001)Scans6644.396696.9610321.9242.35037.321242.1132260.1053.8-24.2%+27.4%number IN [42, 100, 200, 300, 400]
[I]ndex::index_inIndex Build/Remove1010.431010.691010.691.01011.971012.221012.221.0+0.2%+0.0%number IN [42, 100, 200, 300, 400]
[S]can::index_in::indexed (2001)Indexed Scans30.1524.11144.389168.99.008.7221.4129209.8-70.1%+218.6%number IN [42, 100, 200, 300, 400]
[R]emoveIndex::index_inIndex Build/Remove279.94280.06280.063.6228.03228.09228.094.4-18.5%+22.7%number IN [42, 100, 200, 300, 400]
[I]ndex::index_fulltextIndex Build/Remove73302.0273334.7873334.780.094273.5494306.3094306.300.0+28.6%+0.0%words @@ 'hello'
[S]can::index_fulltext::indexed (2001)Indexed Scans217.18202.37485.631267.2199.83192.90408.061388.3-8.0%+9.6%words @@ 'hello'
[R]emoveIndex::index_fulltextIndex Build/Remove435.07435.20435.202.33038.213039.233039.230.3+598.3%-85.7%words @@ 'hello'
[I]ndex::index_hnswIndex Build/Remove15544.3215548.4215548.420.114544.9014548.9914548.990.1-6.4%+16.7%SELECT * FROM record WHERE embedding [0.5, 0.3, 0.7, 0.1]
[S]can::index_hnsw::indexed (2001)Indexed Scans35562.0235782.6542139.657.94524.913153.9215179.7760.0-87.3%+659.9%SELECT * FROM record WHERE embedding [0.5, 0.3, 0.7, 0.1]
[R]emoveIndex::index_hnswIndex Build/Remove1546.751547.261547.260.71295.871296.381296.380.8-16.2%+18.5%SELECT * FROM record WHERE embedding [0.5, 0.3, 0.7, 0.1]

More performance improvements coming

We have a roadmap of performance-related improvements that we'll be rolling out across the coming minor releases. Here's what to expect:

  • Expanding the new executor to cover write workloads and all remaining statement types

  • End-to-end streaming, pushing the streaming model from internal execution all the way to the client

  • Indexing improvements, including better index planning, reduced scan overhead, expanded index types, and optimised composite index strategies

  • Continued optimisation of the planning and execution pipeline itself

For now, you can start reaping the benefits of the new executor on read-heavy workloads, and expect it to get better with every release.

What’s next?

These benchmarks are just the beginning. This release focuses on measuring the performance gains in SurrealDB 3.0 compared directly to SurrealDB v2.x, highlighting the impact of the new foundations.

Next, we’ll expand this work with a broader benchmarking iteration that compares SurrealDB against other databases and platforms across a wider range of workloads. Stay tuned - there’s much more to come.

If you have any questions, feedback, or want to dive deeper into the results, feel free to reach out to us or join our Discord community to chat directly with the community and team.

Our newsletter

Explore our releases, news, events, and much more