Skip to main content

yo_graph/
lib.rs

1//! The graph model: adjacency runs a traversal can read at memory speed
2//! (`11`).
3//!
4//! The embedded graph space had two things happen to it and neither was about
5//! traversal speed. Kuzu, which would otherwise be the comparison, was acquired
6//! in October 2025 and archived the next day. FalkorDB spent 2025 and 2026
7//! rewriting in Rust and pointing at GraphRAG. Both events are about who is
8//! willing to keep an embedded graph engine alive, which is why the format
9//! being documented byte for byte and read by something other than this code is
10//! a first class decision here rather than a nicety.
11//!
12//! What does not come along is the query processor. There is no Cypher, no GQL
13//! and no factorised execution, because a graph database without a query
14//! language is an adjacency structure with good ergonomics, and that is what an
15//! agent memory or a recommendation workload actually calls. A traversal is an
16//! iterator the caller drives, and its cost is written down rather than hidden
17//! behind a planner.
18//!
19//! # What is here so far
20//!
21//! [`Adjacency`], the hot form of the adjacency plane. A run is the neighbours
22//! of one node under one label in one direction, it is contiguous, and it is
23//! appended to and deleted from in place. A one hop is a probe and a sequential
24//! read; a two hop is a probe per neighbour over runs that can be prefetched
25//! before any of them is read.
26//!
27//! ```
28//! use yo_graph::{Adjacency, Dir};
29//!
30//! const FOLLOWS: u32 = 1;
31//!
32//! let mut g = Adjacency::new();
33//! g.link(1, 2, FOLLOWS, 0);
34//! g.link(2, 3, FOLLOWS, 1);
35//!
36//! let hop = g.neighbours(1, FOLLOWS, Dir::Out).to_vec();
37//! let two: Vec<u64> = hop.iter().flat_map(|n| g.neighbours(*n, FOLLOWS, Dir::Out)).copied().collect();
38//! assert_eq!(two, vec![3]);
39//! ```
40//!
41//! Twelve bytes an edge is the payload, and the run headers and the capacity
42//! slack take a graph shaped like LiveJournal to around 15 once it has settled.
43//! That is the price of a structure where every operation is O(1).
44//!
45//! [`Csr`], the cold form, which is the same adjacency once nothing is changing
46//! it: node grouped, gap coded and bit packed, read only, and about an order of
47//! magnitude smaller. On an R-MAT graph it is 11.98 bits an edge as the ids
48//! come, and 9.38 after [`csr::order_by_degree`] gives the hubs the small ids.
49//! On a uniformly random graph it is 15.96 against a floor of 13.44, and the
50//! ordering pass moves that by nothing, which is what says the difference
51//! between the two graphs is the graph rather than the encoder.
52//!
53//! [`bisect`], the numbering that matters on a real graph. It reads the graph as
54//! a bipartite one, splits the nodes in half, swaps nodes across the middle for
55//! as long as an estimate of the compressed size goes down, and recurses, which
56//! is Facebook's recursive graph bisection from 2016. On soc-LiveJournal1 it
57//! takes the cold form to 15.00 bits an edge where degree ordering leaves it at
58//! 19.00, and on web-Google to 15.04 against 20.21. It costs twelve minutes on
59//! eight cores for a graph of seventy million edges, which is the trade the cold
60//! form is for.
61//!
62//! Neither number is the 8 bits an edge the spec asks for, and [`csr`] has the
63//! full breakdown of where the rest of it is.
64//!
65//! ```
66//! use yo_graph::{Csr, csr};
67//!
68//! let mut edges = vec![(0u32, 3u32), (0, 1), (2, 0), (0, 9)];
69//! let to = csr::order_by_degree(10, &edges);
70//! csr::renumber(&mut edges, &to);
71//!
72//! let cold = Csr::build(10, &mut edges);
73//! assert_eq!(cold.degree(to[0]), 3);
74//! ```
75//!
76//! [`Graph`] is the two of them together with a document behind every node and
77//! every edge. The typed `Graph<N, E>` surface, the ten command `G.*` family and
78//! the algorithms are the rest of M7.
79
80#![deny(missing_docs)]
81
82pub mod adjacency;
83pub mod algo;
84pub mod bisect;
85pub mod csr;
86pub mod graph;
87pub mod props;
88pub mod snapshot;
89
90pub use adjacency::{Adjacency, Dir};
91pub use csr::Csr;
92pub use graph::{Graph, NO_PROPS};
93pub use props::{Props, id_key};
94pub use snapshot::Snapshot;