Back to Blog
    Performance
    Philip Moore
    September 4, 2026
    10 min read

    TPC-H 1 TB in 80 Seconds: GizmoSQL on AWS Graviton5 (vs. Graviton4 and Azure Cobalt 100)

    We ran our 22-query TPC-H 1 TB benchmark on one of the first AWS Graviton5 instances, an r9gd.24xlarge: 80.5 seconds for $0.17 of compute. The same afternoon, same DuckDB, a Graviton4 r8gd.24xlarge took 89.8 seconds and an Azure Cobalt 100 E96pds_v6 took 106.8, so Graviton5 is 10% faster than Graviton4 and 25% faster than Cobalt 100, winning all 22 queries. Full methodology and every per-query timing for all three runs.

    Benchmark
    TPC-H
    AWS Graviton5
    Graviton4
    Graviton
    Arm
    GizmoSQL
    DuckDB
    Performance
    GizmoSQL runs the 22-query TPC-H 1 TB benchmark in 80.5 seconds on an AWS Graviton5 r9gd.24xlarge, versus 89.8 seconds on a Graviton4 r8gd.24xlarge and 106.8 seconds on an Azure Cobalt 100 E96pds_v6

    AWS made its Graviton5-based R9g and R9gd instances generally available on August 31, 2026. Four days later I ran our standard TPC-H 1 TB benchmark on one. GizmoSQL ran all 22 queries in a combined 80.475 seconds on a single r9gd.24xlarge. At $7.69 per hour on-demand, that is $0.17 of compute for the whole run.

    To know how much of that is the new chip, I ran the identical benchmark the same afternoon, with the same GizmoSQL and DuckDB build, on two other 96-vCPU Arm machines: the Graviton4 equivalent, an r8gd.24xlarge in the same AWS region, and the Azure Cobalt 100 shape our previously published number came from, a Standard_E96pds_v6. Graviton4 took 89.831 seconds. Cobalt 100 took 106.806 seconds. So on this workload Graviton5 is 10% faster than Graviton4 and 25% faster than Cobalt 100, core for core, and it wins every one of the 22 queries against the Azure box. Because the run is shorter, it is also no more expensive per run despite the highest hourly price of the three.

    This post covers exactly how the benchmark is run, the three machines side by side, every per-query timing from all three runs, and what I think it says about Arm in the cloud.

    The headline numbers

    AWS Graviton5AWS Graviton4Azure Cobalt 100
    Instancer9gd.24xlarger8gd.24xlargeStandard_E96pds_v6
    vCPUs969696
    Memory768 GiB768 GiB672 GiB
    Local NVMe3 × 1,900 GB3 × 1,900 GB6 × 880 GiB
    Regionus-east-2 (Ohio)us-east-2 (Ohio)East US
    DuckDB enginev1.5.5v1.5.5v1.5.5
    On-demand price$7.69 / hour$7.05 / hour$5.62 / hour
    TPC-H 1 TB, 22 queries80.475 s89.831 s106.806 s
    Compute cost per run$0.172$0.176$0.167

    All three machines have 96 vCPUs, all three are memory-optimized shapes with local NVMe, and all three are Arm. All three runs happened within about an hour of each other on September 4, 2026, with the same GizmoSQL build, the same DuckDB v1.5.5 engine, the same data, and the same operator. That is as controlled as a cross-cloud benchmark gets. For the record, our previously published Azure number was 108.88 seconds; today's 106.806 on the same shape shows that DuckDB moved about 2% in the interim, so the gap to Graviton5 is almost entirely hardware.

    Methodology

    I run every cloud the same way, so the numbers are comparable across providers and across time. Nothing here is tuned per query.

    1. Provision a VM with local NVMe

    I pick the instance family in the target cloud with local NVMe drives, because analytics at this scale is a storage-bandwidth problem as much as a CPU problem. On AWS that is the d suffix (r9gd, r8gd); on Azure it is the d in E96pds_v6. Network block storage would add a variable I do not want in the measurement.

    I also deliberately stop at 96 vCPUs. All three of these shapes are a single NUMA node: every core sees every byte of memory at the same latency. The 192-vCPU 48xlarge sizes are two NUMA nodes (two sockets on Graviton4; two memory regions on Graviton5's single 192-core die), and a single DuckDB process that has to pull hash tables across the NUMA boundary does not scale anywhere near linearly. More on that below.

    2. Stripe the NVMe drives into one RAID 0 volume

    The drives arrive as separate raw devices. I combine them into a single RAID 0 array so DuckDB sees one fast volume with the aggregate throughput of all the drives. RAID 0 has no redundancy, which is fine here: the data is a reproducible benchmark set and the volume is ephemeral by design.

    # Three NVMe drives on the r9gd/r8gd 24xlarge; the Azure VM had six
    sudo mdadm --create /dev/md0 --level=0 --raid-devices=3 \
      /dev/nvme1n1 /dev/nvme2n1 /dev/nvme3n1
    sudo mkfs.ext4 -F /dev/md0
    sudo mkdir -p /data && sudo mount /dev/md0 /data

    3. Copy the TPC-H scale factor 1,000 Parquet data onto the volume

    The dataset is TPC-H at scale factor 1,000, roughly 1 TB uncompressed, stored as Parquet with one directory per table. I generate it with our open-source tpch-datagen utility, which uses DuckDB and multiprocessing to produce the tables in parallel, and stage it in object storage so every run starts from identical bytes. Copying it onto the RAID 0 volume is not part of the timed run.

    4. Ingest the Parquet into GizmoSQL tables

    With GizmoSQL pointed at a fresh DuckDB database file on the NVMe volume, I load each table with a CREATE TABLE ... AS SELECT over a glob of that table's Parquet directory. Eight tables, eight statements:

    CREATE TABLE lineitem AS SELECT * FROM read_parquet('/data/tpch/sf1000/lineitem/*.parquet');
    CREATE TABLE orders   AS SELECT * FROM read_parquet('/data/tpch/sf1000/orders/*.parquet');
    CREATE TABLE customer AS SELECT * FROM read_parquet('/data/tpch/sf1000/customer/*.parquet');
    CREATE TABLE part     AS SELECT * FROM read_parquet('/data/tpch/sf1000/part/*.parquet');
    CREATE TABLE partsupp AS SELECT * FROM read_parquet('/data/tpch/sf1000/partsupp/*.parquet');
    CREATE TABLE supplier AS SELECT * FROM read_parquet('/data/tpch/sf1000/supplier/*.parquet');
    CREATE TABLE nation   AS SELECT * FROM read_parquet('/data/tpch/sf1000/nation/*.parquet');
    CREATE TABLE region   AS SELECT * FROM read_parquet('/data/tpch/sf1000/region/*.parquet');

    Ingest time is also not part of the benchmark number. The point of the benchmark is query performance against native DuckDB storage, which is how GizmoSQL runs in production.

    5. Run the 22 queries, in order, three times each

    The runner is our open-source benchmark-flight-sql tool. It connects to the GizmoSQL server over Apache Arrow Flight SQL, exactly like a real client would, and executes the standard 22 TPC-H queries in order. Each query runs three times back to back before moving on to the next. Every run's wall-clock time and row count are written to a JSON file.

    benchmark-flight-sql \
      --hostname ec2-XX-XX-XX-XX.us-east-2.compute.amazonaws.com \
      --port 443 \
      --username scott \
      --password '...' \
      --num-query-runs 3

    6. Average each query, then sum the averages

    For each query I take the mean of its three runs. The benchmark figure is the sum of those 22 means. That is the 80.475 seconds. Note that this is deliberately not "best of three": the first run of each query is usually the slowest because the operating system page cache is cold, and it counts.

    All three, query by query

    Mean of three runs per query, in seconds; shorter is better. Graviton5 is the shortest bar in all 22 groups. The label on the right is how much less time Graviton5 took than Cobalt 100 on that query. Hover a bar for its exact value.

    TPC-H 1 TB per-query mean runtime on Graviton5, Graviton4, and Cobalt 100Horizontal grouped bars for each of the 22 TPC-H queries. Graviton5 is the shortest bar in every group; the label at the right of each group is how much less time Graviton5 took than Cobalt 100.0s3s6s9s12s15sQ1Q1 · Graviton5 r9gd.24xlarge: 2.905 s (mean of 3 runs)Q1 · Graviton4 r8gd.24xlarge: 3.913 s (mean of 3 runs)Q1 · Cobalt 100 E96pds_v6: 4.738 s (mean of 3 runs)−39%Graviton5Graviton4Cobalt 100Q2Q2 · Graviton5 r9gd.24xlarge: 0.624 s (mean of 3 runs)Q2 · Graviton4 r8gd.24xlarge: 0.700 s (mean of 3 runs)Q2 · Cobalt 100 E96pds_v6: 0.851 s (mean of 3 runs)−27%Q3Q3 · Graviton5 r9gd.24xlarge: 2.561 s (mean of 3 runs)Q3 · Graviton4 r8gd.24xlarge: 2.709 s (mean of 3 runs)Q3 · Cobalt 100 E96pds_v6: 3.323 s (mean of 3 runs)−23%Q4Q4 · Graviton5 r9gd.24xlarge: 2.563 s (mean of 3 runs)Q4 · Graviton4 r8gd.24xlarge: 3.184 s (mean of 3 runs)Q4 · Cobalt 100 E96pds_v6: 3.718 s (mean of 3 runs)−31%Q5Q5 · Graviton5 r9gd.24xlarge: 2.237 s (mean of 3 runs)Q5 · Graviton4 r8gd.24xlarge: 2.593 s (mean of 3 runs)Q5 · Cobalt 100 E96pds_v6: 3.273 s (mean of 3 runs)−32%Q6Q6 · Graviton5 r9gd.24xlarge: 0.599 s (mean of 3 runs)Q6 · Graviton4 r8gd.24xlarge: 0.732 s (mean of 3 runs)Q6 · Cobalt 100 E96pds_v6: 0.793 s (mean of 3 runs)−24%Q7Q7 · Graviton5 r9gd.24xlarge: 2.042 s (mean of 3 runs)Q7 · Graviton4 r8gd.24xlarge: 2.272 s (mean of 3 runs)Q7 · Cobalt 100 E96pds_v6: 2.816 s (mean of 3 runs)−27%Q8Q8 · Graviton5 r9gd.24xlarge: 2.115 s (mean of 3 runs)Q8 · Graviton4 r8gd.24xlarge: 2.491 s (mean of 3 runs)Q8 · Cobalt 100 E96pds_v6: 3.381 s (mean of 3 runs)−37%Q9Q9 · Graviton5 r9gd.24xlarge: 13.865 s (mean of 3 runs)Q9 · Graviton4 r8gd.24xlarge: 14.744 s (mean of 3 runs)Q9 · Cobalt 100 E96pds_v6: 17.420 s (mean of 3 runs)−20%Q10Q10 · Graviton5 r9gd.24xlarge: 4.947 s (mean of 3 runs)Q10 · Graviton4 r8gd.24xlarge: 5.874 s (mean of 3 runs)Q10 · Cobalt 100 E96pds_v6: 5.909 s (mean of 3 runs)−16%Q11Q11 · Graviton5 r9gd.24xlarge: 0.847 s (mean of 3 runs)Q11 · Graviton4 r8gd.24xlarge: 0.905 s (mean of 3 runs)Q11 · Cobalt 100 E96pds_v6: 1.070 s (mean of 3 runs)−21%Q12Q12 · Graviton5 r9gd.24xlarge: 1.537 s (mean of 3 runs)Q12 · Graviton4 r8gd.24xlarge: 1.695 s (mean of 3 runs)Q12 · Cobalt 100 E96pds_v6: 2.122 s (mean of 3 runs)−28%Q13Q13 · Graviton5 r9gd.24xlarge: 8.360 s (mean of 3 runs)Q13 · Graviton4 r8gd.24xlarge: 8.926 s (mean of 3 runs)Q13 · Cobalt 100 E96pds_v6: 9.660 s (mean of 3 runs)−13%Q14Q14 · Graviton5 r9gd.24xlarge: 2.487 s (mean of 3 runs)Q14 · Graviton4 r8gd.24xlarge: 2.407 s (mean of 3 runs)Q14 · Cobalt 100 E96pds_v6: 3.182 s (mean of 3 runs)−22%Q15Q15 · Graviton5 r9gd.24xlarge: 1.173 s (mean of 3 runs)Q15 · Graviton4 r8gd.24xlarge: 1.217 s (mean of 3 runs)Q15 · Cobalt 100 E96pds_v6: 1.568 s (mean of 3 runs)−25%Q16Q16 · Graviton5 r9gd.24xlarge: 2.199 s (mean of 3 runs)Q16 · Graviton4 r8gd.24xlarge: 2.207 s (mean of 3 runs)Q16 · Cobalt 100 E96pds_v6: 3.792 s (mean of 3 runs)−42%Q17Q17 · Graviton5 r9gd.24xlarge: 2.446 s (mean of 3 runs)Q17 · Graviton4 r8gd.24xlarge: 2.819 s (mean of 3 runs)Q17 · Cobalt 100 E96pds_v6: 3.719 s (mean of 3 runs)−34%Q18Q18 · Graviton5 r9gd.24xlarge: 10.075 s (mean of 3 runs)Q18 · Graviton4 r8gd.24xlarge: 10.811 s (mean of 3 runs)Q18 · Cobalt 100 E96pds_v6: 11.462 s (mean of 3 runs)−12%Q19Q19 · Graviton5 r9gd.24xlarge: 2.530 s (mean of 3 runs)Q19 · Graviton4 r8gd.24xlarge: 3.074 s (mean of 3 runs)Q19 · Cobalt 100 E96pds_v6: 3.498 s (mean of 3 runs)−28%Q20Q20 · Graviton5 r9gd.24xlarge: 2.611 s (mean of 3 runs)Q20 · Graviton4 r8gd.24xlarge: 3.089 s (mean of 3 runs)Q20 · Cobalt 100 E96pds_v6: 3.961 s (mean of 3 runs)−34%Q21Q21 · Graviton5 r9gd.24xlarge: 9.686 s (mean of 3 runs)Q21 · Graviton4 r8gd.24xlarge: 11.318 s (mean of 3 runs)Q21 · Cobalt 100 E96pds_v6: 13.783 s (mean of 3 runs)−30%Q22Q22 · Graviton5 r9gd.24xlarge: 2.066 s (mean of 3 runs)Q22 · Graviton4 r8gd.24xlarge: 2.151 s (mean of 3 runs)Q22 · Cobalt 100 E96pds_v6: 2.767 s (mean of 3 runs)−25%G5 vs Cobalt
    TPC-H 1 TB, per-query mean of 3 runs. Totals: Graviton5 80.475 s · Graviton4 89.831 s · Cobalt 100 106.806 s. All three on GizmoSQL with DuckDB v1.5.5, September 4, 2026.
    Show the raw numbers (all three runs per query)

    Straight from the runner's output files; all 66 executions succeeded on every machine. The two ratio columns divide the Graviton5 mean by the Graviton4 and Cobalt 100 means, so 0.74 means Graviton5 took 74% of the time.

    QueryG5 run 1G5 run 2G5 run 3G5 mean (s)G4 mean (s)Cobalt mean (s)G5 / G4G5 / Cobalt
    Q15.351.691.682.9053.9134.7380.740.61
    Q20.850.470.550.6240.7000.8510.890.73
    Q32.692.502.502.5612.7093.3230.950.77
    Q43.092.232.372.5633.1843.7180.810.69
    Q52.312.062.342.2372.5933.2730.860.68
    Q60.600.620.580.5990.7320.7930.820.76
    Q71.822.002.312.0422.2722.8160.900.73
    Q82.021.822.502.1152.4913.3810.850.63
    Q914.1613.5713.8613.86514.74417.4200.940.80
    Q106.054.304.484.9475.8745.9090.840.84
    Q111.200.840.500.8470.9051.0700.940.79
    Q121.391.581.641.5371.6952.1220.910.72
    Q137.558.728.818.3608.9269.6600.940.87
    Q143.232.152.072.4872.4073.1821.030.78
    Q151.370.971.181.1731.2171.5680.960.75
    Q162.532.121.952.1992.2073.7921.000.58
    Q172.522.052.772.4462.8193.7190.870.66
    Q189.7310.2710.2210.07510.81111.4620.930.88
    Q192.532.722.342.5303.0743.4980.820.72
    Q202.682.532.622.6113.0893.9610.850.66
    Q218.1712.038.859.68611.31813.7830.860.70
    Q222.461.781.962.0662.1512.7670.960.75
    Total84.2979.0478.1080.47589.831106.8060.900.75

    A few things worth noticing:

    • Graviton5 wins 20 of 22 against Graviton4 and 22 of 22 against Cobalt 100. Versus Graviton4, Q14 and Q16 are a wash (within run-to-run noise) and everything else is 4% to 26% faster. Versus Cobalt 100, the smallest win is 12% (Q18) and the largest is 42% (Q16), with Q1 close behind at 39%.
    • The scan-heavy queries gained the most. Q1 (a full pass over the six-billion-row lineitem table) dropped 26%, and Q4, Q6, Q19, Q10, and Q20 each dropped 15% to 19%. These are the queries that stream columns through the CPU as fast as memory will feed them, which is exactly where Graviton5's faster DDR5-8800 memory should show up.
    • The big joins gained less. Q9, Q13, and Q18, the three heaviest multi-way joins, improved 6% to 7%. They are still where the time goes: those three plus Q21 account for about 42 of the 80 seconds on Graviton5.
    • The cold pass is where the Azure box hurts most. Pass one totals 84.3 s on Graviton5, 96.1 s on Graviton4, and 117.9 s on Cobalt 100; Q1's first run alone is 5.4 s, 7.5 s, and 10.0 s respectively. If you only report warm runs, you would quote 78 seconds for Graviton5. We do not.
    • Twelve of the 22 queries finish in under 2.5 seconds on a terabyte of data, over the network, through a Flight SQL client. That is the part that still feels unreal to me.

    Why Graviton5 is the real deal

    Ten percent over Graviton4 might not sound like a headline, so let me put it in context. This is a single generation, on a workload that is bound by memory bandwidth and NVMe throughput rather than by per-core arithmetic, measured with zero software changes: I did not recompile, retune, or touch a single setting between any of the three runs. The same binary just ran 10% faster than on Graviton4 and 25% faster than on Cobalt 100, and the Graviton5 run cost 2% less than the Graviton4 run despite the instance costing 9% more per hour. That is free performance, and it compounds with every DuckDB release.

    AWS's own claims for the chip explain where it comes from:

    • Up to 25% higher compute performance per vCPU versus Graviton4. We saw 10% on an I/O- and memory-heavy analytical workload; compute-bound workloads should see more.
    • DDR5-8800 memory, up from DDR5-5600 on Graviton4. Analytical SQL at this scale streams columns through the CPU, and memory bandwidth is the ceiling. That lines up with the scan-heavy queries gaining the most.
    • Five times the L3 cache of Graviton4. Hash joins and aggregations over hundreds of millions of groups live or die on cache hit rates, and Q9, Q18, and Q21 are exactly that kind of query.
    • Up to 192 vCPUs in the largest sizes. I used the 24xlarge, with 96, to keep the core count identical to the other two runs. There is a 48xlarge with double the cores and memory waiting for the next round.

    The honest caveats: each machine got one benchmark run, on a Thursday afternoon, so a percent or two of any gap is noise. The Azure box has 672 GiB of memory to AWS's 768 GiB, and different NVMe (six 880 GiB drives versus three 1,900 GB), so the cross-cloud comparison folds in the platform, not only the CPU. And Cobalt 100 is Microsoft's first-generation Arm chip; Graviton5 is Amazon's fifth. The Graviton5 versus Graviton4 comparison is the clean one, and it is the one I would quote: same cloud, same region, same instance layout, one generation apart, 10% faster. The 48xlarge is next on my list.

    What it costs

    The math is simple: seconds, divided by 3,600, times the on-demand hourly price.

    Graviton5   80.475 s ÷ 3,600 × $7.689/hr = $0.172
    Graviton4   89.831 s ÷ 3,600 × $7.054/hr = $0.176
    Cobalt 100 106.806 s ÷ 3,600 × $5.616/hr = $0.167

    All three round to $0.17 per full TPC-H 1 TB run, the figure we have published for a while now, which means our comparison against the cloud warehouses does not change on cost: GizmoSQL is still about 16x cheaper per run than Snowflake, 31x cheaper than Databricks SQL, and 138x cheaper than BigQuery on this workload. What changes is speed. At 80.5 seconds, GizmoSQL is now faster than every one of them, including Snowflake's 103.4 seconds, which had previously edged us out on runtime while costing 16 times more.

    And those are on-demand prices. Spot pricing on Graviton instances is typically a fraction of on-demand, and for a batch analytics job that finishes in 80 seconds, spot is a perfectly reasonable place to run.

    The horse race is the point

    Here is the part I care about more than any single number. GizmoSQL is one process that runs on one machine, and it is built on DuckDB, which is compiled for Arm and gets faster every release. That means every time a cloud provider ships a better CPU, every GizmoSQL user gets the improvement the same day, for free, by changing one string in a launch script. No re-platforming, no new pricing tier, no waiting for a vendor to "support" the new instance type. Today that string was r9gd.24xlarge.

    And the race is far from over:

    • Azure Cobalt 200 is Microsoft's answer to exactly this result. It is a 132-core chip on Arm Neoverse V3 and TSMC 3 nm, and Microsoft claims more than 50% higher performance than Cobalt 100 across real workloads. If even half of that lands on TPC-H, Cobalt 200 would leapfrog Graviton4 and land right next to Graviton5. Microsoft says general availability comes later in 2026. We will run this exact benchmark on it the week it is available.
    • Google Cloud Axion is the third Arm contender. The C4A family is built on Neoverse V2, the same core generation as Graviton4, and its -lssd shapes ship with up to 6 TiB of local Titanium SSD, which is what this benchmark wants. C4A tops out at 72 vCPUs, so it is not a like-for-like 96-core run, but GizmoData Cloud already provisions GizmoSQL on C4A and an Axion result belongs in this table. It is coming.
    • The 48xlarge sizes, and NUMA. Doubling to 192 vCPUs on either generation also means going from one NUMA node to two, and in my experience that is where linear scaling for a single DuckDB process goes to die: hash joins and aggregations that fit comfortably in one socket's memory suddenly spend their time shuttling across the interconnect. Graviton4's 48xlarge is two 96-core sockets. Graviton5's is one 192-core die with two NUMA regions, and AWS says the inter-core latency is about a third lower, so it may be the first 48xlarge where the second node pays for itself on this workload. Measuring that, and whether two NUMA-pinned GizmoSQL processes beat one big one, is a post of its own.

    Amazon, Microsoft, and Google are each spending billions to make their Arm chips beat the other two. Snowflake, Databricks, and BigQuery customers see those gains as whatever margin the vendor decides to pass along, if any. GizmoSQL customers see all of it, at the hourly price of the VM. That is the structural advantage of a single-node engine on commodity cloud hardware, and it gets bigger every generation.

    Reproduce it

    Everything here is open source and the steps are the ones above. The data generator is gizmodata/tpch-datagen, the query runner is gizmodata/benchmark-flight-sql, and GizmoSQL Core is free, Apache-2.0, and installs in one command. If you get a different number on the same shape, I want to hear about it.

    If your team is paying a warehouse by the credit, the DBU, or the byte scanned to answer questions like these, this is what one right-sized Arm VM does for $0.17. We will help you move the workload, and we guarantee the bill drops by half.

    Ready to Try GizmoSQL?

    Experience lightning-fast data analytics with our open-source SQL engine