Artifact Registry ADR 007: carry size in the shadow table's primary key
What
blob_storage_blobs_by_namespace's primary key becomes (namespace_id, sha256) INCLUDE (size), and the document now says which of the table's two indexes serves which read.
Why
!20663 (merged) established that the repository- and artifact-level recomputes resolve each walked digest through the shadow table rather than blob_storage_blobs. It credited that lookup to the covering index on (namespace_id) INCLUDE (size):
the shadow is keyed
(namespace_id, sha256)with a covering index on(namespace_id) INCLUDE (size), so the lookup is a single-partition index-only scan
That index cannot serve the lookup. sha256 is neither a key nor an included column in it, so a per-digest lookup goes through the primary key and heap-fetches size for every matched row. The scan is single-partition — namespace_id is a literal — but not index-only.
Adding INCLUDE (size) to the primary key makes the claim true. The cost is size in the primary key's leaf pages, which is the same 8 bytes per row the covering index already carries, on a table whose whole purpose is making these two reads cheap.
Which index serves what, after this
- Primary key
(namespace_id, sha256) INCLUDE (size)— per-digest lookups from repository- and artifact-level reconciliation, index-only. - Covering index
(namespace_id) INCLUDE (size)— the whole-namespaceSUM(size), which has nosha256to look up and so cannot use the primary key.
The asymmetry runs one way only: the covering index cannot serve the per-digest lookups at all, while the primary key can serve the whole-namespace sum, just over wider entries. Both stay because each is the cheaper scan for one of the two reads.
Downstream
The S22 storage accounting spec in gitlab-org/ops/artifact-registry carries the same correction in !1358, which waits on this one, and the S22 plan (!1296) sizes the shadow's migration against this index set.