chore(datastore): maven package and version management reads
Why
S17 Phase 3 serves the artifact browse surface the monolith repository-detail and artifact-detail slices consume, and the Maven package and version reads it needs do not exist. The pre-existing Maven store carried protocol-shaped queries only: no keyset list, no repository-scoped detail, no chain verification. This is Step 6 of the merged plan, under S17 Phase 3: format artifact reads (#312 - closed) • Hayley Swimelar.
Originally stacked on chore(datastore): add artifact read keyset inde... (!1125 - merged) • Hayley Swimelar • 19.3. The created_at version sort rides the index that MR added, because the pre-existing index_maven_versions_on_ns_id_pkg_id_id is id-ordered and cannot back it. That MR merged, and this one targets main.
What
Management-shaped read methods in internal/datastore, per the plan's decision to keep Maven management reads on the Jet models. The protocol-SQL relocation landed on main mid-review, so after the rebase these reads live on the same per-entity MavenPackageStore and MavenVersionStore the protocol writes use, merged there by refactor(maven): relocate protocol SQL into per... (!1143 - merged) • David Fernandez • 19.3.
FindMavenVersionByID verifies version to package to repository in two statements rather than one join. Neither form can push the package id into maven_packages at plan time, because that id lives in the version row the query has not read yet, so both are estimate-driven. The split bounds the worst case to a range over one repository's packages instead of a walk of every package in the namespace. The method comment carries the measurements and the read race the split accepts.
Reviewable diff is 3,277 added lines against main, over the 500 guideline: 974 production, the rest tests. The two stores share two production helpers, and the test suites share the seed, walk, and EXPLAIN scaffolding, so splitting along the package and version seam yields two MRs that both still exceed it. Kept as one.
Test plan
test:integration runs both suites against PostgreSQL 16, 17, and 18. Locally:
go test ./internal/datastore/
go test -tags=integration ./internal/datastore/ -run 'TestMavenPackageStore|TestMavenVersionStore'The EXPLAIN gates are the ones worth reading. Each sort must ride its own index with the cursor bound folded into the Index Cond, and each detail leg must be a one-row primary-key seek that joins nothing.
Spec coverage
| Spec behavior | Test |
|---|---|
Packages list, name asc, ordered by (group_id, artifact_id) |
TestMavenPackageStore_ListMavenPackages |
Versions list, created_at desc default and lexicographic version |
TestMavenVersionStore_ListMavenVersionsByPackage |
| Keyset pages walk gap-free in both directions | the two walkMaven* helpers, which reject a repeat id |
created_at ties settled by the id tiebreaker |
TestMavenVersionStore_ListMavenVersionsByPackage_SameCreatedAtTiebreak |
| Every list order is index-backed at depth | ..._IsIndexBacked |
| Version detail verifies the chain to the repository | TestMavenVersionStore_FindMavenVersionByID |
| Detail reads stay one-row seeks | ..._IsPrimaryKeySeek, ..._IsTwoPrimaryKeySeeks |
| Soft-delete excluded at every level, soft-deleted parents included | ..._Scoping, plus the soft-deleted-package cases in both detail suites |
| Tenant isolation, and every chain break reads the same | the cross-namespace and sibling-repository cases in both detail suites |
| Invalid sort, order, limit, or cursor rejected before any query | the guard tables in both unit suites |
| A database failure is not a miss | TestWrapMavenPackageErr, TestWrapMavenVersionChainErr |
Context for LLM agents
Design decisions and rejected alternatives
Two statements for the version detail chain, not one join. Two independent review passes measured this. The single-statement form (both INNER JOIN and correlated EXISTS, which the planner unnests into the same join) plans as a nested loop with the inner side parameterized by maven_package_id whenever statistics are fresh, so the earlier claim that the push-down was impossible was wrong and the comment was rewritten. What decides it is the degraded case, which the database review below measured: with statistics stale on new rows in a ten-repository namespace, the single statement lost the parameterized probe and walked every package in the namespace behind a join filter, 5,044 buffer hits against 10. It cannot do better once the probe is gone, because the package primary key leads with id and leaves maven_repository_id no prefix to bound the walk. The two-statement parent leg keeps one, so its worst case is a range over a single repository's packages. The EXPLAIN gates cannot prove either bound, because analyzeBeforeExplain runs first and they therefore observe the fresh-statistics plan. What they do hold is that the intended shape is reachable and that neither leg joins. A rejected alternative was dropping maven_repository_id from the parent leg and comparing it in Go, which would leave the primary key as the only usable path but move a tenant-scoping predicate out of SQL.
Chain scoping stops at maven_repositories.id. That table has no soft_deleted_at, and the handler resolves the repositories parent, including its soft-delete state, before it reaches these stores. The research finding the plan records is that artifact queries start from the child-row id the per-family resolver returns.
The version list carries no parent leg. It scopes on (namespace_id, maven_package_id) only, so it cannot tell a live parent from a soft-deleted one. That is the plan's resolution flow: the handler reads the package detail first, which is what separates a missing parent's 404 from an existing parent's empty 200. An integration subtest pins the resulting behavior so a later MR cannot "harden" the list with a redundant parent probe without noticing.
Order has no safe zero value. Ascending is the zero value, matching RepositorySortOrder and the npm sibling, but the spec default for the version list is created_at descending. An unset sentinel rejected by valid() was considered and declined, because it would diverge from the two sibling stores that Step 12's shared handler consumes. The field carries a comment instead.
Store-side page ceilings. Both lists bound Limit to (0, max] on their table's page constant (MaxMavenVersionsPageSize, maxMavenPackagesPageSize), matching the npm siblings: an unbounded LIMIT holds one pool connection while materialising a whole set, and no statement_timeout backstops it. Clamping to managementapi.maxPageSize still belongs to the Step 11 and 12 handlers, which must route limit through parseLimitParam and pin it with a handler test.
Cursor boundaries are bounded at the store. LabKit's pool runs pgx in simple-protocol mode, so a boundary reaches PostgreSQL as query text. A NUL, invalid UTF-8, or a year outside the timestamptz range fails the whole statement rather than the comparison, which the handler would render 500 on a malformed cursor. The byte-length rule is safe because validCoordinateField in internal/format/maven/parse.go restricts coordinates to ASCII within 255 bytes. The column CHECK counts characters and does not support the bound on its own.
Non-goals
- Handlers and wiring. Steps 11 and 12 own
handler.goandwire_management.go. Nothing calls these stores yet, which is why there is no metric, no log line, and no HTTP behavior to review here. - Unifying the near-duplicate Maven test helpers. Step 7 merged first (
5ab70a0e) and the protocol relocation followed, so this branch now sits on top of both. The helpers still exist in two shapes:insertMaven*Rowhere vs the relocation'sseedMaven*,flattenMavenSQLvsnormalizeSQL, andvalidMavenKeysetTextvsvalidMavenFileName. Adoption is not drop-in, becauseinsertMavenVersionRowtakes the explicitcreated_atthe keyset walks require andseedMavenVersionlacks. Unification is deferred to Unify the duplicated Maven datastore test and c... (#475 - closed) • Hayley Swimelar. - Finishing the store consolidation. The relocation itself landed mid-review and the rebase absorbed it, closing Relocate Maven datastore SQL from internal/form... (#372 - closed) • David Fernandez • 19.3. The remaining helper unification moved to Unify the duplicated Maven datastore test and c... (#475 - closed) • Hayley Swimelar and stays out of this MR.
- Conformance and e2e catalogs. Management-API surface, not Maven protocol behavior. The plan's Testing Strategy states this explicitly.
Database Review Evidence
Queries
Note
Plans are from EXPLAIN (ANALYZE, BUFFERS) against an ephemeral PostgreSQL
17.10 container (matching GL_PG_CURR_VERSION from
.gitlab-ci-other-versions.yml), with synthesized seed data torn down at the
end of the run. This run leaves the planner at its defaults, so each plan is
the planner's own choice: the suite's explainListPlan pins
enable_seqscan = off, which would hide a fallback. LIMIT is bound to 21,
a page size of 20 plus the hasMore probe row. Both stores bound the page
size to their table's max page constant, and the handler owns the much lower
request ceiling. The Index
column names the logical parent index, and each plan below carries the
partition child Postgres actually scanned. 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 each row's details for the seed shape, rendered SQL, bound args, and
raw plan.
| Method | Plan node | Index | Rows (plan / actual) | Cost | Time | Buffers (hit / read) | Partitions |
|---|---|---|---|---|---|---|---|
datastore.MavenPackageStore.ListMavenPackages.Ascending |
Limit | unique_maven_packages_ns_id_repo_id_group_id_artifact_id |
21 / 21 | 2.82 | 0.040ms | 3 / 0 | 1 of 64 |
datastore.MavenPackageStore.ListMavenPackages.Descending |
Limit | unique_maven_packages_ns_id_repo_id_group_id_artifact_id |
21 / 21 | 2.82 | 0.037ms | 5 / 0 | 1 of 64 |
datastore.MavenPackageStore.ListMavenPackages.Ascending.CursorNil |
Limit | unique_maven_packages_ns_id_repo_id_group_id_artifact_id |
21 / 21 | 2.27 | 0.041ms | 3 / 0 | 1 of 64 |
datastore.MavenVersionStore.ListMavenVersionsByPackage.CreatedAtDescending |
Limit | index_maven_versions_on_ns_id_pkg_id_created_at_id |
21 / 21 | 2.75 | 0.028ms | 3 / 0 | 1 of 64 |
datastore.MavenVersionStore.ListMavenVersionsByPackage.CreatedAtAscending |
Limit | index_maven_versions_on_ns_id_pkg_id_created_at_id |
21 / 21 | 2.75 | 0.032ms | 3 / 0 | 1 of 64 |
datastore.MavenVersionStore.ListMavenVersionsByPackage.VersionAscending |
Limit | unique_maven_versions_ns_id_pkg_id_version |
21 / 21 | 2.65 | 0.023ms | 3 / 0 | 1 of 64 |
datastore.MavenVersionStore.ListMavenVersionsByPackage.VersionDescending |
Limit | unique_maven_versions_ns_id_pkg_id_version |
21 / 21 | 2.65 | 0.021ms | 3 / 0 | 1 of 64 |
datastore.MavenVersionStore.ListMavenVersionsByPackage.CreatedAtDescending.CursorNil |
Limit | index_maven_versions_on_ns_id_pkg_id_created_at_id |
21 / 21 | 2.18 | 0.029ms | 3 / 0 | 1 of 64 |
datastore.MavenPackageStore.FindMavenPackageInRepository |
Limit | pk_maven_packages |
1 / 1 | 8.30 | 0.014ms | 3 / 0 | 1 of 64 |
datastore.MavenVersionStore.FindMavenVersionByID.VersionLeg |
Limit | unique_maven_versions_id_pkg_id_ns_id |
1 / 1 | 8.30 | 0.013ms | 3 / 0 | 1 of 64 |
datastore.MavenVersionStore.FindMavenVersionByID.ParentPackageLeg |
Limit | pk_maven_packages |
1 / 1 | 8.30 | 0.021ms | 3 / 0 | 1 of 64 |
datastore.MavenPackageStore.ListMavenPackages.Ascending
Summary: Ascending keyset page. The tuple bound folds into the index range as an Index Cond, so the coordinate order comes from the partial unique index with no post-scan Sort and no rows discarded by a filter. namespace_id prunes to one of 64 partitions. No anomalies.
Seed shape: namespaces=1, repositories=1, maven_repositories=1, maven_packages=5001, maven_versions=5001
Rendered SQL:
SELECT maven_packages.namespace_id AS "maven_packages.namespace_id",
maven_packages.id AS "maven_packages.id",
maven_packages.maven_repository_id AS "maven_packages.maven_repository_id",
maven_packages.last_downloaded_at AS "maven_packages.last_downloaded_at",
maven_packages.soft_deleted_at AS "maven_packages.soft_deleted_at",
maven_packages.group_id AS "maven_packages.group_id",
maven_packages.artifact_id AS "maven_packages.artifact_id"
FROM public.maven_packages
WHERE (((maven_packages.namespace_id = $1::uuid) AND (maven_packages.maven_repository_id = $2::uuid)) AND (maven_packages.soft_deleted_at IS NULL)) AND ((maven_packages.group_id, maven_packages.artifact_id) > ($3::text, $4::text))
ORDER BY maven_packages.group_id ASC, maven_packages.artifact_id ASC
LIMIT $5;Bound args: [f0cc91ce-0906-44fd-b5d3-fb3c52231252 019fab69-3044-7b6f-9cae-af494f9e5a65 com.example.g02500 widget 21]
Plan (EXPLAIN (ANALYZE, BUFFERS) output):
Limit (cost=0.28..2.82 rows=21 width=89) (actual time=0.036..0.040 rows=21 loops=1)
Buffers: shared hit=3
-> Index Scan using maven_packages_p17_namespace_id_maven_repository_id_group_i_idx on maven_packages_p17 maven_packages (cost=0.28..302.60 rows=2500 width=89) (actual time=0.036..0.038 rows=21 loops=1)
Index Cond: ((namespace_id = 'f0cc91ce-0906-44fd-b5d3-fb3c52231252'::uuid) AND (maven_repository_id = '019fab69-3044-7b6f-9cae-af494f9e5a65'::uuid) AND (ROW(group_id, artifact_id) > ROW('com.example.g02500'::text, 'widget'::text)))
Buffers: shared hit=3
Planning Time: 0.105 ms
Execution Time: 0.068 msTimings: planning 0.105ms, execution 0.068ms, total 0.173ms.
datastore.MavenPackageStore.ListMavenPackages.Descending
Summary: Descending is the same index read backward, which is what mavenPackageOrderBy moving both columns together buys. Same one-partition prune, same folded bound, no Sort. No anomalies.
Seed shape: namespaces=1, repositories=1, maven_repositories=1, maven_packages=5001, maven_versions=5001
Rendered SQL:
SELECT maven_packages.namespace_id AS "maven_packages.namespace_id",
maven_packages.id AS "maven_packages.id",
maven_packages.maven_repository_id AS "maven_packages.maven_repository_id",
maven_packages.last_downloaded_at AS "maven_packages.last_downloaded_at",
maven_packages.soft_deleted_at AS "maven_packages.soft_deleted_at",
maven_packages.group_id AS "maven_packages.group_id",
maven_packages.artifact_id AS "maven_packages.artifact_id"
FROM public.maven_packages
WHERE (((maven_packages.namespace_id = $1::uuid) AND (maven_packages.maven_repository_id = $2::uuid)) AND (maven_packages.soft_deleted_at IS NULL)) AND ((maven_packages.group_id, maven_packages.artifact_id) < ($3::text, $4::text))
ORDER BY maven_packages.group_id DESC, maven_packages.artifact_id DESC
LIMIT $5;Bound args: [f0cc91ce-0906-44fd-b5d3-fb3c52231252 019fab69-3044-7b6f-9cae-af494f9e5a65 com.example.g02500 widget 21]
Plan (EXPLAIN (ANALYZE, BUFFERS) output):
Limit (cost=0.28..2.82 rows=21 width=89) (actual time=0.028..0.037 rows=21 loops=1)
Buffers: shared hit=5
-> Index Scan Backward using maven_packages_p17_namespace_id_maven_repository_id_group_i_idx on maven_packages_p17 maven_packages (cost=0.28..302.60 rows=2500 width=89) (actual time=0.027..0.035 rows=21 loops=1)
Index Cond: ((namespace_id = 'f0cc91ce-0906-44fd-b5d3-fb3c52231252'::uuid) AND (maven_repository_id = '019fab69-3044-7b6f-9cae-af494f9e5a65'::uuid) AND (ROW(group_id, artifact_id) < ROW('com.example.g02500'::text, 'widget'::text)))
Buffers: shared hit=5
Planning Time: 0.119 ms
Execution Time: 0.058 msTimings: planning 0.119ms, execution 0.058ms, total 0.177ms.
datastore.MavenPackageStore.ListMavenPackages.Ascending.CursorNil
Summary: First page, the branch where listMavenPackagesStmt omits the keyset bound. The predicate still matches the index prefix (namespace_id, maven_repository_id) and the LIMIT stops the scan at 21 rows out of an estimated 5001. No anomalies.
Seed shape: namespaces=1, repositories=1, maven_repositories=1, maven_packages=5001, maven_versions=5001
Rendered SQL:
SELECT maven_packages.namespace_id AS "maven_packages.namespace_id",
maven_packages.id AS "maven_packages.id",
maven_packages.maven_repository_id AS "maven_packages.maven_repository_id",
maven_packages.last_downloaded_at AS "maven_packages.last_downloaded_at",
maven_packages.soft_deleted_at AS "maven_packages.soft_deleted_at",
maven_packages.group_id AS "maven_packages.group_id",
maven_packages.artifact_id AS "maven_packages.artifact_id"
FROM public.maven_packages
WHERE ((maven_packages.namespace_id = $1::uuid) AND (maven_packages.maven_repository_id = $2::uuid)) AND (maven_packages.soft_deleted_at IS NULL)
ORDER BY maven_packages.group_id ASC, maven_packages.artifact_id ASC
LIMIT $3;Bound args: [f0cc91ce-0906-44fd-b5d3-fb3c52231252 019fab69-3044-7b6f-9cae-af494f9e5a65 21]
Plan (EXPLAIN (ANALYZE, BUFFERS) output):
Limit (cost=0.28..2.27 rows=21 width=89) (actual time=0.036..0.041 rows=21 loops=1)
Buffers: shared hit=3
-> Index Scan using maven_packages_p17_namespace_id_maven_repository_id_group_i_idx on maven_packages_p17 maven_packages (cost=0.28..472.93 rows=5001 width=89) (actual time=0.036..0.038 rows=21 loops=1)
Index Cond: ((namespace_id = 'f0cc91ce-0906-44fd-b5d3-fb3c52231252'::uuid) AND (maven_repository_id = '019fab69-3044-7b6f-9cae-af494f9e5a65'::uuid))
Buffers: shared hit=3
Planning Time: 0.120 ms
Execution Time: 0.064 msTimings: planning 0.120ms, execution 0.064ms, total 0.184ms.
datastore.MavenVersionStore.ListMavenVersionsByPackage.CreatedAtDescending
Summary: The spec's default sort. Rides index_maven_versions_on_ns_id_pkg_id_created_at_id backward, the index the parent MR adds. The (created_at, id) tuple bound folds into the range, so the id tiebreaker costs no post-scan work. No anomalies.
Seed shape: namespaces=1, repositories=1, maven_repositories=1, maven_packages=5001, maven_versions=5001
Rendered SQL:
SELECT maven_versions.namespace_id AS "maven_versions.namespace_id",
maven_versions.id AS "maven_versions.id",
maven_versions.maven_package_id AS "maven_versions.maven_package_id",
maven_versions.last_downloaded_at AS "maven_versions.last_downloaded_at",
maven_versions.soft_deleted_at AS "maven_versions.soft_deleted_at",
maven_versions.created_at AS "maven_versions.created_at",
maven_versions.version AS "maven_versions.version",
maven_versions.gitlab_user_id AS "maven_versions.gitlab_user_id",
maven_versions.gitlab_project_id AS "maven_versions.gitlab_project_id",
maven_versions.gitlab_git_commit_sha AS "maven_versions.gitlab_git_commit_sha"
FROM public.maven_versions
WHERE (((maven_versions.namespace_id = $1::uuid) AND (maven_versions.maven_package_id = $2::uuid)) AND (maven_versions.soft_deleted_at IS NULL)) AND ((maven_versions.created_at, maven_versions.id) < ($3::timestamp with time zone, $4::uuid))
ORDER BY maven_versions.created_at DESC, maven_versions.id DESC
LIMIT $5;Bound args: [f0cc91ce-0906-44fd-b5d3-fb3c52231252 019fab69-304e-7a58-81bb-bd3a7ab69418 2026-07-03 05:40:00 +0000 UTC 019fab69-330a-7dcd-b7d6-005c69138bf5 21]
Plan (EXPLAIN (ANALYZE, BUFFERS) output):
Limit (cost=0.28..2.75 rows=21 width=177) (actual time=0.024..0.028 rows=21 loops=1)
Buffers: shared hit=3
-> Index Scan Backward using maven_versions_p17_namespace_id_maven_package_id_created_at_idx on maven_versions_p17 maven_versions (cost=0.28..294.60 rows=2500 width=177) (actual time=0.023..0.026 rows=21 loops=1)
Index Cond: ((namespace_id = 'f0cc91ce-0906-44fd-b5d3-fb3c52231252'::uuid) AND (maven_package_id = '019fab69-304e-7a58-81bb-bd3a7ab69418'::uuid) AND (ROW(created_at, id) < ROW('2026-07-03 05:40:00+00'::timestamp with time zone, '019fab69-330a-7dcd-b7d6-005c69138bf5'::uuid)))
Buffers: shared hit=3
Planning:
Buffers: shared hit=1
Planning Time: 0.149 ms
Execution Time: 0.045 msTimings: planning 0.149ms, execution 0.045ms, total 0.194ms.
datastore.MavenVersionStore.ListMavenVersionsByPackage.CreatedAtAscending
Summary: Same index read forward. Estimate and actual agree at the root (21 / 21) and the bound is index-resident. No anomalies.
Seed shape: namespaces=1, repositories=1, maven_repositories=1, maven_packages=5001, maven_versions=5001
Rendered SQL:
SELECT maven_versions.namespace_id AS "maven_versions.namespace_id",
maven_versions.id AS "maven_versions.id",
maven_versions.maven_package_id AS "maven_versions.maven_package_id",
maven_versions.last_downloaded_at AS "maven_versions.last_downloaded_at",
maven_versions.soft_deleted_at AS "maven_versions.soft_deleted_at",
maven_versions.created_at AS "maven_versions.created_at",
maven_versions.version AS "maven_versions.version",
maven_versions.gitlab_user_id AS "maven_versions.gitlab_user_id",
maven_versions.gitlab_project_id AS "maven_versions.gitlab_project_id",
maven_versions.gitlab_git_commit_sha AS "maven_versions.gitlab_git_commit_sha"
FROM public.maven_versions
WHERE (((maven_versions.namespace_id = $1::uuid) AND (maven_versions.maven_package_id = $2::uuid)) AND (maven_versions.soft_deleted_at IS NULL)) AND ((maven_versions.created_at, maven_versions.id) > ($3::timestamp with time zone, $4::uuid))
ORDER BY maven_versions.created_at ASC, maven_versions.id ASC
LIMIT $5;Bound args: [f0cc91ce-0906-44fd-b5d3-fb3c52231252 019fab69-304e-7a58-81bb-bd3a7ab69418 2026-07-03 05:40:00 +0000 UTC 019fab69-330a-7dcd-b7d6-005c69138bf5 21]
Plan (EXPLAIN (ANALYZE, BUFFERS) output):
Limit (cost=0.28..2.75 rows=21 width=177) (actual time=0.028..0.032 rows=21 loops=1)
Buffers: shared hit=3
-> Index Scan using maven_versions_p17_namespace_id_maven_package_id_created_at_idx on maven_versions_p17 maven_versions (cost=0.28..294.60 rows=2500 width=177) (actual time=0.028..0.030 rows=21 loops=1)
Index Cond: ((namespace_id = 'f0cc91ce-0906-44fd-b5d3-fb3c52231252'::uuid) AND (maven_package_id = '019fab69-304e-7a58-81bb-bd3a7ab69418'::uuid) AND (ROW(created_at, id) > ROW('2026-07-03 05:40:00+00'::timestamp with time zone, '019fab69-330a-7dcd-b7d6-005c69138bf5'::uuid)))
Buffers: shared hit=3
Planning:
Buffers: shared hit=1
Planning Time: 0.167 ms
Execution Time: 0.055 msTimings: planning 0.167ms, execution 0.055ms, total 0.222ms.
datastore.MavenVersionStore.ListMavenVersionsByPackage.VersionAscending
Summary: The version sort moves to unique_maven_versions_ns_id_pkg_id_version. A single-column bound is a complete keyset here because version is unique per package among active rows, and the plan confirms no id tiebreaker is needed to avoid a Sort. No anomalies.
Seed shape: namespaces=1, repositories=1, maven_repositories=1, maven_packages=5001, maven_versions=5001
Rendered SQL:
SELECT maven_versions.namespace_id AS "maven_versions.namespace_id",
maven_versions.id AS "maven_versions.id",
maven_versions.maven_package_id AS "maven_versions.maven_package_id",
maven_versions.last_downloaded_at AS "maven_versions.last_downloaded_at",
maven_versions.soft_deleted_at AS "maven_versions.soft_deleted_at",
maven_versions.created_at AS "maven_versions.created_at",
maven_versions.version AS "maven_versions.version",
maven_versions.gitlab_user_id AS "maven_versions.gitlab_user_id",
maven_versions.gitlab_project_id AS "maven_versions.gitlab_project_id",
maven_versions.gitlab_git_commit_sha AS "maven_versions.gitlab_git_commit_sha"
FROM public.maven_versions
WHERE (((maven_versions.namespace_id = $1::uuid) AND (maven_versions.maven_package_id = $2::uuid)) AND (maven_versions.soft_deleted_at IS NULL)) AND (maven_versions.version > $3::text)
ORDER BY maven_versions.version ASC
LIMIT $4;Bound args: [f0cc91ce-0906-44fd-b5d3-fb3c52231252 019fab69-304e-7a58-81bb-bd3a7ab69418 1.0.02500 21]
Plan (EXPLAIN (ANALYZE, BUFFERS) output):
Limit (cost=0.28..2.65 rows=21 width=177) (actual time=0.019..0.023 rows=21 loops=1)
Buffers: shared hit=3
-> Index Scan using maven_versions_p17_namespace_id_maven_package_id_version_idx on maven_versions_p17 maven_versions (cost=0.28..282.60 rows=2500 width=177) (actual time=0.018..0.021 rows=21 loops=1)
Index Cond: ((namespace_id = 'f0cc91ce-0906-44fd-b5d3-fb3c52231252'::uuid) AND (maven_package_id = '019fab69-304e-7a58-81bb-bd3a7ab69418'::uuid) AND (version > '1.0.02500'::text))
Buffers: shared hit=3
Planning:
Buffers: shared hit=1
Planning Time: 0.138 ms
Execution Time: 0.038 msTimings: planning 0.138ms, execution 0.038ms, total 0.176ms.
datastore.MavenVersionStore.ListMavenVersionsByPackage.VersionDescending
Summary: The same version index read backward. No Sort, one partition, bound folded. No anomalies.
Seed shape: namespaces=1, repositories=1, maven_repositories=1, maven_packages=5001, maven_versions=5001
Rendered SQL:
SELECT maven_versions.namespace_id AS "maven_versions.namespace_id",
maven_versions.id AS "maven_versions.id",
maven_versions.maven_package_id AS "maven_versions.maven_package_id",
maven_versions.last_downloaded_at AS "maven_versions.last_downloaded_at",
maven_versions.soft_deleted_at AS "maven_versions.soft_deleted_at",
maven_versions.created_at AS "maven_versions.created_at",
maven_versions.version AS "maven_versions.version",
maven_versions.gitlab_user_id AS "maven_versions.gitlab_user_id",
maven_versions.gitlab_project_id AS "maven_versions.gitlab_project_id",
maven_versions.gitlab_git_commit_sha AS "maven_versions.gitlab_git_commit_sha"
FROM public.maven_versions
WHERE (((maven_versions.namespace_id = $1::uuid) AND (maven_versions.maven_package_id = $2::uuid)) AND (maven_versions.soft_deleted_at IS NULL)) AND (maven_versions.version < $3::text)
ORDER BY maven_versions.version DESC
LIMIT $4;Bound args: [f0cc91ce-0906-44fd-b5d3-fb3c52231252 019fab69-304e-7a58-81bb-bd3a7ab69418 1.0.02500 21]
Plan (EXPLAIN (ANALYZE, BUFFERS) output):
Limit (cost=0.28..2.65 rows=21 width=177) (actual time=0.017..0.021 rows=21 loops=1)
Buffers: shared hit=3
-> Index Scan Backward using maven_versions_p17_namespace_id_maven_package_id_version_idx on maven_versions_p17 maven_versions (cost=0.28..282.60 rows=2500 width=177) (actual time=0.017..0.019 rows=21 loops=1)
Index Cond: ((namespace_id = 'f0cc91ce-0906-44fd-b5d3-fb3c52231252'::uuid) AND (maven_package_id = '019fab69-304e-7a58-81bb-bd3a7ab69418'::uuid) AND (version < '1.0.02500'::text))
Buffers: shared hit=3
Planning:
Buffers: shared hit=1
Planning Time: 0.112 ms
Execution Time: 0.033 msTimings: planning 0.112ms, execution 0.033ms, total 0.145ms.
datastore.MavenVersionStore.ListMavenVersionsByPackage.CreatedAtDescending.CursorNil
Summary: First page of the default sort, the cursor-nil branch. The index prefix (namespace_id, maven_package_id) carries the predicate and the index order carries the sort. No anomalies.
Seed shape: namespaces=1, repositories=1, maven_repositories=1, maven_packages=5001, maven_versions=5001
Rendered SQL:
SELECT maven_versions.namespace_id AS "maven_versions.namespace_id",
maven_versions.id AS "maven_versions.id",
maven_versions.maven_package_id AS "maven_versions.maven_package_id",
maven_versions.last_downloaded_at AS "maven_versions.last_downloaded_at",
maven_versions.soft_deleted_at AS "maven_versions.soft_deleted_at",
maven_versions.created_at AS "maven_versions.created_at",
maven_versions.version AS "maven_versions.version",
maven_versions.gitlab_user_id AS "maven_versions.gitlab_user_id",
maven_versions.gitlab_project_id AS "maven_versions.gitlab_project_id",
maven_versions.gitlab_git_commit_sha AS "maven_versions.gitlab_git_commit_sha"
FROM public.maven_versions
WHERE ((maven_versions.namespace_id = $1::uuid) AND (maven_versions.maven_package_id = $2::uuid)) AND (maven_versions.soft_deleted_at IS NULL)
ORDER BY maven_versions.created_at DESC, maven_versions.id DESC
LIMIT $3;Bound args: [f0cc91ce-0906-44fd-b5d3-fb3c52231252 019fab69-304e-7a58-81bb-bd3a7ab69418 21]
Plan (EXPLAIN (ANALYZE, BUFFERS) output):
Limit (cost=0.28..2.18 rows=21 width=177) (actual time=0.025..0.029 rows=21 loops=1)
Buffers: shared hit=3
-> Index Scan Backward using maven_versions_p17_namespace_id_maven_package_id_created_at_idx on maven_versions_p17 maven_versions (cost=0.28..452.93 rows=5001 width=177) (actual time=0.024..0.026 rows=21 loops=1)
Index Cond: ((namespace_id = 'f0cc91ce-0906-44fd-b5d3-fb3c52231252'::uuid) AND (maven_package_id = '019fab69-304e-7a58-81bb-bd3a7ab69418'::uuid))
Buffers: shared hit=3
Planning:
Buffers: shared hit=1
Planning Time: 0.142 ms
Execution Time: 0.049 msTimings: planning 0.142ms, execution 0.049ms, total 0.191ms.
datastore.MavenPackageStore.FindMavenPackageInRepository
Summary: Single-row primary-key seek: the Index Cond binds both pk_maven_packages columns, and maven_repository_id and soft_deleted_at are filters on that one row rather than index columns, so the cost does not grow with the repository's package count. No anomalies.
Seed shape: namespaces=1, repositories=1, maven_repositories=1, maven_packages=5001, maven_versions=5001
Rendered SQL:
SELECT maven_packages.namespace_id AS "maven_packages.namespace_id",
maven_packages.id AS "maven_packages.id",
maven_packages.maven_repository_id AS "maven_packages.maven_repository_id",
maven_packages.last_downloaded_at AS "maven_packages.last_downloaded_at",
maven_packages.soft_deleted_at AS "maven_packages.soft_deleted_at",
maven_packages.group_id AS "maven_packages.group_id",
maven_packages.artifact_id AS "maven_packages.artifact_id"
FROM public.maven_packages
WHERE (((maven_packages.namespace_id = $1::uuid) AND (maven_packages.id = $2::uuid)) AND (maven_packages.maven_repository_id = $3::uuid)) AND (maven_packages.soft_deleted_at IS NULL)
LIMIT $4;Bound args: [f0cc91ce-0906-44fd-b5d3-fb3c52231252 019fab69-304e-7a58-81bb-bd3a7ab69418 019fab69-3044-7b6f-9cae-af494f9e5a65 1]
Plan (EXPLAIN (ANALYZE, BUFFERS) output):
Limit (cost=0.28..8.30 rows=1 width=89) (actual time=0.014..0.014 rows=1 loops=1)
Buffers: shared hit=3
-> Index Scan using maven_packages_p17_pkey on maven_packages_p17 maven_packages (cost=0.28..8.30 rows=1 width=89) (actual time=0.013..0.013 rows=1 loops=1)
Index Cond: ((id = '019fab69-304e-7a58-81bb-bd3a7ab69418'::uuid) AND (namespace_id = 'f0cc91ce-0906-44fd-b5d3-fb3c52231252'::uuid))
Filter: ((soft_deleted_at IS NULL) AND (maven_repository_id = '019fab69-3044-7b6f-9cae-af494f9e5a65'::uuid))
Buffers: shared hit=3
Planning Time: 0.071 ms
Execution Time: 0.026 msTimings: planning 0.071ms, execution 0.026ms, total 0.097ms.
datastore.MavenVersionStore.FindMavenVersionByID.VersionLeg
Summary: The version leg is a point probe. The planner picks unique_maven_versions_id_pkg_id_ns_id over the primary key. Both lead with (id, namespace_id), so the probe is one row either way. The plan does not reference maven_packages. No anomalies.
Seed shape: namespaces=1, repositories=1, maven_repositories=1, maven_packages=5001, maven_versions=5001
Rendered SQL:
SELECT maven_versions.namespace_id AS "maven_versions.namespace_id",
maven_versions.id AS "maven_versions.id",
maven_versions.maven_package_id AS "maven_versions.maven_package_id",
maven_versions.last_downloaded_at AS "maven_versions.last_downloaded_at",
maven_versions.soft_deleted_at AS "maven_versions.soft_deleted_at",
maven_versions.created_at AS "maven_versions.created_at",
maven_versions.version AS "maven_versions.version",
maven_versions.gitlab_user_id AS "maven_versions.gitlab_user_id",
maven_versions.gitlab_project_id AS "maven_versions.gitlab_project_id",
maven_versions.gitlab_git_commit_sha AS "maven_versions.gitlab_git_commit_sha"
FROM public.maven_versions
WHERE ((maven_versions.namespace_id = $1::uuid) AND (maven_versions.id = $2::uuid)) AND (maven_versions.soft_deleted_at IS NULL)
LIMIT $3;Bound args: [f0cc91ce-0906-44fd-b5d3-fb3c52231252 019fab69-3052-7a10-b6d6-c8c1ccb05feb 1]
Plan (EXPLAIN (ANALYZE, BUFFERS) output):
Limit (cost=0.28..8.30 rows=1 width=177) (actual time=0.012..0.013 rows=1 loops=1)
Buffers: shared hit=3
-> Index Scan using maven_versions_p17_id_maven_package_id_namespace_id_idx on maven_versions_p17 maven_versions (cost=0.28..8.30 rows=1 width=177) (actual time=0.012..0.012 rows=1 loops=1)
Index Cond: ((id = '019fab69-3052-7a10-b6d6-c8c1ccb05feb'::uuid) AND (namespace_id = 'f0cc91ce-0906-44fd-b5d3-fb3c52231252'::uuid))
Filter: (soft_deleted_at IS NULL)
Buffers: shared hit=3
Planning:
Buffers: shared hit=1
Planning Time: 0.086 ms
Execution Time: 0.023 msTimings: planning 0.086ms, execution 0.023ms, total 0.109ms.
datastore.MavenVersionStore.FindMavenVersionByID.ParentPackageLeg
Summary: The parent leg reuses addressableMavenPackage and seeks the same primary key as the package detail read, projecting only id. One row, one partition. No anomalies.
Seed shape: namespaces=1, repositories=1, maven_repositories=1, maven_packages=5001, maven_versions=5001
Rendered SQL:
SELECT maven_packages.id AS "maven_packages.id"
FROM public.maven_packages
WHERE (((maven_packages.namespace_id = $1::uuid) AND (maven_packages.id = $2::uuid)) AND (maven_packages.maven_repository_id = $3::uuid)) AND (maven_packages.soft_deleted_at IS NULL)
LIMIT $4;Bound args: [f0cc91ce-0906-44fd-b5d3-fb3c52231252 019fab69-304e-7a58-81bb-bd3a7ab69418 019fab69-3044-7b6f-9cae-af494f9e5a65 1]
Plan (EXPLAIN (ANALYZE, BUFFERS) output):
Limit (cost=0.28..8.30 rows=1 width=16) (actual time=0.021..0.021 rows=1 loops=1)
Buffers: shared hit=3
-> Index Scan using maven_packages_p17_pkey on maven_packages_p17 maven_packages (cost=0.28..8.30 rows=1 width=16) (actual time=0.021..0.021 rows=1 loops=1)
Index Cond: ((id = '019fab69-304e-7a58-81bb-bd3a7ab69418'::uuid) AND (namespace_id = 'f0cc91ce-0906-44fd-b5d3-fb3c52231252'::uuid))
Filter: ((soft_deleted_at IS NULL) AND (maven_repository_id = '019fab69-3044-7b6f-9cae-af494f9e5a65'::uuid))
Buffers: shared hit=3
Planning Time: 0.062 ms
Execution Time: 0.032 msTimings: planning 0.062ms, execution 0.032ms, total 0.094ms.
Chain-form verification
FindMavenVersionByID verifies the parent chain in two statements rather than
one join. This run measured that choice against both single-statement
alternatives on a fixture matching production shape: 10 Maven repositories in
one namespace, 500 packages each, all ids UUIDv7, and the addressed package the
newest of the 5000.
| Statistics | Form | Buffers (hit) | Execution |
|---|---|---|---|
| Fresh | Two statements (version leg + parent leg) | 1 + 3 = 4 | 0.025ms |
| Fresh | Single statement, INNER JOIN |
4 | 0.025ms |
| Fresh | Single statement, correlated EXISTS |
4 | 0.062ms |
Stale on maven_packages |
Two statements (version leg + parent leg) | 1 + 9 = 10 | 0.089ms |
Stale on maven_packages |
Single statement, INNER JOIN |
5044 | 1.221ms |
Stale on maven_packages |
Single statement, correlated EXISTS |
5044 | 1.190ms |
With fresh statistics the three forms are equivalent, and the single statement
plans exactly as the doc comment predicts: a nested loop whose inner side is
parameterized by the outer row's maven_package_id and probes
pk_maven_packages for one row.
Stale statistics separate them. pk_maven_packages is (id, namespace_id) with
id leading, so once the planner stops choosing the parameterized probe,
maven_repository_id cannot bound the scan and the inner side walks every
package in the namespace (5000 rows, 4500 dropped by the repository filter and
499 by the join filter). The two-statement parent leg keeps a bounded range on
(namespace_id, maven_repository_id), which is one repository's 500 packages.
That is 504 times the buffers and 13.7 times the execution time for the same
one-row answer.
The ratio scales with repositories per namespace, because that is the factor separating the two scan scopes. A fixture with a single repository per namespace collapses them and measures only 1.3 times the buffers, which is why the numbers above come from a 10-repository namespace.
Stale-statistics plans, single statement and two-statement parent leg
Single statement, INNER JOIN:
Limit (cost=0.25..9.30 rows=1 width=174) (actual time=1.187..1.187 rows=1 loops=1)
Buffers: shared hit=5044
-> Nested Loop (cost=0.25..9.30 rows=1 width=174) (actual time=1.186..1.187 rows=1 loops=1)
Join Filter: (p.id = v.maven_package_id)
Rows Removed by Join Filter: 499
Buffers: shared hit=5044
-> Seq Scan on maven_versions_p60 v (cost=0.00..1.01 rows=1 width=174) (actual time=0.005..0.005 rows=1 loops=1)
Filter: ((soft_deleted_at IS NULL) AND (namespace_id = '1cf550c3-b6df-42f3-8003-3b78dc251b37'::uuid) AND (id = '019fab6d-07b6-7c3a-ab88-353ab423a92a'::uuid))
Buffers: shared hit=1
-> Index Scan using maven_packages_p60_pkey on maven_packages_p60 p (cost=0.25..8.27 rows=1 width=32) (actual time=0.019..1.155 rows=500 loops=1)
Index Cond: (namespace_id = '1cf550c3-b6df-42f3-8003-3b78dc251b37'::uuid)
Filter: ((soft_deleted_at IS NULL) AND (maven_repository_id = '019fab6d-06a5-7f15-8aef-6566963a7013'::uuid))
Rows Removed by Filter: 4500
Buffers: shared hit=5043
Planning:
Buffers: shared hit=1
Planning Time: 0.244 ms
Execution Time: 1.221 msTwo-statement parent leg:
Limit (cost=0.25..8.27 rows=1 width=16) (actual time=0.059..0.059 rows=1 loops=1)
Buffers: shared hit=9
-> Index Scan using maven_packages_p60_namespace_id_maven_repository_id_last_do_idx on maven_packages_p60 maven_packages (cost=0.25..8.27 rows=1 width=16) (actual time=0.058..0.058 rows=1 loops=1)
Index Cond: ((namespace_id = '1cf550c3-b6df-42f3-8003-3b78dc251b37'::uuid) AND (maven_repository_id = '019fab6d-06a5-7f15-8aef-6566963a7013'::uuid))
Filter: (id = '01977420-ef87-754f-8f48-8898c984a653'::uuid)
Rows Removed by Filter: 499
Buffers: shared hit=9
Planning Time: 0.068 ms
Execution Time: 0.074 msTwo-statement version leg:
Limit (cost=0.00..1.01 rows=1 width=174) (actual time=0.006..0.006 rows=1 loops=1)
Buffers: shared hit=1
-> Seq Scan on maven_versions_p60 maven_versions (cost=0.00..1.01 rows=1 width=174) (actual time=0.005..0.006 rows=1 loops=1)
Filter: ((soft_deleted_at IS NULL) AND (namespace_id = '1cf550c3-b6df-42f3-8003-3b78dc251b37'::uuid) AND (id = '019fab6d-07b6-7c3a-ab88-353ab423a92a'::uuid))
Buffers: shared hit=1
Planning:
Buffers: shared hit=1
Planning Time: 0.119 ms
Execution Time: 0.015 ms