docs(plans): key the S31 rules read on namespace_id in spec and plan
What this does
Corrects the S31 batched rules-read predicate in the spec and the plan. It was
written as WHERE npm_virtual_repository_upstream_id = ANY(...), with no
namespace_id equality.
The index the spec already gives for npm_virtual_upstream_rules, which !2099 (merged)
creates as index_nvur_on_ns_id_repository_upstream_id, leads on namespace_id.
A predicate that names only the foreign-key column therefore matches no usable
index prefix and gives the planner nothing to prune the 64 hash partitions
with, so the read becomes an Append over every partition and grows with the
table rather than with the rule set.
Measured on PostgreSQL 16.15 over 200k rules in 20k namespaces, 5 upstreams per namespace and 2 rules per upstream:
| Predicate | Plan | Buffers | Time |
|---|---|---|---|
with namespace_id |
Index Scan, 1 partition | 3 | 0.039 ms |
| without | Append of 64 Seq Scans |
2303 | 11.5 ms |
Reversing the index column order does not rescue the shorter form either: it becomes 64 bitmap index scans, 129 buffers, still every partition. The predicate is the half that has to change, which is why this is a spec fix and not an index fix.
This sits on the npm install path, where a client issues one request per dependency.
Why now
The branch review of !2099 (merged) found it. That MR ships the table and states the requirement in its index comment, but a step MR cannot edit the plan and the correction belongs in the artifacts Step 4 is written from, not in a migration comment nobody opens.
Scope
Four passages carried the uncorrected predicate, not one:
docs/specs/S31-npm-virtual.md— the resolution enumeration, plus a new paragraph saying why the equality is required.docs/plans/2026-08-21-s31-npm-virtual.md## Research Findings— the quote of that enumeration, which would otherwise have started misattributing text to the spec, plus a bullet recording the correction.- The same plan's squawk
LIMITargument, which named only the foreign-key column. It now names both and says which one decides: the foreign-key column is still non-unique, so the read stays on theLIMIT-requiring side of that exemption. Thenamespace_idequality buys index use and partition pruning, not uniqueness. - The same plan's Step 4 Files entry.
The spec's index row is unchanged; it was already correct.
The nvr abbreviation family, added after review
Duo asked whether index_nvur_on_ns_id_repository_upstream_id, the index name
this MR introduces to the plan, was a transcription typo for nvr. It is not:
!2099 (merged) ships that name verbatim. The plan's own wording is what invited the
question, because it wrote "the nvr scheme" and "the nvr abbreviation
family" as if nvr were one string all three npm_virtual_* tables share.
The family is one prefix per table, each the initial of every word of its own
table's name, and docs/dev/database.md requires them to differ ("a form no
other table in the schema has taken"):
| Table | Prefix | Shipped by |
|---|---|---|
npm_virtual_repositories |
nvr |
!1971 (merged) |
npm_virtual_repository_upstreams |
nvru |
!2066 (merged) |
npm_virtual_upstream_rules |
nvur |
!2099 (merged) |
Six passages carried the ambiguity, so all six are fixed rather than the one the index name sits in:
## Naming Conventions, which owns the convention, now lists the three prefixes and says why they cannot collapse to one. It no longer attributes the choice to Step 2, which merged after Step 1 had already shippednvr.## Research Findingsno longer calls it "the prefix", singular.- The same section's free-family grep is rewritten. Its old pattern matched a
nvrsubstring, which missesnvurentirely. The claim is now anchored to40e080250, the commit that added this plan, since the grep returns the family's own names once each schema step merges. - Steps 1, 2 and 3 each name their own prefix instead of "the
nvrscheme". - Step 4 says which table
nvurbelongs to, where the index name appears.
Verified on the merged migrations: at 40e080250 the grep returns nothing, and
at this branch's merge base it returns fk_nvr_, unique_nvr_, fk_nvru_,
check_nvru_, index_nvru_ and unique_nvru_.
Issue #885 (closed), which is the artifact Step 4 is actually written from, was corrected the same way outside this MR.
Testing
Documentation only. No code, no schema, no generated file. markdownlint-cli2,
vale and lychee pass through pre-commit.
No e2e scenario is added or affected, and no conformance suite applies: this MR changes no behavior.
Related to #885 (closed)