knut-flight — an Arrow Flight server for the rel2graph output (N4)
Class:
design. A plain Apache Arrow Flight server (not Flight SQL) that serves knut's rel2graph output — nodes, edges, and the source relational tables — as ticketed Arrow streams. Sibling of the Flight SQL server (graphar-flight, Cypher-in/Arrow-out over a live FalkorDB): this one has no database and no query language; it just streams the graph the mapping fabric built. Source: cratecrates/knut-flight—lib.rs(Catalog,GraphFlightService), binsrc/bin/knut-flight-server.rs; testtests/roundtrip.rs; Python clientdemo/flight_networkx_demo.py.
Why a new crate, not graphar-flight
graphar-flight implements the Flight SQL trait: it takes Cypher, runs it against FalkorDB, and returns Arrow. knut-flight implements the plain Flight trait ([FlightService]) over an in-memory [Catalog] built from a GraphMutation at startup. Different trait, no DB, no query language — so it stays a clean single-binary service. Ports are disjoint: FlightSQL uses :50051/:50054, this server defaults to :50055.
The catalog
Built once at startup from a rel2graph GraphMutation through the single seam Catalog::from_mutation(mutation, source_tables). There are two entrypoints onto that seam:
Catalog::from_bench(scale)— the synthetic path: runs the engine over the seeded 12-table ERP schema (deterministic,DEFAULT_SEED).Catalog::from_parquet_dir(spec, dir)— the live-data path: reads<table>.parquetfor every table in a mapping spec and runs the engine's canonicalknut_bifrost::rel2graph::build_mutationover the collectedArrowTables. This serves a REAL graph read off disk — an Iceberg / object-store export — with no bench and no live DB. It is the shape korp'siceberg2graphstep produces when it lands lakehouse tables as parquet and hands the directory to this server. See the engine seam.
Both funnel through from_mutation, so a bench catalog and a real-data catalog are identical in shape. At scale 1 the bench path materialises 35 datasets:
| ticket | rows @1 | schema |
nodes |
1106 | (label: Utf8, id: Int64) — aggregate node skeleton, all labels |
edges |
2955 | (src_label, rel, dst_label, src, dst) — resolved edges only |
node/<Label> |
per label | the label's native vertex batch (typed property columns) |
edge/<src>_<rel>_<dst> |
per rel | the relationship's edge batch (src/dst + props) |
table/<erp.qualified.name> |
per table | the raw source relational table |
The ticket is the dataset name as UTF-8 bytes — no protobuf envelope — so any Flight client (Rust, pyarrow, Go) round-trips it verbatim. The nodes/edges aggregates carry a UNIFORM schema across labels: exactly what a networkx loader wants (see below). The node/* and edge/* flights carry the full native typed columns; table/* exposes the rel2graph inputs for provenance.
The Flight surface
Read-only. Implemented verbs:
list_flights— one [FlightInfo] per dataset: its Arrow schema, aCmddescriptor and aTicket(both the name bytes), andtotal_records.get_flight_info— theFlightInfofor a descriptor (cmdbytes, or a/-joined path).get_schema— a dataset's schema without pulling data.do_get— streams the dataset'sRecordBatches for a ticket, encoded withFlightDataEncoderBuilder.
The mutating verbs (do_put, do_exchange, do_action, handshake, poll_flight_info) return unimplemented — this is a read-only projection server; list_actions returns an empty list.
The binary
knut-flight-server — config via flags/env only, no interactive setup:
| flag | env | default |
--addr <host:port> |
KNUT_FLIGHT_ADDR |
0.0.0.0:50055 |
--scale <n> |
KNUT_FLIGHT_SCALE |
1 |
--parquet <dir> |
KNUT_FLIGHT_PARQUET |
(unset → bench data) |
--mapping <file> |
KNUT_FLIGHT_MAPPING |
(unset → bench 12-table spec) |
With --parquet <dir> the server serves a REAL graph from a parquet export (the live-data path above); --mapping supplies the rel→graph YAML for that data and defaults to the bench 12-table spec, so a rel2graph-export … --parquet <dir> dump serves out of the box. Without --parquet, --scale sizes the bench.
--version prints knut-flight-server v<version> (<git-hash>) — the git hash is stamped by build.rs (KNUT_GIT_HASH, pure-Rust file walk, no shelling), per the build-version law. --help lists the flags.
cargo run -p knut-flight --release --bin knut-flight-server -- --addr 0.0.0.0:50055 --scale 1
Proving it end-to-end
Two proofs, one per audience:
- Rust —
tests/roundtrip.rsstands the server up on an ephemeral port, connects a realFlightClient,list_flights(asserts the 35 datasets incl.nodes,edges,edge/Employee_REPORTS_TO_Employee,table/erp.hr.employees), anddo_getsnodes/edges/a source table, asserting 1106/2955 rows and that everyREPORTS_TOedge hassrc != dst(no self-loop, a7208dc). - Python —
demo/flight_networkx_demo.pyconnects withpyarrow.flight, lists flights, pullsnodes+edgesas Arrow, and rebuilds aMultiDiGraphwith the samedemo/knut_networkx.pyloader the file-export demo (N3) uses — then asserts the Flight-path graph equals the file-path graph AND knut's oracle. Same graph, two delivery roads. - Live data —
tests/live_data.rswrites the scale-1 source tables to parquet (write_source_parquet, the export bin's--parquet), builds a catalog withfrom_parquet_dirover that directory, asserts it equals the bench catalog's shape (35 datasets, 1106 nodes, 2955 edges, 21 REPORTS_TO with no self-loop), and pullsnodes/edges/a source table with a realFlightClient— a client pull of REAL on-disk data, engine-driven end to end.
N5: a service, two ways (DONE)
This clean single-binary service — in-memory state built at startup, env-only config, no interactive setup, disjoint port — is now delivered as a service two ways, both driven from ONE ComponentSpec::knut_flight in Skidbladnir (so the Runtime targets never drift):
- systemd —
SystemdInstallerstages the binary into a TarZstd bundle, unpacks it under the airgap layout, and dropsknut-flight.service(User=_knut,Environment=KNUT_FLIGHT_ADDR=0.0.0.0:50055/KNUT_FLIGHT_SCALE=1, TCP health on 50055), enable+start over systemd D-Bus. Driver:skidbladnir --example install-knut-flight. - Kubernetes —
containers/knut-flight/Containerfileimages the version-stamped release binary onto a slim glibc base (knut-flight-server:0.1.3); Skidbladnir'sKubernetesRendererprojects the same spec to a Deployment + Service in namespaceknut(skidbladnir --example render-knut-flight-k8s). Manifests + apply steps:Skidbladnir/deploy/knut-flight/.
The image build copies the host-built binary (the knut workspace has path deps on sibling repos, so it does not cargo build in isolation inside a container); the binary needs glibc ≤ 2.39, so a trixie base runs it directly.
Future work: source the graph from a live sink or object store instead of the bench generator (the Catalog builder is the single seam to swap).
See also
- The file-export sibling that feeds the same networkx loader: rel2graph-networkx-design.md.
- The Flight SQL server (Cypher/FalkorDB): flight-design.md / flight-guide.md.
- The graph seam + correctness oracle: rel2graph-design.md, benching-benchmarks.md.