Skip to content

100M-Row Query Benchmark

ReferencePerformanceBenchmarks

This page documents a single recorded run of AetheriusDB’s 31-query analytical suite over a 100,676,159-row dataset — ten times the size of the 10M-Row Query Benchmark — with the exact SQL of every query and the measured latency alongside how many rows each query actually has to process.

CPUApple M4 — 10 cores: 4 performance + 6 efficiency
Memory16 GB unified
StorageExternal SSD — the 14 GB data directory does not sit on the internal NVMe, unlike the 10M run
OSmacOS 26.6 (build 25G72), arm64
BuildAetheriusDB 0.1.105, release profile, dedicated daemon on port 5690
Clientpsql (pgwire), loopback, single client, one query at a time
Memory ceilingAETHERIUS_MAX_MEMORY_BYTES = 8 GiB
Capstatement_timeout=10000 and daemon --query-watchdog-ms 10000
Host at run startload averages: 3.03 6.89 6.57; mem_free=61% pageouts=52446 ; swapfile=total = 9216.00M used = 8212.19M free = 1003.81M (encrypted)
TableRows
order_items64,676,159
orders30,000,000
customers5,000,000
products1,000,000
Total100,676,159

Row counts come from the generator’s own manifest.json, and the live database returns exactly these figures. On disk the data directory is 14 GB.

A gx_ecom graph index is declared over the four foreign-key links before the suite runs, and the run verifies it registered — a plan must actually report a declared edge — rather than trusting the DDL’s return code.

Each query runs five times. Run 0 is the cold sample; the reported warm figure is the median of runs 1–4.

Sorted by query id. Warm is the median of runs 1–4; cold is run 0.

QueryCold msWarm msInput rowsBytes outWhat it measures
Q011180.265,000,00065PK lookup on customers (100 random keys)
Q023650.4030,000,000607Secondary-index lookup: all orders for one customer
Q032090.90100,676,15994FIND ONE ITEM FOR A CUSTOMER — 4-table hop customers→orders→order_items→products, latest first
Q0410810565,676,15959Same needle via denormalized order_items.customer_id (1 hop) — isolates the graph-index gain
Q0569145,000,00056Full-scan filter on UNINDEXED email (row-store vs columnar asymmetry, not a speed test)
Q05b17165,000,00020Companion: COUNT over an unindexed LIKE prefix scan
Q061,16746535,000,0001262-table join: orders ⋈ customers, count by tier
Q075,5871,15399,676,1594653-table join: order_items ⋈ orders ⋈ customers, revenue by country
Q082,3381,517100,676,1598294-table join: revenue by (tier, category) — the full star
Q09366303100,676,1591464-table join WITH selective filter — tests predicate pushdown
Q1068968835,000,00022Anti-join: customers with zero orders (LEFT JOIN … IS NULL)
Q112,5042,48364,676,159280Self-join: product pairs bought together, top 20 (deliberately brutal)
Q1217947100,676,1593,3083-table join + LIMIT 100 (the known 1910 ms weak spot)
Q13150.9164,676,15923COUNT(*) over order_items (largest table)
Q140.770.2230,000,000189Single-key GROUP BY status (6 buckets)
Q1574635,000,0002,1272-key GROUP BY (country, tier) — ~80 buckets
Q16473030,000,0002,245High-cardinality GROUP BY customer_id → top 100 by revenue
Q17962730,000,0001,080GROUP BY … HAVING on an aggregate (tier-scaled cut)
Q181,01099030,000,000812COUNT(DISTINCT customer_id) per month
Q191,31899495,676,159259Conditional aggregation: return rate by category (4-table)
Q200.460.1730,000,00033Timestamp BETWEEN — one-month slice
Q210.160.1730,000,000183Timestamp BETWEEN — 12-month slice, grouped
Q220.140.1830,000,000170Integer range on materialized order_ym (index-friendly)
Q231,09487635,000,000114Compound: range + IN list + LIKE prefix across a join
Q241269230,000,0004,905Top-N: ORDER BY total_cents DESC LIMIT 100 over all orders
Q252,2151,95030,000,0004,607ROW_NUMBER per customer → each customer’s latest order
Q2643136665,676,159466RANK/DENSE_RANK — top 3 products by revenue per category
Q27392230,000,0001,798LAG/LEAD — month-over-month revenue delta
Q28222230,000,0001,375Running total of monthly revenue (ROWS UNBOUNDED PRECEDING)
Q291,6741,6545,000,000213NTILE(10) — customer deciles by lifetime value
Q301,1721,156100,676,1592,120Window over a 4-table join — per-country customer rank
MetricValue (warm query time)
Median63 ms
Mean485 ms
Slowest2,483 ms (Q11)
Sum of all 3115.04 s
Under 1 ms8
Under 100 ms17
Over 1 s6
Killed at the cap0
Empty results0

