RDF Framework Benchmark

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

April 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

Eleven configurations spanning four languages and multiple execution models.

FrameworkLanguageEngineVersionLicense
maplibPython (Rust core)Polars + Apache Arrow, in-memory0.20.15Apache 2.0
maplib (disk) *Python (Rust core)Polars + Apache Arrow, disk-backed storage0.20.15Proprietary
oxigraphPython (Rust core)SPOG indexes, disk-backed store (RocksDB)0.5.7MIT / Apache 2.0
rdflibPython (pure)In-memory dict-of-dictslatestBSD 3-Clause
JenaJavaIn-memory Model5.2.0Apache 2.0
RDF4JJavaMemoryStore SAIL5.0.3EDL 1.0
QLeverC++ (Docker)On-disk index + SPARQL endpointlatestApache 2.0
VirtuosoC (Docker)Hybrid relational/RDF, column store7.x (latest)GPL v2
GraphDBJava (Docker)RDF4J-based triplestore, on-disk persistence10.8.0Proprietary (free tier)
dotNetRDFC# (Docker)In-memory TripleStore, Leviathan SPARQL engine3.5.1MIT
Neo4j + n10sJava (Docker)Native property graph with neosemantics RDF import5.26 + n10s 5.26.0GPL v3 (Community)

* 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