RDF Framework Benchmark

Comparing eleven RDF frameworks and triplestores across I/O and SPARQL query performance at 100K, 1M, and 10M triples

August 2026 Apple M3 Max · 36 GB RAM 100K → 1M → 10M triples

Compare Frameworks

Toggle frameworks on or off to customise all charts and tables below. At least one must remain active.

All In-memory Disk / server Python Docker-based

Frameworks

Thirteen configurations spanning five languages and multiple execution models. Every result also records peak memory.

FrameworkLanguageEngineVersionLicense
maplibPython (Rust core)Polars + Apache Arrow, in-memory0.20.29Apache 2.0
maplib (disk) *Python (Rust core)Polars + Apache Arrow, disk-backed storage0.20.29Proprietary
oxigraphPython (Rust core)SPOG indexes, in-memory (RocksDB engine, no path)0.5.9MIT / Apache 2.0
oxigraph (disk)Python (Rust core)Disk-backed RocksDB (on-disk path)0.5.9MIT / Apache 2.0
rdflibPython (pure)In-memory dict-of-dicts7.6.0BSD 3-Clause
JenaJavaIn-memory Model6.1.0Apache 2.0
RDF4JJavaMemoryStore SAIL6.0.0EDL 1.0
QLeverC++ (Docker)On-disk index + SPARQL endpointlatestApache 2.0
VirtuosoC (Docker)Hybrid relational/RDF, column store7.2.17GPL v2
GraphDBJava (Docker)RDF4J-based triplestore, on-disk persistence10.8.0Proprietary (free tier)
dotNetRDFC# (Docker)In-memory TripleStore, Leviathan SPARQL engine3.5.2MIT
Neo4j + n10sJava (Docker)Native property graph with neosemantics RDF import5.26 + n10s 5.26.0GPL v3 (Community)
TentrisDB †C++ (Docker)Tensor-based, disk-based, worst-case-optimal joinslatest (beta)Proprietary (free tier)

* maplib (disk) uses the storage_folder parameter for disk-backed storage. This feature is part of the proprietary maplib distribution and is not available in the open-source release. The in-memory maplib (without storage_folder) is fully open source under Apache 2.0.

Test Data

Synthetic e-commerce graph (customers, orders, products) generated with a fixed seed for reproducibility.

ScaleTriplesTurtleN-Triples
Medium~100 K3.6 MB10.9 MB
Large~1 M36.9 MB111 MB
XLarge~10 M369 MB1.1 GB

SPARQL Queries

Six queries of increasing complexity, representative of real analytical workloads. Click a row to see the SPARQL.

IDDescriptionComplexity
Q1 COUNT all triples Trivial, full scan
SELECT (COUNT(*) AS ?count) WHERE { ?s ?p ?o . }
Q2 Top 20 customers by spend (GROUP BY + SUM + ORDER BY) Aggregation over joins
PREFIX : <http://benchmark.example/> PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> SELECT ?customer_name (COUNT(?order) AS ?order_count) (SUM(?amount) AS ?total_spend) WHERE { ?order :placedBy ?customer ; :totalAmount ?amount . ?customer rdfs:label ?customer_name . } GROUP BY ?customer_name ORDER BY DESC(?total_spend) LIMIT 20
Q3 3-entity join (customer + order + product) with country filter Multi-pattern + filter
PREFIX : <http://benchmark.example/> PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> SELECT ?customer_name ?product_name ?amount ?status WHERE { ?order :placedBy ?customer ; :contains ?product ; :totalAmount ?amount ; :orderStatus ?status . ?customer rdfs:label ?customer_name ; :country "Norway" . ?product rdfs:label ?product_name . } ORDER BY DESC(?amount) LIMIT 50
Q4 Revenue by country/segment with OPTIONAL orders OPTIONAL + aggregation
PREFIX : <http://benchmark.example/> PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> SELECT ?country ?segment (COUNT(DISTINCT ?customer) AS ?customers) (COUNT(DISTINCT ?order) AS ?orders) (SUM(?amount) AS ?revenue) WHERE { ?customer rdf:type :Customer ; :country ?country ; :segment ?segment . OPTIONAL { ?order :placedBy ?customer ; :totalAmount ?amount . } } GROUP BY ?country ?segment ORDER BY DESC(?revenue)
Q5 CONSTRUCT subgraph of Norwegian customer orders with products CONSTRUCT + multi-join
PREFIX : <http://benchmark.example/> PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> CONSTRUCT { ?customer rdf:type :Customer . ?customer rdfs:label ?name . ?customer :country :Norway . ?order :placedBy ?customer . ?order :totalAmount ?amount . ?order :contains ?product . ?product rdfs:label ?productName . } WHERE { ?customer rdf:type :Customer ; rdfs:label ?name ; :country :Norway . ?order :placedBy ?customer ; :totalAmount ?amount ; :contains ?product . ?product rdfs:label ?productName . }
Q6 DELETE-INSERT: adjust product prices by category (5 conditional branches) SPARQL Update with BIND + nested IF
PREFIX : <http://benchmark.example/> PREFIX xsd: <http://www.w3.org/2001/XMLSchema#> DELETE { ?product :unitPrice ?oldPrice } INSERT { ?product :unitPrice ?newPrice } WHERE { ?product a :Product ; :unitPrice ?oldPrice ; :category ?cat . BIND( IF(?cat = "Software", ?oldPrice * 1.10, IF(?cat = "Hardware", ?oldPrice * 0.95, IF(?cat = "Services", ?oldPrice * 1.15, IF(?cat = "Accessories", ?oldPrice * 0.90, ?oldPrice * 1.05)))) AS ?newPrice ) }

I/O Performance

Time to read and write RDF data in Turtle and N-Triples format.

Read Turtle

Write Turtle

Read N-Triples

Write N-Triples

Tabular Data

Query Performance

Best of 3 runs after warmup. All frameworks execute the same SPARQL queries.

Q1: COUNT all triples

Q2: GROUP BY + SUM

Q3: 3-entity join + filter

Q4: OPTIONAL + GROUP BY

Q5: CONSTRUCT subgraph

Q6: DELETE-INSERT price adjustment

Tabular Data

Scaling Behavior

How each framework handles growing data volumes, from 100K to 10M triples.

Read Turtle across scales

Q2 (Aggregation) across scales

Q5 (CONSTRUCT) across scales

Memory Usage

Peak memory during the benchmark. Three different bases, not directly comparable: Python engines report process RSS (psutil), Java engines report JVM heap used, and Docker engines report container memory (docker stats). Compare within a basis, and read absolute cross-language differences with caution.

Peak memory by engine (MB)

Peak memory across scales (MB)

† TentrisDB is beta and runs here under linux/amd64 emulation on Apple silicon, so its timings are indicative only and not directly comparable to the natively-running engines (its memory figure is container memory).

Overall: Speed vs Memory

One picture summing it all up. Each engine is a single point: the horizontal axis is its overall speed (geometric mean of every timed operation — I/O + all queries — across all three scales, lower/left = faster), and the vertical axis is its average peak memory across scales (lower/down = leaner). Bottom-left is the sweet spot: fast and light. Both axes are log. Memory bases differ by engine family (see above), so read cross-family memory distances with caution; Tentris is emulated.

Speed vs peak memory (per engine, aggregated over all tests)