The distribution is bimodal, and that is the honest shape of it: 8 queries answer in under a millisecond — point lookups and indexed range scans that touch a bounded number of rows — while 6 take over a second, all of them multi-table joins or window functions over tens of millions of rows. There is very little in between.

This is one run on one machine. It is not a certified benchmark score, and the host is a laptop with an external SSD, not server hardware.

Cold and warm differ, and both are shown. Cold figures include first-touch page-cache misses against the external volume. A single cold sample is not a stable measurement — which is why the warm figure is a median of four and the cold is labelled separately rather than averaged in.

Input rows are the sum of the base tables each query reads, not the rows surviving its filters. A query with 100 M input rows and a 1-row answer is doing selective work, not scanning 100 M rows to completion.

Bytes out, not rows out. The harness records the result payload size, which is what it can measure without re-parsing every result set.

Every query below is the exact SQL executed, taken from the run’s own capture.

Q01 — PK lookup on customers (100 random keys)

Section titled “Q01 — PK lookup on customers (100 random keys)”

0.26 ms warm · cold 118 ms · 5,000,000 input rows · 65 bytes returned

SELECT customer_id, email, country, tier, lifetime_value_cents FROM customers WHERE customer_id = 1666666

Q02 — Secondary-index lookup: all orders for one customer

Section titled “Q02 — Secondary-index lookup: all orders for one customer”

0.40 ms warm · cold 365 ms · 30,000,000 input rows · 607 bytes returned

SELECT order_id, order_ts, status, total_cents FROM orders WHERE customer_id = 2500007 ORDER BY order_id

Q03 — FIND ONE ITEM FOR A CUSTOMER — 4-table hop customers→orders→order_items→products, latest first

Section titled “Q03 — FIND ONE ITEM FOR A CUSTOMER — 4-table hop customers→orders→order_items→products, latest first”

0.90 ms warm · cold 209 ms · 100,676,159 input rows · 94 bytes returned

SELECT c.customer_id, c.tier, o.order_id, o.order_ts, p.product_id, p.name, oi.quantity, oi.line_total_cents FROM customers c JOIN orders o ON o.customer_id = c.customer_id JOIN order_items oi ON oi.order_id = o.order_id JOIN products p ON p.product_id = oi.product_id WHERE c.customer_id = 2500007 ORDER BY o.order_ts DESC, oi.order_item_id ASC LIMIT 1

Q04 — Same needle via denormalized order_items.customer_id (1 hop) — isolates the graph-index gain

Section titled “Q04 — Same needle via denormalized order_items.customer_id (1 hop) — isolates the graph-index gain”

105 ms warm · cold 108 ms · 65,676,159 input rows · 59 bytes returned

SELECT oi.order_item_id, oi.order_id, p.product_id, p.name, oi.quantity, oi.line_total_cents FROM order_items oi JOIN products p ON p.product_id = oi.product_id WHERE oi.customer_id = 2500007 ORDER BY oi.order_item_id DESC LIMIT 1

Q05 — Full-scan filter on UNINDEXED email (row-store vs columnar asymmetry, not a speed test)

Section titled “Q05 — Full-scan filter on UNINDEXED email (row-store vs columnar asymmetry, not a speed test)”

14 ms warm · cold 69 ms · 5,000,000 input rows · 56 bytes returned

SELECT customer_id, email, tier FROM customers WHERE email = 'user1666666@mail12.example'

Q05b — Companion: COUNT over an unindexed LIKE prefix scan

Section titled “Q05b — Companion: COUNT over an unindexed LIKE prefix scan”

16 ms warm · cold 17 ms · 5,000,000 input rows · 20 bytes returned

SELECT COUNT(*) FROM customers WHERE email LIKE 'user123%'

Q06 — 2-table join: orders ⋈ customers, count by tier

Section titled “Q06 — 2-table join: orders ⋈ customers, count by tier”

465 ms warm · cold 1,167 ms · 35,000,000 input rows · 126 bytes returned

