feat(managementapi): serve the npm remote package and version reads (S17 Phase 6 plan: 17/42)
Why
The npm package and version read routes answer 404 on a kind=remote
repository. Every remote arm has sat on that pending 404 since the
artifact-read kind-dispatch scaffold landed in chore(managementapi): artifact-read kind-dispat... (!1582 - merged) • Hayley Swimelar • 19.4, so a remote
repository's cached npm packages and versions were unreachable through the
management API even though the rows existed.
This fills those arms from npm_remote_packages and npm_remote_versions,
implementing Step 17 of
docs/plans/2026-08-13-s17-phase6-virtual-remote-repositories.md against the
npm package and version slices of acceptance criteria #95, #97, #98,
#99, and #100 in docs/specs/S17-rest-management-api.md. The npm file
routes stay on the pending 404, which step 18 closes.
What
Each caller peels its own remote arm off ahead of the shared kind gate.
Both gates this step fills, resolvePackageRepositoryID and
hostedVersionFileRepository, are shared with route families it must not
serve, so the fill lands at each caller rather than inside the switch. The
Maven package reads, the npm dist-tag routes, both package deletes, both file
routes, and both file deletes keep answering exactly what they answered before.
Eight gate-holder test functions (52 subtests) pin that.
The kind gate stays after the query parse, so an invalid sort on a remote
repository still answers 400 rather than 404
(TestVersionFileRoutes_InvalidQueryBeats404OnNonHostedRepository).
The keyset list is index-backed in both directions with no post-scan sort,
pinned by EXPLAIN against 5000 analyzed rows. The deep-page tests also assert
the absence of a post-index Filter, so rewriting the row-value keyset bound
into its expanded OR form cannot silently turn a deep page from O(limit) into
O(offset).
The new store methods' error strings name the operation and the table but no
row ids. docs/dev/database-query-patterns.md requires that of a newly added
store query method, with the dropped identifiers logged as structured fields at
the call site instead. Five methods and six call sites were converted.
namespace_id is deliberately absent from those fields, because the 500 writers
already supply it.
Four operations move from 404 to 200 on a remote repository: the npm
package list and detail, and the npm version list and detail. That is the
intended behavior change rather than a regression, and no client can observe
it. docs/dev/api-style.md line 134 calls any status-code change breaking, but
create.go line 214 rejects a non-hosted kind with 422 and
RepositoryStore.Update names kind among the columns it never assigns, so no
reachable repository holds that kind today.
Departures from the plan
api/openapi/v1.yaml widens NpmPackage.versions_count and tags_count to
[integer, 'null']. The plan assigns v1.yaml to step 18, but the two cache
tables store no counters, so both fields serialize as null and a remote npm
package response matches zero Package oneOf branches. The oneOf
discriminates on required-key presence, so it is unaffected. No Bruno change is
owed, because no operation was added, renamed, or removed.
Three sibling files outside the plan's list each take a //nolint:dupl.
dupl reports both sides of a clone and this branch's code creates it, so
without the directives the default-build lint fails on already-merged code.
Every dupl token this MR adds was re-measured by drop-and-rerun after the
error strings lost their identifiers, one token at a time so a clone partner's
own directive could not mask the finding. Thirteen fire, each naming the file
its partner sits in. The fourteenth had gone stale: the identifier drop took the
FindNpmVersionByID pair under the threshold, so that exemption is deleted and
npm_versions.go leaves the change set. The funlen token on
requireArtifactDeps was measured the same way, at 79 lines against the 70
limit. The rebase onto main also retired the dupl token main carries on
requireArtifactDeps and requireArtifactWriteDeps, because the npm remote
seams grow the first past dupl's similarity threshold, so the pair no longer
clones.
Four now-dead exemptions came out of packages.go and versions.go earlier in
the branch.
Both npm version sorts ship, not only created_at. The plan's step 17 says
"same shapes as Step 15" and step 15 names created_at alone, but AC #95
carries Phase 3's sorts over unchanged and the Phase 3 npm version list accepts
created_at and version. Both are index-backed on npm_remote_versions,
and both get an EXPLAIN pin.
The run recipe was stale. The management API rejects kind != hosted, so
nothing could reach these routes. driver.sh seed-remote, the cache rows the
management reads serve, and the recipe update ride this MR, with driver.sh smoke passing 30 of 30 from a cold rebuild.
Spec coverage
| Acceptance criterion | Tests |
|---|---|
#95 cached npm packages and versions serve with Phase 3's sorts, pagination, and error rules, and an uncached coordinate is absent |
TestNpmRemotePackageList_ServesTheCachedPackages, _DescendingOrderAndKeysetWalk, _EmptyCacheIsAnEmptyArray, _ClampsAnOversizedLimit, TestNpmRemotePackageDetail_ServesTheCachedRow, TestNpmRemoteVersionList_ServesTheCachedVersions, _BothSortsAndOrders, _KeysetWalkFollowsTheLinkHeader, _ParentResolveSeparatesAbsentFromEmpty, TestNpmRemoteVersionDetail_ServesTheCachedRow, and the store-side TestNpmRemotePackageStore_ListNpmRemotePackages* and TestNpmRemoteVersionStore_ListNpmRemoteVersionsByPackage* families |
#97 version publish attribution and the npm package counters serialize null |
TestNpmRemotePackageFromModel_NullsTheCounters, _UnscopedPackageNullsTheScope, TestVersionFromNpmRemoteModel_NullsTheAttribution, _ZeroSizeIsPresent, TestNpmPackageFromModel_KeepsTheHostedCountersPresent, and the two new rows in TestPackageReadHandlers_ResponsesMatchOpenAPIContract and TestVersionFileHandlers_ResponsesMatchOpenAPIContract |
#98 dist-tag clause: npm dist-tag routes return 404 on a remote repository |
Held unchanged by the two dist-tag remote rows of TestPackageSubtreeRoutes_NonHostedRepositoryIs404. The remote cache stores dist-tags inside one opaque rewritten document, so this 404 is permanent |
#99 every artifact route on a virtual repository returns 404 |
Held unchanged. Every virtual row of TestPackageSubtreeRoutes_NonHostedRepositoryIs404 and TestVersionFileRoutes_NonHostedRepositoryIs404 survives |
#100 an evicted-but-unreaped row is absent from every list and detail read |
TestNpmRemotePackageStore_SoftDeletedPackageIsInvisibleToReads, TestNpmRemoteVersionStore_SoftDeletedVersionIsInvisibleToReads, _MarkedPackageHidesItsLiveVersions, TestNpmRemotePackageList_EvictedRowsAreAbsent, TestNpmRemoteVersionList_EvictedVersionsAreAbsent, and the evicted rows of TestNpmRemotePackageDetail_IdOutsideTheChainIs404 and TestNpmRemoteVersionDetail_IdOutsideTheChainIs404 |
Invalid sort, order, limit, or an undecodable cursor returns 400, while a limit past the maximum clamps |
TestNpmRemotePackageList_RejectsInvalidQueryBeforeAnyRead, TestNpmRemoteVersionList_RejectsInvalidQueryBeforeAnyRead, both _ClampsAnOversizedLimit cases, the store-side *_ArgumentGuards pair, and TestNpmRemoteVersionCursor_HasKeysFor |
| Tenant isolation: every store call carries the namespace and the remote binding | TestNpmRemotePackageList_ScopesTheQueryByTheRemoteBinding, TestNpmRemoteVersionList_ScopesTheChainByTheRemoteBinding, and the store-side *_ChainScoping pair |
An unexpected store failure returns 500 and logs the scoping ids as structured fields |
TestNpmRemotePackageRoutes_StoreFailureLogsTheScopingIDs and TestNpmRemoteVersionRoutes_StoreFailureLogsTheScopingIDs, six cases across the packages list, the binding resolve, the package detail, the parent package read, the versions list, and the version detail. Store-side: the four *_QueryFailure cases and TestNpmRemoteRepositoryStore_FindNpmRemoteRepositoryID |
Diff size
7317 reviewable LOC, past the 500 that docs/dev/development-model.md asks be
split or justified. By group: test Go 5588 (76%), source Go 1407 (19%), run
recipe 226, dev docs 65, contract 31.
Two cuts, measured:
- Store from handler, 3876 LOC against 3165. The store half can land alone,
as the container remote read stores did in
chore(datastore): add container remote image an... (!1629 - merged) • Hayley Swimelar • 19.4. The handler half cannot: this MR
widens
NpmPackage.versions_countandtags_countto nullable, and without that widening a cached package's body fails its own contract test. - Packages from versions, 3014 LOC packages, 3770 versions, 1386 shared,
measured before the comment-caps pass shortened every group.
The dependency runs one way, since the remote version reads resolve their
parent through
FindNpmRemotePackageInRepository, so packages could land first with versions stacked on it. The versions half needs no contract change, because theVersionattribution fields are already nullable onmain. Step 18 depends on both finders.
The shape follows Step 15, which the plan names as this step's template twice
and which ships the same two entity types undivided at 6940 LOC in
feat(managementapi): serve the maven remote pac... (!1749 - merged) • Hayley Swimelar • 19.4. The 75% test mass mirrors the merged
container read-store suite subtest for subtest, across two entity types and two
sorts, plus EXPLAIN pins on both sorts in both directions.
e2e scenarios
No scenario is added or affected. docs/testing/e2e/npm.md lists virtual and
remote npm repositories under "Out of scope until the capability ships", and
the management API still rejects a non-hosted kind on create, so no
protocol-client journey can reach a remote repository.
Test plan
go test -count=1 ./internal/managementapi/... ./internal/datastore/... ./cmd/...
ARTIFACT_REGISTRY_DATABASE_TEST_DSN=<dsn> go test -tags=integration -count=1 \
./internal/managementapi/... ./internal/datastore/...
golangci-lint run --build-tags=integration --max-same-issues=0 \
--max-issues-per-linter=0 ./internal/datastore/... ./internal/managementapi/...
.claude/skills/run-artifact-registry/driver.sh smokeVerified on the local rig against a cold-booted binary with a remote npm
repository seeded through the new seed-remote: an empty cache lists 200 [],
a cached package lists 200 with null counters, a cached version detail serves
200 with null attribution, sort=bogus answers 400, the dist-tag and file
routes hold their 404, and setting soft_deleted_at on either cache table
drops the row from its list and turns its detail into a 404.
Both deep-page Filter assertions, the enumerated-kind totality enrollment, and
the package list's parse-before-resolve assertion are mutation-verified.
Context for LLM agents
Rationale
- Hold the contract widening for step 18, as the plan assigns it. Rejected
because a remote npm package response then matches no
PackageoneOfbranch, so the route's own contract test fails onmainfor a whole step. - Fill the remote arm inside the shared kind switch. Rejected because both gates are shared with the Maven reads, the dist-tag routes, the deletes, and the file routes, none of which this step serves, so a switch-level fill changes six route families at once.
- Keyset the version list on
created_atalone, as step 15's template does. Rejected because Phase 3's npm version list acceptsversiontoo, andunique_npm_remote_versions_ns_id_pkg_id_versionalready backs it, so dropping the sort would narrow the remote surface againstAC #95.
Non-goals
- npm file reads and the
cacheobject. Step 18 owns them, and the file routes keep their pending 404 here. - Eviction and the delete arms. Steps 26 and 29 own them, which is why the remote deletes keep the gate's 404.
- Backfilling the cache.
internal/format/npm/npmremotewrites these tables on the outbound fetch path, and this MR reads them.
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 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 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.FindNpmRemotePackageInRepository |
Limit → Index Scan | pk_npm_remote_packages |
1 / 1 | 8.30 | 0.010ms | 3 / 0 | 1/64 |
datastore.FindNpmRemoteRepositoryID |
Limit → Index Scan | unique_npm_remote_repositories_namespace_id_and_repository_id |
1 / 1 | 8.30 | 0.009ms | 3 / 0 | 1/64 |
datastore.FindNpmRemoteVersionByID.ParentProbe |
Limit → Index Scan | pk_npm_remote_packages |
1 / 1 | 8.30 | 0.010ms | 3 / 0 | 1/64 |
datastore.FindNpmRemoteVersionByID.VersionLeg |
Limit → Index Scan | pk_npm_remote_versions |
1 / 1 | 8.30 | 0.010ms | 3 / 0 | 1/64 |
datastore.ListNpmRemotePackages.AscCursorNil |
Limit → Index Scan | unique_npm_remote_packages_ns_id_repo_id_name |
101 / 101 | 9.41 | 0.021ms | 4 / 0 | 1/64 |
datastore.ListNpmRemotePackages.AscCursorSet |
Limit → Index Scan | unique_npm_remote_packages_ns_id_repo_id_name |
101 / 101 | 15.83 | 0.026ms | 5 / 0 | 1/64 |
datastore.ListNpmRemotePackages.DescCursorNil |
Limit → Index Scan Backward | unique_npm_remote_packages_ns_id_repo_id_name |
101 / 101 | 10.28 | 0.026ms | 6 / 0 | 1/64 |
datastore.ListNpmRemotePackages.DescCursorSet |
Limit → Index Scan Backward | unique_npm_remote_packages_ns_id_repo_id_name |
101 / 101 | 11.22 | 0.026ms | 6 / 0 | 1/64 |
datastore.ListNpmRemoteVersionsByPackage.CreatedAtAscCursorNil |
Limit → Index Scan | index_npm_remote_versions_on_ns_id_pkg_id_created_at_id |
101 / 101 | 11.39 | 0.026ms | 5 / 0 | 1/64 |
datastore.ListNpmRemoteVersionsByPackage.CreatedAtAscCursorSet |
Limit → Index Scan | index_npm_remote_versions_on_ns_id_pkg_id_created_at_id |
101 / 101 | 18.78 | 0.028ms | 5 / 0 | 1/64 |
datastore.ListNpmRemoteVersionsByPackage.CreatedAtDescCursorNil |
Limit → Index Scan Backward | index_npm_remote_versions_on_ns_id_pkg_id_created_at_id |
101 / 101 | 11.39 | 0.027ms | 6 / 0 | 1/64 |
datastore.ListNpmRemoteVersionsByPackage.CreatedAtDescCursorSet |
Limit → Index Scan Backward | index_npm_remote_versions_on_ns_id_pkg_id_created_at_id |
101 / 101 | 12.62 | 0.028ms | 6 / 0 | 1/64 |
datastore.ListNpmRemoteVersionsByPackage.VersionAscCursorNil |
Limit → Index Scan | unique_npm_remote_versions_ns_id_pkg_id_version |
101 / 101 | 10.92 | 0.027ms | 5 / 0 | 1/64 |
datastore.ListNpmRemoteVersionsByPackage.VersionAscCursorSet |
Limit → Index Scan | unique_npm_remote_versions_ns_id_pkg_id_version |
101 / 101 | 17.49 | 0.028ms | 5 / 0 | 1/64 |
datastore.ListNpmRemoteVersionsByPackage.VersionDescCursorNil |
Limit → Index Scan Backward | unique_npm_remote_versions_ns_id_pkg_id_version |
101 / 101 | 10.92 | 0.028ms | 6 / 0 | 1/64 |
datastore.ListNpmRemoteVersionsByPackage.VersionDescCursorSet |
Limit → Index Scan Backward | unique_npm_remote_versions_ns_id_pkg_id_version |
101 / 101 | 22.13 | 0.053ms | 6 / 0 | 1/64 |
datastore.FindNpmRemotePackageInRepository
Summary: Plan matches the method's intent: a single-row (id, namespace_id) primary-key seek on pk_npm_remote_packages, pruned to 1 of 64 partitions. The remaining two terms (npm_remote_repository_id, soft_deleted_at IS NULL) apply as a Filter over the one row the PK returns, which costs nothing and is the correct shape for an id-addressed detail read. No anomalies.
Seed shape: npm_remote_packages=7500 (5000 live target repo, 2000 live sibling repo, 500 marked target), npm_remote_repositories=2, repositories=2, namespaces=1
Rendered SQL:
SELECT npm_remote_packages.namespace_id AS "npm_remote_packages.namespace_id",
npm_remote_packages.id AS "npm_remote_packages.id",
npm_remote_packages.npm_remote_repository_id AS "npm_remote_packages.npm_remote_repository_id",
npm_remote_packages.last_downloaded_at AS "npm_remote_packages.last_downloaded_at",
npm_remote_packages.soft_deleted_at AS "npm_remote_packages.soft_deleted_at",
npm_remote_packages.name AS "npm_remote_packages.name",
npm_remote_packages.scope AS "npm_remote_packages.scope"
FROM public.npm_remote_packages
WHERE (((npm_remote_packages.namespace_id = $1::uuid) AND (npm_remote_packages.npm_remote_repository_id = $2::uuid)) AND (npm_remote_packages.id = $3::uuid)) AND (npm_remote_packages.soft_deleted_at IS NULL)
LIMIT $4;Bound args: ['88539ef7-f7bf-4f49-ae9c-cb2f4933618c', 'b517a34f-c945-4ac9-9ea7-a74a8786e32f', '013217b2-a08b-4a30-8cbb-8479bf3bfb8f', 1]
Plan (EXPLAIN (ANALYZE, BUFFERS) output):
Limit (cost=0.28..8.30 rows=1 width=109) (actual time=0.009..0.010 rows=1 loops=1)
Buffers: shared hit=3
-> Index Scan using npm_remote_packages_p36_pkey on npm_remote_packages_p36 npm_remote_packages (cost=0.28..8.30 rows=1 width=109) (actual time=0.009..0.009 rows=1 loops=1)
Index Cond: ((id = '013217b2-a08b-4a30-8cbb-8479bf3bfb8f'::uuid) AND (namespace_id = '88539ef7-f7bf-4f49-ae9c-cb2f4933618c'::uuid))
Filter: ((soft_deleted_at IS NULL) AND (npm_remote_repository_id = 'b517a34f-c945-4ac9-9ea7-a74a8786e32f'::uuid))
Buffers: shared hit=3
Planning:
Buffers: shared hit=75
Planning Time: 0.401 ms
Execution Time: 0.021 msTimings: planning 0.401ms, execution 0.021ms, total 0.422ms.
datastore.FindNpmRemoteRepositoryID
Summary: Plan matches the doc comment: both key columns are bound as equalities, so the probe is satisfied entirely from unique_npm_remote_repositories_namespace_id_and_repository_id with repository_id as the index's second column rather than a heap filter, and namespace_id prunes to 1 of 64 partitions. Seeded at 5000 bindings in the namespace to rule out a small-table Seq Scan, and the planner still picks the index (cost 8.30, 3 buffer hits). No anomalies.
Seed shape: npm_remote_repositories=5000, repositories=5000, namespaces=1
Rendered SQL:
SELECT npm_remote_repositories.id AS "npm_remote_repositories.id"
FROM public.npm_remote_repositories
WHERE (npm_remote_repositories.namespace_id = $1::uuid) AND (npm_remote_repositories.repository_id = $2::uuid)
LIMIT $3;Bound args: ['c1eb1b6e-257a-45c1-aca3-2ac6b4f6c15f', '34b3b519-9386-4582-918f-ee5f44295454', 1]
Plan (EXPLAIN (ANALYZE, BUFFERS) output):
Limit (cost=0.28..8.30 rows=1 width=16) (actual time=0.009..0.009 rows=1 loops=1)
Buffers: shared hit=3
-> Index Scan using npm_remote_repositories_p38_namespace_id_repository_id_idx on npm_remote_repositories_p38 npm_remote_repositories (cost=0.28..8.30 rows=1 width=16) (actual time=0.008..0.008 rows=1 loops=1)
Index Cond: ((namespace_id = 'c1eb1b6e-257a-45c1-aca3-2ac6b4f6c15f'::uuid) AND (repository_id = '34b3b519-9386-4582-918f-ee5f44295454'::uuid))
Buffers: shared hit=3
Planning:
Buffers: shared hit=91
Planning Time: 0.422 ms
Execution Time: 0.020 msTimings: planning 0.422ms, execution 0.020ms, total 0.442ms.
datastore.FindNpmRemoteVersionByID.ParentProbe
Summary: The parent leg of FindNpmRemoteVersionByID plans identically to the package detail read: a pk_npm_remote_packages seek pruned to one partition, with the repository scope and the marker as a Filter over one row. 3 buffer hits, so the chain's second statement adds a single index descent to the request. No anomalies.
Seed shape: npm_remote_packages=7500 (5000 live target repo, 2000 live sibling repo, 500 marked target), npm_remote_repositories=2, repositories=2, namespaces=1
Rendered SQL:
SELECT npm_remote_packages.id AS "npm_remote_packages.id"
FROM public.npm_remote_packages
WHERE (((npm_remote_packages.namespace_id = $1::uuid) AND (npm_remote_packages.id = $2::uuid)) AND (npm_remote_packages.npm_remote_repository_id = $3::uuid)) AND (npm_remote_packages.soft_deleted_at IS NULL)
LIMIT $4;Bound args: ['8b796221-3024-48bb-bca8-d190aa18aa25', '38ff0ab3-f644-4595-b4bb-8377c0610856', 'b970afd0-3e77-451c-879b-85e56c5826a5', 1]
Plan (EXPLAIN (ANALYZE, BUFFERS) output):
Limit (cost=0.28..8.30 rows=1 width=16) (actual time=0.010..0.010 rows=1 loops=1)
Buffers: shared hit=3
-> Index Scan using npm_remote_packages_p44_pkey on npm_remote_packages_p44 npm_remote_packages (cost=0.28..8.30 rows=1 width=16) (actual time=0.009..0.009 rows=1 loops=1)
Index Cond: ((id = '38ff0ab3-f644-4595-b4bb-8377c0610856'::uuid) AND (namespace_id = '8b796221-3024-48bb-bca8-d190aa18aa25'::uuid))
Filter: ((soft_deleted_at IS NULL) AND (npm_remote_repository_id = 'b970afd0-3e77-451c-879b-85e56c5826a5'::uuid))
Buffers: shared hit=3
Planning:
Buffers: shared hit=65
Planning Time: 0.369 ms
Execution Time: 0.022 msTimings: planning 0.369ms, execution 0.022ms, total 0.391ms.
datastore.FindNpmRemoteVersionByID.VersionLeg
Summary: The version leg of the chain: a (id, namespace_id) primary-key seek on pk_npm_remote_versions, pruned to 1 of 64 partitions, with soft_deleted_at IS NULL as a Filter over the one row returned. Paired with the parent probe above, a version detail request costs two single-partition index descents whose cost is independent of how many versions the package holds, which is the two-statement shape the method's doc argues for. No anomalies.
Seed shape: npm_remote_versions=7500 (5000 live target package, 2000 live sibling package, 500 marked target), npm_remote_packages=2, npm_remote_repositories=1, repositories=1, namespaces=1
Rendered SQL:
SELECT npm_remote_versions.namespace_id AS "npm_remote_versions.namespace_id",
npm_remote_versions.id AS "npm_remote_versions.id",
npm_remote_versions.npm_remote_package_id AS "npm_remote_versions.npm_remote_package_id",
npm_remote_versions.last_downloaded_at AS "npm_remote_versions.last_downloaded_at",
npm_remote_versions.soft_deleted_at AS "npm_remote_versions.soft_deleted_at",
npm_remote_versions.created_at AS "npm_remote_versions.created_at",
npm_remote_versions.version AS "npm_remote_versions.version",
npm_remote_versions.size_bytes AS "npm_remote_versions.size_bytes"
FROM public.npm_remote_versions
WHERE ((npm_remote_versions.namespace_id = $1::uuid) AND (npm_remote_versions.id = $2::uuid)) AND (npm_remote_versions.soft_deleted_at IS NULL)
LIMIT $3;Bound args: ['2904e3b5-0ab9-4dab-812b-9f344b40015b', '782c586c-3d53-471d-8d79-7117c307bf6a', 1]
Plan (EXPLAIN (ANALYZE, BUFFERS) output):
Limit (cost=0.28..8.30 rows=1 width=93) (actual time=0.010..0.010 rows=1 loops=1)
Buffers: shared hit=3
-> Index Scan using npm_remote_versions_p26_pkey on npm_remote_versions_p26 npm_remote_versions (cost=0.28..8.30 rows=1 width=93) (actual time=0.009..0.009 rows=1 loops=1)
Index Cond: ((id = '782c586c-3d53-471d-8d79-7117c307bf6a'::uuid) AND (namespace_id = '2904e3b5-0ab9-4dab-812b-9f344b40015b'::uuid))
Filter: (soft_deleted_at IS NULL)
Buffers: shared hit=3
Planning:
Buffers: shared hit=103
Planning Time: 0.456 ms
Execution Time: 0.025 msTimings: planning 0.456ms, execution 0.025ms, total 0.481ms.
datastore.ListNpmRemotePackages.AscCursorNil
Summary: Plan matches the method's intent: a forward Index Scan on the partial unique unique_npm_remote_packages_ns_id_repo_id_name, with the namespace_id literal pruning to 1 of 64 partitions. The index leads with (namespace_id, npm_remote_repository_id) and carries WHERE soft_deleted_at IS NULL, so every predicate term is index-resident and ORDER BY name ASC is satisfied by the scan with no Sort node. Root estimate matches actual (101 / 101). No anomalies.
Seed shape: npm_remote_packages=7500 (5000 live target repo, 2000 live sibling repo, 500 marked target), npm_remote_repositories=2, repositories=2, namespaces=1
Rendered SQL:
SELECT npm_remote_packages.namespace_id AS "npm_remote_packages.namespace_id",
npm_remote_packages.id AS "npm_remote_packages.id",
npm_remote_packages.npm_remote_repository_id AS "npm_remote_packages.npm_remote_repository_id",
npm_remote_packages.last_downloaded_at AS "npm_remote_packages.last_downloaded_at",
npm_remote_packages.soft_deleted_at AS "npm_remote_packages.soft_deleted_at",
npm_remote_packages.name AS "npm_remote_packages.name",
npm_remote_packages.scope AS "npm_remote_packages.scope"
FROM public.npm_remote_packages
WHERE ((npm_remote_packages.namespace_id = $1::uuid) AND (npm_remote_packages.npm_remote_repository_id = $2::uuid)) AND (npm_remote_packages.soft_deleted_at IS NULL)
ORDER BY npm_remote_packages.name ASC
LIMIT $3;Bound args: ['3d9425a3-8231-4789-a387-6ba5258fd9fd', '6491fc07-6aef-4319-80cf-1b2a6cb68cc3', 101]
Plan (EXPLAIN (ANALYZE, BUFFERS) output):
Limit (cost=0.28..9.41 rows=101 width=109) (actual time=0.010..0.021 rows=101 loops=1)
Buffers: shared hit=4
-> Index Scan using npm_remote_packages_p50_namespace_id_npm_remote_repository__idx on npm_remote_packages_p50 npm_remote_packages (cost=0.28..464.32 rows=5133 width=109) (actual time=0.010..0.017 rows=101 loops=1)
Index Cond: ((namespace_id = '3d9425a3-8231-4789-a387-6ba5258fd9fd'::uuid) AND (npm_remote_repository_id = '6491fc07-6aef-4319-80cf-1b2a6cb68cc3'::uuid))
Buffers: shared hit=4
Planning:
Buffers: shared hit=275
Planning Time: 0.881 ms
Execution Time: 0.036 msTimings: planning 0.881ms, execution 0.036ms, total 0.917ms.
datastore.ListNpmRemotePackages.AscCursorSet
Summary: Same index and partition as the first page, reached by a forward scan. The cursor's name > $3 folds into the index range bound (it appears inside Index Cond, not as a Filter), which is what keeps a deep page O(limit) rather than O(offset): 5 buffer hits at the 2500th name, against 4 on the first page. No anomalies.
Seed shape: npm_remote_packages=7500 (5000 live target repo, 2000 live sibling repo, 500 marked target), npm_remote_repositories=2, repositories=2, namespaces=1
Rendered SQL:
SELECT npm_remote_packages.namespace_id AS "npm_remote_packages.namespace_id",
npm_remote_packages.id AS "npm_remote_packages.id",
npm_remote_packages.npm_remote_repository_id AS "npm_remote_packages.npm_remote_repository_id",
npm_remote_packages.last_downloaded_at AS "npm_remote_packages.last_downloaded_at",
npm_remote_packages.soft_deleted_at AS "npm_remote_packages.soft_deleted_at",
npm_remote_packages.name AS "npm_remote_packages.name",
npm_remote_packages.scope AS "npm_remote_packages.scope"
FROM public.npm_remote_packages
WHERE (((npm_remote_packages.namespace_id = $1::uuid) AND (npm_remote_packages.npm_remote_repository_id = $2::uuid)) AND (npm_remote_packages.soft_deleted_at IS NULL)) AND (npm_remote_packages.name > $3::text)
ORDER BY npm_remote_packages.name ASC
LIMIT $4;Bound args: ['92425581-a8fd-4adc-aefb-95bc18ebe3eb', '63e0d903-f107-4c09-b0c9-048a379c418d', 'explain-2500', 101]
Plan (EXPLAIN (ANALYZE, BUFFERS) output):
Limit (cost=0.28..15.83 rows=101 width=109) (actual time=0.012..0.026 rows=101 loops=1)
Buffers: shared hit=5
-> Index Scan using npm_remote_packages_p59_namespace_id_npm_remote_repository__idx on npm_remote_packages_p59 npm_remote_packages (cost=0.28..261.97 rows=1700 width=109) (actual time=0.011..0.021 rows=101 loops=1)
Index Cond: ((namespace_id = '92425581-a8fd-4adc-aefb-95bc18ebe3eb'::uuid) AND (npm_remote_repository_id = '63e0d903-f107-4c09-b0c9-048a379c418d'::uuid) AND (name > 'explain-2500'::text))
Buffers: shared hit=5
Planning:
Buffers: shared hit=77
Planning Time: 0.407 ms
Execution Time: 0.039 msTimings: planning 0.407ms, execution 0.039ms, total 0.446ms.
datastore.ListNpmRemotePackages.DescCursorNil
Summary: Descending order runs as an Index Scan Backward over the same partial unique, so the descending page needs no Sort either. Partition pruning and estimate accuracy are unchanged from the ascending first page (101 / 101, 1 of 64 partitions). No anomalies.
Seed shape: npm_remote_packages=7500 (5000 live target repo, 2000 live sibling repo, 500 marked target), npm_remote_repositories=2, repositories=2, namespaces=1
Rendered SQL:
SELECT npm_remote_packages.namespace_id AS "npm_remote_packages.namespace_id",
npm_remote_packages.id AS "npm_remote_packages.id",
npm_remote_packages.npm_remote_repository_id AS "npm_remote_packages.npm_remote_repository_id",
npm_remote_packages.last_downloaded_at AS "npm_remote_packages.last_downloaded_at",
npm_remote_packages.soft_deleted_at AS "npm_remote_packages.soft_deleted_at",
npm_remote_packages.name AS "npm_remote_packages.name",
npm_remote_packages.scope AS "npm_remote_packages.scope"
FROM public.npm_remote_packages
WHERE ((npm_remote_packages.namespace_id = $1::uuid) AND (npm_remote_packages.npm_remote_repository_id = $2::uuid)) AND (npm_remote_packages.soft_deleted_at IS NULL)
ORDER BY npm_remote_packages.name DESC
LIMIT $3;Bound args: ['5a866be6-3b3d-44ab-9c05-f96b95f58c02', '9e046fe4-5718-4cad-bbec-80ddb18b6862', 101]
Plan (EXPLAIN (ANALYZE, BUFFERS) output):
Limit (cost=0.28..10.28 rows=101 width=109) (actual time=0.012..0.026 rows=101 loops=1)
Buffers: shared hit=6
-> Index Scan Backward using npm_remote_packages_p13_namespace_id_npm_remote_repository__idx on npm_remote_packages_p13 npm_remote_packages (cost=0.28..508.32 rows=5133 width=109) (actual time=0.012..0.021 rows=101 loops=1)
Index Cond: ((namespace_id = '5a866be6-3b3d-44ab-9c05-f96b95f58c02'::uuid) AND (npm_remote_repository_id = '9e046fe4-5718-4cad-bbec-80ddb18b6862'::uuid))
Buffers: shared hit=6
Planning:
Buffers: shared hit=79
Planning Time: 0.406 ms
Execution Time: 0.041 msTimings: planning 0.406ms, execution 0.041ms, total 0.447ms.
datastore.ListNpmRemotePackages.DescCursorSet
Summary: Backward scan with the cursor's name < $3 folded into the index range bound, the mirror of the ascending deep page. 6 buffer hits at the 2500th name confirms the bound is a seek and not a post-scan filter. No anomalies.
Seed shape: npm_remote_packages=7500 (5000 live target repo, 2000 live sibling repo, 500 marked target), npm_remote_repositories=2, repositories=2, namespaces=1
Rendered SQL:
SELECT npm_remote_packages.namespace_id AS "npm_remote_packages.namespace_id",
npm_remote_packages.id AS "npm_remote_packages.id",
npm_remote_packages.npm_remote_repository_id AS "npm_remote_packages.npm_remote_repository_id",
npm_remote_packages.last_downloaded_at AS "npm_remote_packages.last_downloaded_at",
npm_remote_packages.soft_deleted_at AS "npm_remote_packages.soft_deleted_at",
npm_remote_packages.name AS "npm_remote_packages.name",
npm_remote_packages.scope AS "npm_remote_packages.scope"
FROM public.npm_remote_packages
WHERE (((npm_remote_packages.namespace_id = $1::uuid) AND (npm_remote_packages.npm_remote_repository_id = $2::uuid)) AND (npm_remote_packages.soft_deleted_at IS NULL)) AND (npm_remote_packages.name < $3::text)
ORDER BY npm_remote_packages.name DESC
LIMIT $4;Bound args: ['caf071aa-a94e-4b8a-ac1d-3efa9a807b38', '53dda79f-1dc5-4ae2-9799-066989261834', 'explain-2500', 101]
Plan (EXPLAIN (ANALYZE, BUFFERS) output):
Limit (cost=0.28..11.22 rows=101 width=109) (actual time=0.012..0.026 rows=101 loops=1)
Buffers: shared hit=6
-> Index Scan Backward using npm_remote_packages_p46_namespace_id_npm_remote_repository__idx on npm_remote_packages_p46 npm_remote_packages (cost=0.28..372.21 rows=3433 width=109) (actual time=0.011..0.021 rows=101 loops=1)
Index Cond: ((namespace_id = 'caf071aa-a94e-4b8a-ac1d-3efa9a807b38'::uuid) AND (npm_remote_repository_id = '53dda79f-1dc5-4ae2-9799-066989261834'::uuid) AND (name < 'explain-2500'::text))
Buffers: shared hit=6
Planning:
Buffers: shared hit=80
Planning Time: 0.411 ms
Execution Time: 0.039 msTimings: planning 0.411ms, execution 0.039ms, total 0.45ms.
datastore.ListNpmRemoteVersionsByPackage.CreatedAtAscCursorNil
Summary: Plan matches the method's intent: a forward Index Scan on index_npm_remote_versions_on_ns_id_pkg_id_created_at_id, pruned to 1 of 64 partitions. The index's (namespace_id, npm_remote_package_id, created_at, id) key set covers both the predicate and the two-column ORDER BY, and its soft_deleted_at IS NULL predicate absorbs the marker filter, so nothing is left for a Sort or a Filter. No anomalies.
Seed shape: npm_remote_versions=7500 (5000 live target package, 2000 live sibling package, 500 marked target), npm_remote_packages=2, npm_remote_repositories=1, repositories=1, namespaces=1
Rendered SQL:
SELECT npm_remote_versions.namespace_id AS "npm_remote_versions.namespace_id",
npm_remote_versions.id AS "npm_remote_versions.id",
npm_remote_versions.npm_remote_package_id AS "npm_remote_versions.npm_remote_package_id",
npm_remote_versions.last_downloaded_at AS "npm_remote_versions.last_downloaded_at",
npm_remote_versions.soft_deleted_at AS "npm_remote_versions.soft_deleted_at",
npm_remote_versions.created_at AS "npm_remote_versions.created_at",
npm_remote_versions.version AS "npm_remote_versions.version",
npm_remote_versions.size_bytes AS "npm_remote_versions.size_bytes"
FROM public.npm_remote_versions
WHERE ((npm_remote_versions.namespace_id = $1::uuid) AND (npm_remote_versions.npm_remote_package_id = $2::uuid)) AND (npm_remote_versions.soft_deleted_at IS NULL)
ORDER BY npm_remote_versions.created_at ASC, npm_remote_versions.id ASC
LIMIT $3;Bound args: ['7120ea5f-e1b5-4346-a0d7-4a40ef98f984', 'f2dc2f4d-289b-40b8-b9a5-349fc7caada3', 101]
Plan (EXPLAIN (ANALYZE, BUFFERS) output):
Limit (cost=0.28..11.39 rows=101 width=93) (actual time=0.010..0.026 rows=101 loops=1)
Buffers: shared hit=5
-> Index Scan using npm_remote_versions_p04_namespace_id_npm_remote_package_id_idx2 on npm_remote_versions_p04 npm_remote_versions (cost=0.28..564.75 rows=5133 width=93) (actual time=0.010..0.021 rows=101 loops=1)
Index Cond: ((namespace_id = '7120ea5f-e1b5-4346-a0d7-4a40ef98f984'::uuid) AND (npm_remote_package_id = 'f2dc2f4d-289b-40b8-b9a5-349fc7caada3'::uuid))
Buffers: shared hit=5
Planning:
Buffers: shared hit=322
Planning Time: 1.085 ms
Execution Time: 0.039 msTimings: planning 1.085ms, execution 0.039ms, total 1.124ms.
datastore.ListNpmRemoteVersionsByPackage.CreatedAtAscCursorSet
Summary: The row-value bound (created_at, id) > ($3, $4) renders as a single ROW(...) > ROW(...) comparison and lands inside Index Cond, so Postgres folds it into the index range instead of re-checking each row. That is the distinction between an O(limit) deep page and an O(offset) one, and it is why the mid-dataset page costs 5 buffer hits rather than scanning to the cursor. No anomalies.
Seed shape: npm_remote_versions=7500 (5000 live target package, 2000 live sibling package, 500 marked target), npm_remote_packages=2, npm_remote_repositories=1, repositories=1, namespaces=1
Rendered SQL:
SELECT npm_remote_versions.namespace_id AS "npm_remote_versions.namespace_id",
npm_remote_versions.id AS "npm_remote_versions.id",
npm_remote_versions.npm_remote_package_id AS "npm_remote_versions.npm_remote_package_id",
npm_remote_versions.last_downloaded_at AS "npm_remote_versions.last_downloaded_at",
npm_remote_versions.soft_deleted_at AS "npm_remote_versions.soft_deleted_at",
npm_remote_versions.created_at AS "npm_remote_versions.created_at",
npm_remote_versions.version AS "npm_remote_versions.version",
npm_remote_versions.size_bytes AS "npm_remote_versions.size_bytes"
FROM public.npm_remote_versions
WHERE (((npm_remote_versions.namespace_id = $1::uuid) AND (npm_remote_versions.npm_remote_package_id = $2::uuid)) AND (npm_remote_versions.soft_deleted_at IS NULL)) AND ((npm_remote_versions.created_at, npm_remote_versions.id) > ($3::timestamp with time zone, $4::uuid))
ORDER BY npm_remote_versions.created_at ASC, npm_remote_versions.id ASC
LIMIT $5;Bound args: ['1ab83bcb-33a1-4062-b665-2e19867bd0e7', '90fc9c07-6d80-42e7-ac69-9592c02e4192', '2026-01-02 17:40:00+00', '66666666-6666-4666-8666-666666666666', 101]
Plan (EXPLAIN (ANALYZE, BUFFERS) output):
Limit (cost=0.28..18.78 rows=101 width=93) (actual time=0.011..0.028 rows=101 loops=1)
Buffers: shared hit=5
-> Index Scan using npm_remote_versions_p30_namespace_id_npm_remote_package_id_idx2 on npm_remote_versions_p30 npm_remote_versions (cost=0.28..314.88 rows=1718 width=93) (actual time=0.011..0.023 rows=101 loops=1)
Index Cond: ((namespace_id = '1ab83bcb-33a1-4062-b665-2e19867bd0e7'::uuid) AND (npm_remote_package_id = '90fc9c07-6d80-42e7-ac69-9592c02e4192'::uuid) AND (ROW(created_at, id) > ROW('2026-01-02 17:40:00+00'::timestamp with time zone, '66666666-6666-4666-8666-666666666666'::uuid)))
Buffers: shared hit=5
Planning:
Buffers: shared hit=127
Planning Time: 0.582 ms
Execution Time: 0.059 msTimings: planning 0.582ms, execution 0.059ms, total 0.641ms.
datastore.ListNpmRemoteVersionsByPackage.CreatedAtDescCursorNil
Summary: The API's default sort. created_at DESC, id DESC runs as an Index Scan Backward over the created_at index with no Sort node, so the default page is as cheap as the ascending one (101 / 101, 6 buffer hits, 1 of 64 partitions). No anomalies.
Seed shape: npm_remote_versions=7500 (5000 live target package, 2000 live sibling package, 500 marked target), npm_remote_packages=2, npm_remote_repositories=1, repositories=1, namespaces=1
Rendered SQL:
SELECT npm_remote_versions.namespace_id AS "npm_remote_versions.namespace_id",
npm_remote_versions.id AS "npm_remote_versions.id",
npm_remote_versions.npm_remote_package_id AS "npm_remote_versions.npm_remote_package_id",
npm_remote_versions.last_downloaded_at AS "npm_remote_versions.last_downloaded_at",
npm_remote_versions.soft_deleted_at AS "npm_remote_versions.soft_deleted_at",
npm_remote_versions.created_at AS "npm_remote_versions.created_at",
npm_remote_versions.version AS "npm_remote_versions.version",
npm_remote_versions.size_bytes AS "npm_remote_versions.size_bytes"
FROM public.npm_remote_versions
WHERE ((npm_remote_versions.namespace_id = $1::uuid) AND (npm_remote_versions.npm_remote_package_id = $2::uuid)) AND (npm_remote_versions.soft_deleted_at IS NULL)
ORDER BY npm_remote_versions.created_at DESC, npm_remote_versions.id DESC
LIMIT $3;Bound args: ['25f8e413-67e5-490d-bde5-fe7ef6492cd0', '4ba14eca-13d9-482f-b972-be974ee30a3b', 101]
Plan (EXPLAIN (ANALYZE, BUFFERS) output):
Limit (cost=0.28..11.39 rows=101 width=93) (actual time=0.011..0.027 rows=101 loops=1)
Buffers: shared hit=6
-> Index Scan Backward using npm_remote_versions_p61_namespace_id_npm_remote_package_id_idx2 on npm_remote_versions_p61 npm_remote_versions (cost=0.28..564.75 rows=5133 width=93) (actual time=0.010..0.023 rows=101 loops=1)
Index Cond: ((namespace_id = '25f8e413-67e5-490d-bde5-fe7ef6492cd0'::uuid) AND (npm_remote_package_id = '4ba14eca-13d9-482f-b972-be974ee30a3b'::uuid))
Buffers: shared hit=6
Planning:
Buffers: shared hit=128
Planning Time: 0.580 ms
Execution Time: 0.041 msTimings: planning 0.580ms, execution 0.041ms, total 0.621ms.
datastore.ListNpmRemoteVersionsByPackage.CreatedAtDescCursorSet
Summary: Backward scan with the row-value bound ROW(created_at, id) < ROW($3, $4) inside Index Cond, the descending mirror of the ascending deep page. This is the combination the versions list serves by default, and it holds the flat-latency property at 6 buffer hits. No anomalies.
Seed shape: npm_remote_versions=7500 (5000 live target package, 2000 live sibling package, 500 marked target), npm_remote_packages=2, npm_remote_repositories=1, repositories=1, namespaces=1
Rendered SQL:
SELECT npm_remote_versions.namespace_id AS "npm_remote_versions.namespace_id",
npm_remote_versions.id AS "npm_remote_versions.id",
npm_remote_versions.npm_remote_package_id AS "npm_remote_versions.npm_remote_package_id",
npm_remote_versions.last_downloaded_at AS "npm_remote_versions.last_downloaded_at",
npm_remote_versions.soft_deleted_at AS "npm_remote_versions.soft_deleted_at",
npm_remote_versions.created_at AS "npm_remote_versions.created_at",
npm_remote_versions.version AS "npm_remote_versions.version",
npm_remote_versions.size_bytes AS "npm_remote_versions.size_bytes"
FROM public.npm_remote_versions
WHERE (((npm_remote_versions.namespace_id = $1::uuid) AND (npm_remote_versions.npm_remote_package_id = $2::uuid)) AND (npm_remote_versions.soft_deleted_at IS NULL)) AND ((npm_remote_versions.created_at, npm_remote_versions.id) < ($3::timestamp with time zone, $4::uuid))
ORDER BY npm_remote_versions.created_at DESC, npm_remote_versions.id DESC
LIMIT $5;Bound args: ['d3a2a6b8-37e4-4f21-bcc9-ec87fe2accbe', '469d2581-5689-459e-91de-9ef17bf7f746', '2026-01-02 17:40:00+00', '66666666-6666-4666-8666-666666666666', 101]
Plan (EXPLAIN (ANALYZE, BUFFERS) output):
Limit (cost=0.28..12.62 rows=101 width=93) (actual time=0.010..0.028 rows=101 loops=1)
Buffers: shared hit=6
-> Index Scan Backward using npm_remote_versions_p32_namespace_id_npm_remote_package_id_idx2 on npm_remote_versions_p32 npm_remote_versions (cost=0.28..417.43 rows=3415 width=93) (actual time=0.010..0.023 rows=101 loops=1)
Index Cond: ((namespace_id = 'd3a2a6b8-37e4-4f21-bcc9-ec87fe2accbe'::uuid) AND (npm_remote_package_id = '469d2581-5689-459e-91de-9ef17bf7f746'::uuid) AND (ROW(created_at, id) < ROW('2026-01-02 17:40:00+00'::timestamp with time zone, '66666666-6666-4666-8666-666666666666'::uuid)))
Buffers: shared hit=6
Planning:
Buffers: shared hit=134
Planning Time: 0.579 ms
Execution Time: 0.059 msTimings: planning 0.579ms, execution 0.059ms, total 0.638ms.
datastore.ListNpmRemoteVersionsByPackage.VersionAscCursorNil
Summary: The version sort reads its own index: a forward scan on the partial unique unique_npm_remote_versions_ns_id_pkg_id_version, pruned to 1 of 64 partitions, with ORDER BY version ASC satisfied by the index key. Ordering is lexicographic rather than semver, which the type's doc states. No anomalies.
Seed shape: npm_remote_versions=7500 (5000 live target package, 2000 live sibling package, 500 marked target), npm_remote_packages=2, npm_remote_repositories=1, repositories=1, namespaces=1
Rendered SQL:
SELECT npm_remote_versions.namespace_id AS "npm_remote_versions.namespace_id",
npm_remote_versions.id AS "npm_remote_versions.id",
npm_remote_versions.npm_remote_package_id AS "npm_remote_versions.npm_remote_package_id",
npm_remote_versions.last_downloaded_at AS "npm_remote_versions.last_downloaded_at",
npm_remote_versions.soft_deleted_at AS "npm_remote_versions.soft_deleted_at",
npm_remote_versions.created_at AS "npm_remote_versions.created_at",
npm_remote_versions.version AS "npm_remote_versions.version",
npm_remote_versions.size_bytes AS "npm_remote_versions.size_bytes"
FROM public.npm_remote_versions
WHERE ((npm_remote_versions.namespace_id = $1::uuid) AND (npm_remote_versions.npm_remote_package_id = $2::uuid)) AND (npm_remote_versions.soft_deleted_at IS NULL)
ORDER BY npm_remote_versions.version ASC
LIMIT $3;Bound args: ['60a674c5-2ab5-4d7e-937a-bf1134694aad', 'b01ebde0-8ccb-4d6a-888e-b5d8df622378', 101]
Plan (EXPLAIN (ANALYZE, BUFFERS) output):
Limit (cost=0.28..10.92 rows=101 width=93) (actual time=0.010..0.027 rows=101 loops=1)
Buffers: shared hit=5
-> Index Scan using npm_remote_versions_p24_namespace_id_npm_remote_package_id__idx on npm_remote_versions_p24 npm_remote_versions (cost=0.28..540.75 rows=5133 width=93) (actual time=0.010..0.022 rows=101 loops=1)
Index Cond: ((namespace_id = '60a674c5-2ab5-4d7e-937a-bf1134694aad'::uuid) AND (npm_remote_package_id = 'b01ebde0-8ccb-4d6a-888e-b5d8df622378'::uuid))
Buffers: shared hit=5
Planning:
Buffers: shared hit=116
Planning Time: 0.544 ms
Execution Time: 0.042 msTimings: planning 0.544ms, execution 0.042ms, total 0.586ms.
datastore.ListNpmRemoteVersionsByPackage.VersionAscCursorSet
Summary: Same index as the version first page, with the cursor's version > $3 folded into the index range bound. 5 buffer hits at the 2500th version. No anomalies.
Seed shape: npm_remote_versions=7500 (5000 live target package, 2000 live sibling package, 500 marked target), npm_remote_packages=2, npm_remote_repositories=1, repositories=1, namespaces=1
Rendered SQL:
SELECT npm_remote_versions.namespace_id AS "npm_remote_versions.namespace_id",
npm_remote_versions.id AS "npm_remote_versions.id",
npm_remote_versions.npm_remote_package_id AS "npm_remote_versions.npm_remote_package_id",
npm_remote_versions.last_downloaded_at AS "npm_remote_versions.last_downloaded_at",
npm_remote_versions.soft_deleted_at AS "npm_remote_versions.soft_deleted_at",
npm_remote_versions.created_at AS "npm_remote_versions.created_at",
npm_remote_versions.version AS "npm_remote_versions.version",
npm_remote_versions.size_bytes AS "npm_remote_versions.size_bytes"
FROM public.npm_remote_versions
WHERE (((npm_remote_versions.namespace_id = $1::uuid) AND (npm_remote_versions.npm_remote_package_id = $2::uuid)) AND (npm_remote_versions.soft_deleted_at IS NULL)) AND (npm_remote_versions.version > $3::text)
ORDER BY npm_remote_versions.version ASC
LIMIT $4;Bound args: ['900bd3d6-4422-45b0-9912-6f1c5d3099a2', '80538164-c3c9-40d9-8ba4-55656bdf9cb8', 'explain-2500', 101]
Plan (EXPLAIN (ANALYZE, BUFFERS) output):
Limit (cost=0.28..17.49 rows=101 width=93) (actual time=0.011..0.028 rows=101 loops=1)
Buffers: shared hit=5
-> Index Scan using npm_remote_versions_p46_namespace_id_npm_remote_package_id__idx on npm_remote_versions_p46 npm_remote_versions (cost=0.28..289.91 rows=1700 width=93) (actual time=0.011..0.023 rows=101 loops=1)
Index Cond: ((namespace_id = '900bd3d6-4422-45b0-9912-6f1c5d3099a2'::uuid) AND (npm_remote_package_id = '80538164-c3c9-40d9-8ba4-55656bdf9cb8'::uuid) AND (version > 'explain-2500'::text))
Buffers: shared hit=5
Planning:
Buffers: shared hit=118
Planning Time: 0.538 ms
Execution Time: 0.044 msTimings: planning 0.538ms, execution 0.044ms, total 0.582ms.
datastore.ListNpmRemoteVersionsByPackage.VersionDescCursorNil
Summary: ORDER BY version DESC runs as an Index Scan Backward over the same partial unique with no Sort node. No anomalies.
Seed shape: npm_remote_versions=7500 (5000 live target package, 2000 live sibling package, 500 marked target), npm_remote_packages=2, npm_remote_repositories=1, repositories=1, namespaces=1
Rendered SQL:
SELECT npm_remote_versions.namespace_id AS "npm_remote_versions.namespace_id",
npm_remote_versions.id AS "npm_remote_versions.id",
npm_remote_versions.npm_remote_package_id AS "npm_remote_versions.npm_remote_package_id",
npm_remote_versions.last_downloaded_at AS "npm_remote_versions.last_downloaded_at",
npm_remote_versions.soft_deleted_at AS "npm_remote_versions.soft_deleted_at",
npm_remote_versions.created_at AS "npm_remote_versions.created_at",
npm_remote_versions.version AS "npm_remote_versions.version",
npm_remote_versions.size_bytes AS "npm_remote_versions.size_bytes"
FROM public.npm_remote_versions
WHERE ((npm_remote_versions.namespace_id = $1::uuid) AND (npm_remote_versions.npm_remote_package_id = $2::uuid)) AND (npm_remote_versions.soft_deleted_at IS NULL)
ORDER BY npm_remote_versions.version DESC
LIMIT $3;Bound args: ['d9f2e057-998f-4d4f-a887-d45be08b9782', '732f2a02-b236-4727-996d-8f0055241c97', 101]
Plan (EXPLAIN (ANALYZE, BUFFERS) output):
Limit (cost=0.28..10.92 rows=101 width=93) (actual time=0.011..0.028 rows=101 loops=1)
Buffers: shared hit=6
-> Index Scan Backward using npm_remote_versions_p28_namespace_id_npm_remote_package_id__idx on npm_remote_versions_p28 npm_remote_versions (cost=0.28..540.75 rows=5133 width=93) (actual time=0.011..0.023 rows=101 loops=1)
Index Cond: ((namespace_id = 'd9f2e057-998f-4d4f-a887-d45be08b9782'::uuid) AND (npm_remote_package_id = '732f2a02-b236-4727-996d-8f0055241c97'::uuid))
Buffers: shared hit=6
Planning:
Buffers: shared hit=120
Planning Time: 0.570 ms
Execution Time: 0.042 msTimings: planning 0.570ms, execution 0.042ms, total 0.612ms.
datastore.ListNpmRemoteVersionsByPackage.VersionDescCursorSet
Summary: Backward scan with version < $3 in the Index Cond. Execution time (0.077 ms) is the highest in the set and still two orders of magnitude under any page budget; the gap is measurement noise on a 101-row read, not a plan difference. No anomalies.
Seed shape: npm_remote_versions=7500 (5000 live target package, 2000 live sibling package, 500 marked target), npm_remote_packages=2, npm_remote_repositories=1, repositories=1, namespaces=1
Rendered SQL:
SELECT npm_remote_versions.namespace_id AS "npm_remote_versions.namespace_id",
npm_remote_versions.id AS "npm_remote_versions.id",
npm_remote_versions.npm_remote_package_id AS "npm_remote_versions.npm_remote_package_id",
npm_remote_versions.last_downloaded_at AS "npm_remote_versions.last_downloaded_at",
npm_remote_versions.soft_deleted_at AS "npm_remote_versions.soft_deleted_at",
npm_remote_versions.created_at AS "npm_remote_versions.created_at",
npm_remote_versions.version AS "npm_remote_versions.version",
npm_remote_versions.size_bytes AS "npm_remote_versions.size_bytes"
FROM public.npm_remote_versions
WHERE (((npm_remote_versions.namespace_id = $1::uuid) AND (npm_remote_versions.npm_remote_package_id = $2::uuid)) AND (npm_remote_versions.soft_deleted_at IS NULL)) AND (npm_remote_versions.version < $3::text)
ORDER BY npm_remote_versions.version DESC
LIMIT $4;Bound args: ['9c3f7a86-869c-4517-8e6b-34a022ba389d', '8c5d4fdd-5e44-4f31-b196-ce4202e4f805', 'explain-2500', 101]
Plan (EXPLAIN (ANALYZE, BUFFERS) output):
Limit (cost=0.28..22.13 rows=101 width=93) (actual time=0.026..0.053 rows=101 loops=1)
Buffers: shared hit=6
-> Index Scan Backward using npm_remote_versions_p46_namespace_id_npm_remote_package_id__idx on npm_remote_versions_p46 npm_remote_versions (cost=0.28..742.71 rows=3433 width=93) (actual time=0.025..0.044 rows=101 loops=1)
Index Cond: ((namespace_id = '9c3f7a86-869c-4517-8e6b-34a022ba389d'::uuid) AND (npm_remote_package_id = '8c5d4fdd-5e44-4f31-b196-ce4202e4f805'::uuid) AND (version < 'explain-2500'::text))
Buffers: shared hit=6
Planning:
Buffers: shared hit=120
Planning Time: 1.098 ms
Execution Time: 0.077 msTimings: planning 1.098ms, execution 0.077ms, total 1.175ms.
Related to #314