test(datastore): seed the tags deep-page sibling in its own repository
Summary
TestContainerRemoteTagStore_DeepPageIsIndexBacked flips per run and per PostgreSQL matrix leg: its EXPLAIN assertion bans Rows Removed by Filter, and PG's planner on some statistics draws satisfies a side of the three-table join with a bitmap over live-row index prefixes instead of the parameterized point lookup, scanning past a seeded row and discarding it on a plan that changes no results. The flip is statistics-draw noise: identical datastore code failed test:integration: [POSTGRES, 17] on main at 385fbfd39 (pipeline 2791792440, job 16113708546) and passed the same job eleven minutes later at 4791309a8 with no datastore change between. See issue #872 (closed) for the full flip record across PG16/17/18.
This MR keeps the strict pin and makes it deterministic by construction — the seed closes every relation:
- Images: the sibling moves to its own repository. Every images index binds
(namespace_id, container_remote_repository_id)or(id, namespace_id), so the addressed repository holding exactly one image makes every access path resolve to that one row. - Manifests: the sibling seed no longer creates a manifest; its 2000 tags point at the target's manifest instead (the Shape 1 cross-image pointer the composite FKs permit — the image and manifest references are constrained separately). The namespace then holds one live manifest, so even the non-partial namespace-only manifests indexes (
ns_id_blob_sha256,ns_id_created_at_id,ns_id_bsa_id) resolve to the one wanted row. - Tags: the keyset bound and ordering still ride
unique_container_remote_tags_ns_id_image_id_name; the sibling's 2000 tags keep the namespace's partition large enough that a Seq Scan is not the honest plan for the page (their documented job — hash partitioning keys on namespace_id).
The assertion's failure message states the invariant it checks: with the seed closing every relation, a discard line can only mean the keyset bound stopped folding or a scan widened beyond its index conds. The previous message's claim ("the two joined tables' soft_deleted_at checks degrading to a post-join filter") is unreachable in this seed: all live-row indexes in this table family are partial on soft_deleted_at IS NULL, so soft-deleted rows never enter any access path the pin can see, and a dropped soft_deleted_at conjunct is caught by the result-level Shape 1-3 tests instead.
The issue's proposed direction (run ANALYZE before the EXPLAINs) is already implemented in the test (analyzeBeforeExplain) and did not stop the 08:46 UTC main flip.
Governing ADRs
None. The change is test-only; no ADR governs plan-shape assertion seeding.
Testing
go test -tags=integration ./internal/datastore/ -run TestContainerRemoteTagStore_DeepPageIsIndexBacked -count=30on PostgreSQL 17.11: 30/30 pass.- Same test,
-count=15on PostgreSQL 16-alpine (testcontainers path) and PostgreSQL 18-alpine: 15/15 each. - EXPLAIN (ANALYZE, BUFFERS) verified on PG17: point lookups throughout (manifests pkey, images unique/pkey), zero
Rows Removed by Filter, zero Bitmap/Seq scans. - The original seed was also run 60x on PG17.11 locally (all green — the flip needs the fuller shared-DB statistics CI accumulates), confirming the change removes the failing shape rather than merely reducing its probability: with the closed seed the discard line is impossible in any access path.
scripts/ci/check-comment-caps.shpasses (the new body comments are at the 2-line test-file cap; the full analysis lives here and in issue #872 (closed), per the caps policy).
Related to #872 (closed)