SELECT c.tier, COUNT(*) AS orders_n, SUM(o.total_cents) AS revenue FROM orders o JOIN customers c ON c.customer_id = o.customer_id GROUP BY c.tier ORDER BY c.tier

Q07 — 3-table join: order_items ⋈ orders ⋈ customers, revenue by country

Section titled “Q07 — 3-table join: order_items ⋈ orders ⋈ customers, revenue by country”

1,153 ms warm · cold 5,587 ms · 99,676,159 input rows · 465 bytes returned

SELECT c.country, COUNT(*) AS lines_n, SUM(oi.line_total_cents) AS revenue FROM order_items oi JOIN orders o ON o.order_id = oi.order_id JOIN customers c ON c.customer_id = o.customer_id GROUP BY c.country ORDER BY c.country

Q08 — 4-table join: revenue by (tier, category) — the full star

Section titled “Q08 — 4-table join: revenue by (tier, category) — the full star”

1,517 ms warm · cold 2,338 ms · 100,676,159 input rows · 829 bytes returned

SELECT c.tier, p.category, COUNT(*) AS lines_n, SUM(oi.line_total_cents) AS revenue FROM order_items oi JOIN orders o ON o.order_id = oi.order_id JOIN customers c ON c.customer_id = o.customer_id JOIN products p ON p.product_id = oi.product_id GROUP BY c.tier, p.category ORDER BY c.tier, p.category

Q09 — 4-table join WITH selective filter — tests predicate pushdown

Section titled “Q09 — 4-table join WITH selective filter — tests predicate pushdown”

303 ms warm · cold 366 ms · 100,676,159 input rows · 146 bytes returned

SELECT p.category, COUNT(*) AS lines_n, SUM(oi.line_total_cents) AS revenue FROM order_items oi JOIN orders o ON o.order_id = oi.order_id JOIN customers c ON c.customer_id = o.customer_id JOIN products p ON p.product_id = oi.product_id WHERE o.order_ym BETWEEN 202401 AND 202403 AND c.tier = 'platinum' AND o.status = 'delivered' GROUP BY p.category ORDER BY p.category

Q10 — Anti-join: customers with zero orders (LEFT JOIN … IS NULL)

Section titled “Q10 — Anti-join: customers with zero orders (LEFT JOIN … IS NULL)”

688 ms warm · cold 689 ms · 35,000,000 input rows · 22 bytes returned

SELECT COUNT(*) AS customers_without_orders FROM customers c LEFT JOIN orders o ON o.customer_id = c.customer_id WHERE o.order_id IS NULL

Q11 — Self-join: product pairs bought together, top 20 (deliberately brutal)

Section titled “Q11 — Self-join: product pairs bought together, top 20 (deliberately brutal)”

2,483 ms warm · cold 2,504 ms · 64,676,159 input rows · 280 bytes returned

SELECT a.product_id AS p1, b.product_id AS p2, COUNT(*) AS pair_n FROM order_items a JOIN order_items b ON b.order_id = a.order_id AND a.product_id < b.product_id GROUP BY a.product_id, b.product_id ORDER BY COUNT(*) DESC, a.product_id ASC, b.product_id ASC LIMIT 20

Q12 — 3-table join + LIMIT 100 (the known 1910 ms weak spot)

Section titled “Q12 — 3-table join + LIMIT 100 (the known 1910 ms weak spot)”

47 ms warm · cold 179 ms · 100,676,159 input rows · 3,308 bytes returned

SELECT o.order_id, c.tier, p.name, oi.line_total_cents FROM order_items oi JOIN orders o ON o.order_id = oi.order_id JOIN customers c ON c.customer_id = o.customer_id JOIN products p ON p.product_id = oi.product_id ORDER BY oi.order_item_id LIMIT 100

Q13 — COUNT(*) over order_items (largest table)

Section titled “Q13 — COUNT(*) over order_items (largest table)”

0.91 ms warm · cold 15 ms · 64,676,159 input rows · 23 bytes returned

SELECT COUNT(*) FROM order_items

Q14 — Single-key GROUP BY status (6 buckets)

Section titled “Q14 — Single-key GROUP BY status (6 buckets)”

0.22 ms warm · cold 0.77 ms · 30,000,000 input rows · 189 bytes returned

SELECT status, COUNT(*) AS n, SUM(total_cents) AS revenue FROM orders GROUP BY status ORDER BY status

Q15 — 2-key GROUP BY (country, tier) — ~80 buckets

Section titled “Q15 — 2-key GROUP BY (country, tier) — ~80 buckets”

63 ms warm · cold 74 ms · 5,000,000 input rows · 2,127 bytes returned

SELECT c.country, c.tier, COUNT(*) AS n, SUM(c.lifetime_value_cents) AS ltv FROM customers c GROUP BY c.country, c.tier ORDER BY c.country, c.tier

Q16 — High-cardinality GROUP BY customer_id → top 100 by revenue

Section titled “Q16 — High-cardinality GROUP BY customer_id → top 100 by revenue”

30 ms warm · cold 47 ms · 30,000,000 input rows · 2,245 bytes returned

SELECT customer_id, SUM(total_cents) AS revenue, COUNT(*) AS orders_n FROM orders GROUP BY customer_id ORDER BY SUM(total_cents) DESC, customer_id ASC LIMIT 100

Q17 — GROUP BY … HAVING on an aggregate (tier-scaled cut)

Section titled “Q17 — GROUP BY … HAVING on an aggregate (tier-scaled cut)”

27 ms warm · cold 96 ms · 30,000,000 input rows · 1,080 bytes returned

SELECT order_ym, SUM(total_cents) AS revenue, COUNT(*) AS n FROM orders GROUP BY order_ym HAVING SUM(total_cents) > 28110000000 ORDER BY order_ym

Q18 — COUNT(DISTINCT customer_id) per month

Section titled “Q18 — COUNT(DISTINCT customer_id) per month”

990 ms warm · cold 1,010 ms · 30,000,000 input rows · 812 bytes returned

SELECT order_ym, COUNT(DISTINCT customer_id) AS uniq_customers, COUNT(*) AS orders_n FROM orders GROUP BY order_ym ORDER BY order_ym

Q19 — Conditional aggregation: return rate by category (4-table)

Section titled “Q19 — Conditional aggregation: return rate by category (4-table)”

994 ms warm · cold 1,318 ms · 95,676,159 input rows · 259 bytes returned

SELECT p.category, SUM(CASE WHEN o.status = 'returned' THEN oi.line_total_cents ELSE 0 END) AS returned_cents, SUM(oi.line_total_cents) AS total_cents, COUNT(*) AS lines_n FROM order_items oi JOIN orders o ON o.order_id = oi.order_id JOIN products p ON p.product_id = oi.product_id GROUP BY p.category ORDER BY p.category

Q20 — Timestamp BETWEEN — one-month slice

Section titled “Q20 — Timestamp BETWEEN — one-month slice”

0.17 ms warm · cold 0.46 ms · 30,000,000 input rows · 33 bytes returned

SELECT COUNT(*) AS n, SUM(total_cents) AS revenue FROM orders WHERE order_ts BETWEEN TIMESTAMP '2024-06-01 00:00:00' AND TIMESTAMP '2024-06-30 23:59:59'

Q21 — Timestamp BETWEEN — 12-month slice, grouped

Section titled “Q21 — Timestamp BETWEEN — 12-month slice, grouped”

0.17 ms warm · cold 0.16 ms · 30,000,000 input rows · 183 bytes returned

SELECT status, COUNT(*) AS n, SUM(total_cents) AS revenue FROM orders WHERE order_ts BETWEEN TIMESTAMP '2024-01-01 00:00:00' AND TIMESTAMP '2024-12-31 23:59:59' GROUP BY status ORDER BY status

Q22 — Integer range on materialized order_ym (index-friendly)

Section titled “Q22 — Integer range on materialized order_ym (index-friendly)”

0.18 ms warm · cold 0.14 ms · 30,000,000 input rows · 170 bytes returned

SELECT order_ym, COUNT(*) AS n, SUM(total_cents) AS revenue FROM orders WHERE order_ym BETWEEN 202401 AND 202406 GROUP BY order_ym ORDER BY order_ym

Q23 — Compound: range + IN list + LIKE prefix across a join

Section titled “Q23 — Compound: range + IN list + LIKE prefix across a join”

876 ms warm · cold 1,094 ms · 35,000,000 input rows · 114 bytes returned

SELECT o.channel, COUNT(*) AS n, SUM(o.total_cents) AS revenue FROM orders o JOIN customers c ON c.customer_id = o.customer_id WHERE o.order_ym BETWEEN 202401 AND 202412 AND o.status IN ('paid', 'shipped', 'delivered') AND c.country IN ('US', 'IN', 'GB') AND c.email LIKE 'user1%' GROUP BY o.channel ORDER BY o.channel

Q24 — Top-N: ORDER BY total_cents DESC LIMIT 100 over all orders

Section titled “Q24 — Top-N: ORDER BY total_cents DESC LIMIT 100 over all orders”

92 ms warm · cold 126 ms · 30,000,000 input rows · 4,905 bytes returned

SELECT order_id, customer_id, order_ts, total_cents FROM orders ORDER BY total_cents DESC, order_id ASC LIMIT 100

Q25 — ROW_NUMBER per customer → each customer’s latest order

Section titled “Q25 — ROW_NUMBER per customer → each customer’s latest order”

1,950 ms warm · cold 2,215 ms · 30,000,000 input rows · 4,607 bytes returned

SELECT customer_id, order_id, order_ts, total_cents FROM ( SELECT customer_id, order_id, order_ts, total_cents, row_number() OVER (PARTITION BY customer_id ORDER BY order_ts DESC, order_id DESC) AS rn FROM orders ) z WHERE rn = 1 ORDER BY customer_id LIMIT 100

Q26 — RANK/DENSE_RANK — top 3 products by revenue per category

Section titled “Q26 — RANK/DENSE_RANK — top 3 products by revenue per category”

366 ms warm · cold 431 ms · 65,676,159 input rows · 466 bytes returned

SELECT category, product_id, revenue, rk FROM ( SELECT p.category, p.product_id, SUM(oi.line_total_cents) AS revenue, rank() OVER (PARTITION BY p.category ORDER BY SUM(oi.line_total_cents) DESC) AS rk FROM order_items oi JOIN products p ON p.product_id = oi.product_id GROUP BY p.category, p.product_id ) z WHERE rk <= 3 ORDER BY category, rk, product_id

Q27 — LAG/LEAD — month-over-month revenue delta

Section titled “Q27 — LAG/LEAD — month-over-month revenue delta”

22 ms warm · cold 39 ms · 30,000,000 input rows · 1,798 bytes returned

SELECT order_ym, SUM(total_cents) AS revenue, lag(SUM(total_cents), 1) OVER (ORDER BY order_ym) AS prev_month, lead(SUM(total_cents), 1) OVER (ORDER BY order_ym) AS next_month FROM orders GROUP BY order_ym ORDER BY order_ym

Q28 — Running total of monthly revenue (ROWS UNBOUNDED PRECEDING)

Section titled “Q28 — Running total of monthly revenue (ROWS UNBOUNDED PRECEDING)”

22 ms warm · cold 22 ms · 30,000,000 input rows · 1,375 bytes returned

SELECT order_ym, SUM(total_cents) AS revenue, SUM(SUM(total_cents)) OVER (ORDER BY order_ym ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS cumulative FROM orders GROUP BY order_ym ORDER BY order_ym

Q29 — NTILE(10) — customer deciles by lifetime value

Section titled “Q29 — NTILE(10) — customer deciles by lifetime value”

1,654 ms warm · cold 1,674 ms · 5,000,000 input rows · 213 bytes returned

SELECT decile, COUNT(*) AS n, MIN(lifetime_value_cents) AS lo, MAX(lifetime_value_cents) AS hi FROM ( SELECT customer_id, lifetime_value_cents, ntile(10) OVER (ORDER BY lifetime_value_cents DESC, customer_id ASC) AS decile FROM customers ) z GROUP BY decile ORDER BY decile

Q30 — Window over a 4-table join — per-country customer rank

Section titled “Q30 — Window over a 4-table join — per-country customer rank”

1,156 ms warm · cold 1,172 ms · 100,676,159 input rows · 2,120 bytes returned

SELECT country, customer_id, revenue, rk FROM ( SELECT c.country, c.customer_id, SUM(oi.line_total_cents) AS revenue, rank() OVER (PARTITION BY c.country ORDER BY SUM(oi.line_total_cents) DESC) AS rk FROM order_items oi JOIN orders o ON o.order_id = oi.order_id JOIN customers c ON c.customer_id = o.customer_id JOIN products p ON p.product_id = oi.product_id GROUP BY c.country, c.customer_id ) z WHERE rk <= 5 ORDER BY country, rk, customer_id