Traversal and degree skew
Why does traversal cost explode with depth, and why is degree skew the central mechanism?
Traversal as a query primitive
A traversal is a sequence of frontier expansions. The frontier is a set of vertices. Expansion replaces it with the union of their neighbours, minus what you have already seen. That "minus what you have seen" is not a detail. It is a stateful deduplication against the visited set, and it is why traversal cannot be written as a fixed pipeline: you do not know in advance how many iterations you need.
SQL has a feature for this, the recursive query (WITH RECURSIVE): a query that feeds its own output back in as input until no new rows appear. Graph traversal is fixpoint iteration (repeat a step until the result stops changing), and recursive SQL is the same mechanism behind a different interface. They are not two features.
-- Everyone reachable from user 7 within 3 hops (UNION removes already-seen pairs).
WITH RECURSIVE reach(user_id, depth) AS (
SELECT 7, 0
UNION
SELECT f.followee_id, r.depth + 1
FROM reach r JOIN follows f ON f.follower_id = r.user_id
WHERE r.depth < 3
)
SELECT DISTINCT user_id FROM reach;Frontier size is the cost, and it is governed by the branching factor. With average degree d (the number of edges at a vertex), hop k touches on the order of d^k vertices before deduplication.
- average edges at a vertex
- hops traversed
Data table
| Hops k | series | Vertices touched |
|---|---|---|
| 1 | from one end | 100 |
| 1.06 | from one end | 133 |
| 1.13 | from one end | 178 |
| 1.19 | from one end | 237 |
| 1.25 | from one end | 316 |
| 1.31 | from one end | 422 |
| 1.38 | from one end | 562 |
| 1.44 | from one end | 750 |
| 1.5 | from one end | 1K |
| 1.56 | from one end | 1.33K |
| 1.63 | from one end | 1.78K |
| 1.69 | from one end | 2.37K |
| 1.75 | from one end | 3.16K |
| 1.81 | from one end | 4.22K |
| 1.88 | from one end | 5.62K |
| 1.94 | from one end | 7.5K |
| 2 | from one end | 10K |
| 2.06 | from one end | 13.3K |
| 2.13 | from one end | 17.8K |
| 2.19 | from one end | 23.7K |
| 2.25 | from one end | 31.6K |
| 2.31 | from one end | 42.2K |
| 2.38 | from one end | 56.2K |
| 2.44 | from one end | 75K |
| 2.5 | from one end | 100K |
| 2.56 | from one end | 133K |
| 2.63 | from one end | 178K |
| 2.69 | from one end | 237K |
| 2.75 | from one end | 316K |
| 2.81 | from one end | 422K |
| 2.88 | from one end | 562K |
| 2.94 | from one end | 750K |
| 3 | from one end | 1M |
| 3.06 | from one end | 1.33M |
| 3.13 | from one end | 1.78M |
| 3.19 | from one end | 2.37M |
| 3.25 | from one end | 3.16M |
| 3.31 | from one end | 4.22M |
| 3.38 | from one end | 5.62M |
| 3.44 | from one end | 7.5M |
| 3.5 | from one end | 10M |
| 3.56 | from one end | 13.3M |
| 3.63 | from one end | 17.8M |
| 3.69 | from one end | 23.7M |
| 3.75 | from one end | 31.6M |
| 3.81 | from one end | 42.2M |
| 3.88 | from one end | 56.2M |
| 3.94 | from one end | 75M |
| 4 | from one end | 100M |
| 4.06 | from one end | 133M |
| 4.13 | from one end | 178M |
| 4.19 | from one end | 237M |
| 4.25 | from one end | 316M |
| 4.31 | from one end | 422M |
| 4.38 | from one end | 562M |
| 4.44 | from one end | 750M |
| 4.5 | from one end | 1B |
| 4.56 | from one end | 1.33B |
| 4.63 | from one end | 1.78B |
| 4.69 | from one end | 2.37B |
| 4.75 | from one end | 3.16B |
| 4.81 | from one end | 4.22B |
| 4.88 | from one end | 5.62B |
| 4.94 | from one end | 7.5B |
| 5 | from one end | 10B |
| 5.06 | from one end | 13.3B |
| 5.13 | from one end | 17.8B |
| 5.19 | from one end | 23.7B |
| 5.25 | from one end | 31.6B |
| 5.31 | from one end | 42.2B |
| 5.38 | from one end | 56.2B |
| 5.44 | from one end | 75B |
| 5.5 | from one end | 100B |
| 5.56 | from one end | 133B |
| 5.63 | from one end | 178B |
| 5.69 | from one end | 237B |
| 5.75 | from one end | 316B |
| 5.81 | from one end | 422B |
| 5.88 | from one end | 562B |
| 5.94 | from one end | 750B |
| 6 | from one end | 1,000B |
| 1 | bidirectional | 20 |
| 1.06 | bidirectional | 23.1 |
| 1.13 | bidirectional | 26.7 |
| 1.19 | bidirectional | 30.8 |
| 1.25 | bidirectional | 35.6 |
| 1.31 | bidirectional | 41.1 |
| 1.38 | bidirectional | 47.4 |
| 1.44 | bidirectional | 54.8 |
| 1.5 | bidirectional | 63.2 |
| 1.56 | bidirectional | 73 |
| 1.63 | bidirectional | 84.3 |
| 1.69 | bidirectional | 97.4 |
| 1.75 | bidirectional | 112 |
| 1.81 | bidirectional | 130 |
| 1.88 | bidirectional | 150 |
| 1.94 | bidirectional | 173 |
| 2 | bidirectional | 200 |
| 2.06 | bidirectional | 231 |
| 2.13 | bidirectional | 267 |
| 2.19 | bidirectional | 308 |
| 2.25 | bidirectional | 356 |
| 2.31 | bidirectional | 411 |
| 2.38 | bidirectional | 474 |
| 2.44 | bidirectional | 548 |
| 2.5 | bidirectional | 632 |
| 2.56 | bidirectional | 730 |
| 2.63 | bidirectional | 843 |
| 2.69 | bidirectional | 974 |
| 2.75 | bidirectional | 1.12K |
| 2.81 | bidirectional | 1.3K |
| 2.88 | bidirectional | 1.5K |
| 2.94 | bidirectional | 1.73K |
| 3 | bidirectional | 2K |
| 3.06 | bidirectional | 2.31K |
| 3.13 | bidirectional | 2.67K |
| 3.19 | bidirectional | 3.08K |
| 3.25 | bidirectional | 3.56K |
| 3.31 | bidirectional | 4.11K |
| 3.38 | bidirectional | 4.74K |
| 3.44 | bidirectional | 5.48K |
| 3.5 | bidirectional | 6.32K |
| 3.56 | bidirectional | 7.3K |
| 3.63 | bidirectional | 8.43K |
| 3.69 | bidirectional | 9.74K |
| 3.75 | bidirectional | 11.2K |
| 3.81 | bidirectional | 13K |
| 3.88 | bidirectional | 15K |
| 3.94 | bidirectional | 17.3K |
| 4 | bidirectional | 20K |
| 4.06 | bidirectional | 23.1K |
| 4.13 | bidirectional | 26.7K |
| 4.19 | bidirectional | 30.8K |
| 4.25 | bidirectional | 35.6K |
| 4.31 | bidirectional | 41.1K |
| 4.38 | bidirectional | 47.4K |
| 4.44 | bidirectional | 54.8K |
| 4.5 | bidirectional | 63.2K |
| 4.56 | bidirectional | 73K |
| 4.63 | bidirectional | 84.3K |
| 4.69 | bidirectional | 97.4K |
| 4.75 | bidirectional | 112K |
| 4.81 | bidirectional | 130K |
| 4.88 | bidirectional | 150K |
| 4.94 | bidirectional | 173K |
| 5 | bidirectional | 200K |
| 5.06 | bidirectional | 231K |
| 5.13 | bidirectional | 267K |
| 5.19 | bidirectional | 308K |
| 5.25 | bidirectional | 356K |
| 5.31 | bidirectional | 411K |
| 5.38 | bidirectional | 474K |
| 5.44 | bidirectional | 548K |
| 5.5 | bidirectional | 632K |
| 5.56 | bidirectional | 730K |
| 5.63 | bidirectional | 843K |
| 5.69 | bidirectional | 974K |
| 5.75 | bidirectional | 1.12M |
| 5.81 | bidirectional | 1.3M |
| 5.88 | bidirectional | 1.5M |
| 5.94 | bidirectional | 1.73M |
| 6 | bidirectional | 2M |
This has a clean architectural consequence. Shallow traversal from few starting points is an OLTP workload (many small, fast, interactive operations). Anything else is an OLAP workload (few large scans over all the data). They want opposite systems: pointer-chasing over mutable adjacency, versus scan-shaped iteration over immutable CSR. Treating "graph" as one workload is the source of most disappointment in this family.
Pattern matching (finding a small shape, such as a triangle of three mutually-following users, inside a big graph) is a third cost regime, different from traversal. It is where worst-case-optimal join algorithms live: multiway join methods whose running time is bounded by the largest possible output size. They matter because ordinary pairwise joins can build huge intermediate results on triangle-like patterns.
BFS and DFS differ in memory shape, not in edges touched
| Breadth-first (BFS) | Depth-first (DFS) | |
|---|---|---|
| What it holds | the whole frontier, up to about d^k vertices | one path of depth k (tiny) |
| Parallel? | yes: each level is synchronous, so it can be split across workers and machines | inherently sequential |
| Typical use | every distributed graph system | single-machine pattern matchers |
Parallelism costs memory here. This is the general trade between ordering and parallelism, appearing in its memory-shaped form.
Bidirectional search deserves one line, because it is the only real asymptotic win available. Search k/2 hops from both ends and meet in the middle: the cost is 2·d^(k/2) instead of d^k. With d = 100 and k = 4: 2 × 100² = 20,000 instead of 100,000,000. It needs both endpoints, so it serves shortest-path queries and nothing else.
One line: traversal is fixpoint iteration whose cost is exponential in depth until it saturates the component. Shallow-from-few is OLTP; everything else is a disguised full scan.
Degree skew: the real central mechanism
Real graphs are power-law (also called heavy-tailed): a handful of vertices have degrees in the millions, while the median vertex has a handful. (This is the Zipf shape: a few items take most of the weight.) This is not a data-quality problem to be cleaned. It is the empirical shape of essentially every graph anyone cares about, including Haatly's follower graph.
It breaks partitioning, and the common explanation is wrong. The problem is not that balanced graph partitioning is NP-hard (computationally intractable in the worst case). Approximation algorithms exist and would be fine. The problem is that in a power-law graph there is no good balanced cut to find. The graph has no sparse separators, so every balanced partition cuts an enormous fraction of edges. Better partitioners cannot help, because they are searching for something that does not exist.
That forces a real change of representation:
| Edge-cut | Vertex-cut | |
|---|---|---|
| Unit assigned to a machine | each vertex (with its edges) | each edge |
| What is split | edges across the boundary | high-degree vertices, which are replicated on every machine holding one of their edges |
| Hub with 2 million edges | one machine owns all of them and becomes the bottleneck | its edges spread over all machines |
| Price | hot spots | replication, plus reconciling replica state every round |
Vertex-cut balances load on edges, which is what the work is actually proportional to.
Notice the shape of that resolution: when one entity is too large to place, split it and reconcile. It is the same move as salting a skewed join key (appending a random suffix so one hot key becomes several smaller keys, then merging the partial results), as sharding a hot partition, and as splitting a hot key in a stream. The constant cost is the same every time: an extra reconciliation step that the unskewed case does not need.
Skew also makes the average degree nearly useless for cost estimation. A traversal's cost is dominated by whether it hit a hub, which depends on the data and not on the query. This is variance costing more than volume, in its strongest form here: the variance of degree, not the mean, determines both latency and the failure mode, and a query planner with only average statistics will be wrong in the direction that matters.
One line: power-law graphs have no sparse balanced cuts to find, so you replicate hubs and reconcile. Degree variance, not the mean, sets every cost.