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

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 ms

Timings: 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 ms

Timings: 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 ms

Timings: 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 ms

Timings: 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 ms

Timings: 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 ms

Timings: 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 ms

Timings: 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 ms

Timings: 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 ms

Timings: 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 ms

Timings: 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 ms

Timings: 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 ms

Two-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 ms

Two-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
Edited by Hayley Swimelar

Merge request reports

Loading
Loading