Skip to content

ask: multi-scope PageRank rebuilds the full graph topology once per scope #202

Description

@Skyline-23

Problem

For a multi-scope repository, ask runs personalized PageRank separately for each participating scope. Each call currently applies a nodeFilter by rebuilding the real-node set and scanning every graph edge to reconstruct the filtered adjacency.

With S matching scopes, topology preparation therefore costs approximately:

O(S × (nodes + edges))

even though the scopes are disjoint partitions of the same immutable graph. Large monorepos can turn a single query into many repeated full-graph scans before scope fusion completes.

User-visible effect

Preparing all disjoint scope topologies in one pass reduces CPU work and query latency in multi-scope repositories. The benefit grows with the number of matching scopes because the graph is scanned once instead of once per scope.

It does not change retrieval order or PageRank values. It also deliberately has no effect on single-scope repositories, graphRank: false, or no-match queries.

Synthetic graph-ranking benchmark

Local Node.js v22.22.0 arm64 microbenchmark with 12,000 nodes, 36,000 intra-scope edges, the normal 25 PageRank iterations, two warm-ups, and the median of seven measured runs:

matching scopes current filtered walks prepared partitions speedup
5 9.87 ms 6.27 ms 1.6×
20 28.44 ms 11.98 ms 2.4×
50 72.96 ms 21.43 ms 3.4×

This isolates the graph-ranking stage rather than claiming an end-to-end graft ask latency improvement. Real impact depends on graph shape, the number of matching scopes, and the share of query time spent outside PageRank.

Expected behavior

Partition the graph topology once, then reuse each prepared partition for its scope-specific seed walk:

O(nodes + edges) topology preparation
+ per-scope PageRank iteration

The optimization should:

  • preserve exact PageRank scores, edge multiplicity, and deterministic order
  • exclude cross-scope and unresolved edges exactly as independent filtered walks do
  • remain lazy so graphRank: false and no-match queries do not scan graph edges
  • leave single-scope behavior unchanged

Reproduction / regression guard

A graph with two independent scopes can expose counters on its nodes and edges arrays. Preparing both partitions should read each array once, and running both prepared walks should produce byte-equivalent entries to two independent nodeFilter walks.

I have a focused implementation and regression coverage ready.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions