Link Graph Encyclopedia

Computing at scale

PageRank at scale

The arithmetic of ranking a hundred million nodes and billions of links. What must fit in memory, what each pass costs, why random reads dominate, and why one well-programmed machine often beats a cluster.

The PageRank formula fits on a line; computing it for Common Crawl’s 119,722,885 domains and 2,450,405,793 links is an exercise in memory budgets. This entry works through the numbers for that graph. The same arithmetic applies, with larger constants, to page-level graphs with tens of billions of links.

What must be stored

Item Size per element Total for the CC domain graph
Score vector, 64-bit doubles 8 bytes per node 958 MB
Second score vector (for Jacobi-style passes) 8 bytes per node 958 MB
Out-degrees (or their reciprocals) 4 bytes per node 479 MB
Transposed graph as CSR, 32-bit ids 4 bytes per arc + offsets 10.3 GB
Transposed graph in WebGraph format about 15 bits per arc 4.6 GB

A pull-style computation (each node sums over its in-links) needs the transposed graph, the out-degrees and one or two score vectors: about 6.5 GB with the compressed graph, or 12 GB with plain CSR. Gauss–Seidel updates scores in place and needs only one vector (see PageRank as a linear system). Single-precision floats would halve the vectors, but the smallest scores sit near 4×10−94 \times 10^{-9} and are sums of many tiny contributions; most serious implementations, including LAW’s, use doubles.

What one pass costs

A pass reads every arc once. The arcs themselves stream sequentially: 9.8 GB of CSR targets takes well under a second at the 20–30 GB/s a modern server’s memory delivers. The expensive part is the score lookup that each arc triggers. In a pull pass, the arc u→vu \to v reads p(u)p(u), and uu can be anywhere in a 958 MB vector. If every such read misses the cache, each costs a full 64-byte cache line from main memory:

2.45×109 arcs×64 bytes≈157 GB per pass,2.45 \times 10^{9} \text{ arcs} \times 64 \text{ bytes} \approx 157 \text{ GB per pass},

roughly 5 to 8 seconds of pure memory traffic per pass, far more than the sequential reads. Locality decides the real cost. A locality-preserving order makes many neighbouring arcs read neighbouring scores, so one cache line serves several arcs. Heavy-tailed in-degrees help too: the scores of the few hub nodes that appear in millions of in-lists stay in cache.

With 50 to 100 passes for a tight tolerance, or about half that with Gauss–Seidel, a well-ordered in-memory computation on one multicore machine takes minutes.

When the graph does not fit

Clusters and their COST

Distributed graph systems split the nodes across machines and exchange score messages along cut arcs each pass. They scale to graphs that no single machine can hold, but communication dominates: every arc between machines becomes network traffic on every pass. McSherry, Isard and Murray’s “Scalability! But at what COST?” (2015) measured the configuration that outperforms a single thread, and found that several published distributed systems needed dozens to hundreds of cores just to match one well-written single-threaded PageRank on a laptop for graphs with billions of links. For graphs the size of Common Crawl’s domain graph, a single large machine is the practical choice.

Load balance and hubs

Pull-style parallel passes split nodes among threads. A node with 17 million in-links, such as googleapis.com, makes one thread do 17 million additions while the others idle, unless the work is split by arcs rather than nodes. Edge-balanced partitioning, or splitting the in-lists of the largest hubs, keeps all cores busy. Push-style passes avoid the hub problem but need atomic additions on targets.

Stopping and verifying

References

Edit this entry on GitHub