High-performance graph queries.
Built to scale. No data movement.

Run fast multi-hop queries directly on your databases and lakehouses, and scale graph compute as your workloads grow. No data movement into a dedicated graph database required.

OnTime flight network · Single-node benchmark

Your analytical data.
Graph query speed.

Fast relationship queries on ClickHouse and Apache Iceberg. A head-to-head comparison with Neo4j. One flight-network study shows how PuppyGraph performs across four multi-hop query patterns.

ClickHouseApache IcebergNeo4j Community
Flight dateAircraft IDArrival / departure
Airports are vertices. Each flight edge is one hop.
Illustrative route; the benchmark queries two or three hops.
12.3Mflight records for the Neo4j comparison
435airports connected by flight records
4 patternstwo-hop and three-hop relationship queries

The performance difference

Faster on every
tested query pattern.

PuppyGraph reading directly from ClickHouse or Iceberg outperformed Neo4j Community on all four query patterns in the 12.3M-record test.

PuppyGraph + ClickHousePuppyGraph + IcebergNeo4j Community

Linear axis, 0–500 s for every query. Small values remain labeled even when their bars are very short.

Q1

Two-hop connections

Count qualifying two-hop paths between fixed airports by month.

PuppyGraph + ClickHouse
0.612 s
PuppyGraph + Iceberg
0.240 s
Neo4j Community
36.939 s
0125250375500
0.010.11101001000
View Cypher query for Q1

The airport filters use PuppyGraph vertex IDs and Neo4j airportId properties. The remaining query text is identical.

PuppyGraph + ClickHouse / Iceberg

MATCH p = (a)-[r1]->(b)-[r2]->(c)
WHERE (r1.FlightDate = r2.FlightDate)
    AND (r1.CRSArrTime + 100 <= r2.CRSDepTime)
    AND (id(a) = "airport[12892]")
    AND (id(c) = "airport[12953]")
    AND (r1.Year = {_year})
WITH r1.Month as month, count(p) as path_count
RETURN month, path_count
ORDER BY month;

Neo4j Community

MATCH p = (a)-[r1]->(b)-[r2]->(c)
WHERE (r1.FlightDate = r2.FlightDate)
    AND (r1.CRSArrTime + 100 <= r2.CRSDepTime)
    AND (a.airportId = "12892")
    AND (c.airportId = "12953")
    AND (r1.Year = {_year})
WITH r1.Month as month, count(p) as path_count
RETURN month, path_count
ORDER BY month;

Years tested: 2020, 2021, 2022.

The benchmark runner substitutes {_year} with each value before execution. The chart summarizes all tested values across three rounds.

Q2

Two-hop arrival constraints

Add an actual-arrival versus departure condition to the two-hop connection query.

PuppyGraph + ClickHouse
0.626 s
PuppyGraph + Iceberg
0.204 s
Neo4j Community
37.011 s
0125250375500
0.010.11101001000
View Cypher query for Q2

The airport filters use PuppyGraph vertex IDs and Neo4j airportId properties. The remaining query text is identical.

PuppyGraph + ClickHouse / Iceberg

MATCH p = (a)-[r1]->(b)-[r2]->(c)
WHERE (r1.FlightDate = r2.FlightDate)
    AND (r1.CRSArrTime + 100 <= r2.CRSDepTime)
    AND (r1.ArrTime >= r2.DepTime)
    AND (id(a) = "airport[12892]")
    AND (id(c) = "airport[12953]")
    AND (r1.Year = {_year})
WITH r1.Month as month, count(p) as path_count
RETURN month, path_count
ORDER BY month;

Neo4j Community

MATCH p = (a)-[r1]->(b)-[r2]->(c)
WHERE (r1.FlightDate = r2.FlightDate)
    AND (r1.CRSArrTime + 100 <= r2.CRSDepTime)
    AND (r1.ArrTime >= r2.DepTime)
    AND (a.airportId = "12892")
    AND (c.airportId = "12953")
    AND (r1.Year = {_year})
WITH r1.Month as month, count(p) as path_count
RETURN month, path_count
ORDER BY month;

Years tested: 2020, 2021, 2022.

The benchmark runner substitutes {_year} with each value before execution. The chart summarizes all tested values across three rounds.

Q3

Two-hop aircraft connections

Rank airports by distinct aircraft in qualifying two-hop sequences.

PuppyGraph + ClickHouse
0.176 s
PuppyGraph + Iceberg
0.145 s
Neo4j Community
360.151 s
0125250375500
0.010.11101001000
View Cypher query for Q3

The same query text is used for all three configurations.

PuppyGraph + ClickHouse / Iceberg and Neo4j

MATCH ()-[r1]->(n)-[r2]->()
WHERE (r1.FlightDate = date("{_date}"))
    AND (r1.FlightDate = r2.FlightDate)
    AND (r1.Tail_Number = r2.Tail_Number)
    AND (r1.Tail_Number IS NOT NULL)
    AND (r1.Tail_Number <> "")
    AND (r1.CRSArrTime < r2.CRSDepTime)
WITH n, count(DISTINCT r1.Tail_Number) AS cnt
RETURN n, cnt
ORDER BY cnt DESC
LIMIT 10;

Dates tested: 2020-03-12, 2020-08-15, 2020-10-07, 2020-11-29, 2021-02-18, 2021-06-20, 2021-07-07, 2021-12-21, 2022-02-01, 2022-03-29.

The benchmark runner substitutes {_date} with each value before execution. The chart summarizes all tested values across three rounds.

Q4

Three-hop aircraft sequences

Count distinct aircraft in qualifying three-hop sequences.

PuppyGraph + ClickHouse
0.206 s
PuppyGraph + Iceberg
0.175 s
Neo4j Community
450.026 s
0125250375500
0.010.11101001000
View Cypher query for Q4

The same query text is used for all three configurations.

PuppyGraph + ClickHouse / Iceberg and Neo4j

MATCH ()-[r1]->()-[r2]->()-[r3]->()
WHERE (r1.FlightDate = date("{_date}"))
    AND (r1.FlightDate = r2.FlightDate)
    AND (r1.FlightDate = r3.FlightDate)
    AND (r1.Tail_Number = r2.Tail_Number)
    AND (r1.Tail_Number = r3.Tail_Number)
    AND (r1.Tail_Number IS NOT NULL)
    AND (r1.Tail_Number <> "")
    AND (r1.CRSArrTime < r2.CRSDepTime)
    AND (r2.CRSArrTime < r3.CRSDepTime)
RETURN count(DISTINCT r1.Tail_Number) AS cnt;

Dates tested: 2020-03-12, 2020-08-15, 2020-10-07, 2020-11-29, 2021-02-18, 2021-06-20, 2021-07-07, 2021-12-21, 2022-02-01, 2022-03-29.

The benchmark runner substitutes {_date} with each value before execution. The chart summarizes all tested values across three rounds.

All times are medians of three per-round means, in seconds. Results reflect this workload and deployment.

Test configuration

Data and queries

12,282,219 flight records and 435 airports from ClickHouse’s OnTime dataset (2020–2022, with partial 2022 coverage). All configurations use the same records and query parameters.

Deployment

Single-node deployments on AWS m6a.2xlarge hosts, each with 8 vCPUs and 32 GiB RAM. PuppyGraph reads directly from ClickHouse or Iceberg without using local tables, and runs on the same host as its data source. Neo4j runs on its own host.

Measurement

Three rounds per configuration, with warmup before timing. Each round averages query times across the tested parameters; charts show the median of those three averages, in seconds.

Versions

PuppyGraph 1.11.1 · ClickHouse 25.6.2.5 · Neo4j 2026.07.1 Community.

Explore the complete experiment.

Beyond the comparison shown here, the full experiment also tests PuppyGraph on 204.8 million flight records, compares direct source reads with local tables, and explores five Iceberg file layouts. Find the complete results, queries, setup instructions, and reproduction steps in the public benchmark repository.

Your data. Your queries.
See the performance.

See how fast your graph queries run on your own data, and scale graph compute as your workload grows.

$ docker run -p 8081:8081 \
  -p 8182:8182 -p 7687:7687 \
  puppygraph/puppygraph:latestPulling puppygraph/puppygraph:latestStarting PuppyGraphWeb console listening on 0.0.0.0:8081Gremlin endpoint listening on 0.0.0.0:8182PuppyGraph is running# no pipelines · no second database · no data movement