chore(datastore): maven metadata discovery queries (S20-A Maven metadata purge plan: 3/5)
What
Adds an unregistered discovery query for tombstoned hosted Maven
package-level maven-metadata.xml files:
ScanTombstonedMavenMetadataFiles returns one bounded, namespace-scoped
page of eligible tombstones, oldest-first, built on the metadata-only
partial index Step 1 of this plan already landed on main
(index_maven_files_on_ns_id_soft_deleted_at_metadata). It follows the
same shape as the existing ScanTombstonedRepositories/*Packages/
*Versions scans: shared argument guards
(validateLifecycleScanArgs), an identifier-free error wrap, and a
MaxLifecycleScanPageSize-bounded LIMIT.
This is discovery only. Nothing calls it yet — internal/lifecycle/purge.go's
DefaultPurgeLevels and the CountTombstonesByLevel backlog gauge are
unchanged. Wiring it into the purge sweep is Step 5 of this plan; Step 2's
single-row reaper (!2731, merged) is what it will eventually feed.
Testing
- Unit (
lifecycle_scan_maven_metadata_test.go): pins the statement's sort key, its three bound arguments, and its two index-matching conjuncts; covers every guard-rejection path (nil context, zero namespace, zero/ negative/oversized limit, negative window) on a client-less store. - Integration:
TestLifecycleScanStore_ScanTombstonedMavenMetadataFiles(lifecycle_scan_maven_metadata_integration_test.go) covers the four cases specific to the metadata level — discovery while the package and repository stand, version-level exclusion, other-file-name exclusion, and two tombstones at one coordinate. The cases this scan shares with the other package-level scans (window handling, live-row exclusion, ordering, paging, namespace isolation, and the query-failure wrap) run once, table-driven, via themaven metadata filesrow added topackageLevelScans/packageLevelScanTargets(lifecycle_scan_packages_integration_test.go,lifecycle_scan_packages_test.go). The EXPLAIN-backed proof that the query is index-backed, oldest-first, and one-partition runs the same way, as a row inTestLifecycleScanStore_PackageLevelScans_AreIndexBackedOldestFirst(lifecycle_explain_integration_test.go).
Database Review Evidence
Note
Plans are from EXPLAIN (ANALYZE, BUFFERS) against an ephemeral
PostgreSQL 17.11 container (matching GL_PG_CURR_VERSION from
.gitlab-ci-other-versions.yml), with synthesized seed data rolled
back per query and the container torn down at the end of the run.
Numbers reflect moderate cardinality and do not capture
production-scale effects. See
Database review evidence
for seed sizing, methodology, and the anomalies the skill flags.
Expand the row's details for the seed shape, rendered SQL, bound args,
and raw plan.
Queries
| Method | Plan node | Index | Rows (plan / actual) | Cost | Time | Buffers (hit / read) | Partitions |
|---|---|---|---|---|---|---|---|
datastore.ScanTombstonedMavenMetadataFiles |
Limit | maven_files_p14_namespace_id_soft_deleted_at_metadata_idx |
25 / 25 | 3.15 | 0.015ms | 4 / 0 | 1/64 |
datastore.ScanTombstonedMavenMetadataFiles
Summary: Plan matches the method's intent: a backward Index Scan over the metadata-only partial index, with the namespace_id equality pruning to 1 of 64 partitions and no separate sort node. Actual rows match the LIMIT estimate (25 / 25) and execution stays under 0.03ms against 5000 seeded tombstones. No anomalies.
Seed shape: namespaces=1, repositories=1, maven_repositories=1, maven_packages=1, blob_storage_blobs=1, blob_storage_attachments=1, maven_files=5000 (one package coordinate, all tombstoned — legal because
the live-row unique index is partial on soft_deleted_at IS NULL)
Rendered SQL:
SELECT maven_files.namespace_id AS "tombstone_row.namespace_id",
maven_files.id AS "tombstone_row.id",
maven_files.soft_deleted_at AS "tombstone_row.soft_deleted_at"
FROM public.maven_files
WHERE (((maven_files.namespace_id = $1::uuid) AND (maven_files.maven_version_id IS NULL)) AND (maven_files.file_name = $2::text)) AND (maven_files.soft_deleted_at < (NOW() - INTERVAL '1 HOUR'))
ORDER BY maven_files.soft_deleted_at ASC
LIMIT $3;Bound args: [<seeded namespace_id>, "maven-metadata.xml", 25]
Plan (EXPLAIN (ANALYZE, BUFFERS) output):
Limit (cost=0.29..3.15 rows=25 width=40) (actual time=0.011..0.015 rows=25 loops=1)
Buffers: shared hit=4
-> Index Scan Backward using maven_files_p14_namespace_id_soft_deleted_at_metadata_idx on maven_files_p14 maven_files (cost=0.29..573.04 rows=5000 width=40) (actual time=0.011..0.013 rows=25 loops=1)
Index Cond: ((namespace_id = 'eacca14d-e1e6-7d9c-8c09-cd29243a07c6'::uuid) AND (soft_deleted_at < (now() - '01:00:00'::interval)))
Buffers: shared hit=4
Planning:
Buffers: shared hit=452 read=2
Planning Time: 2.043 ms
Execution Time: 0.025 msTimings: planning 2.043ms, execution 0.025ms, total 2.068ms.
Related to #1167 (closed)