From 9fb44c22d681f276472fa2f10eb8d4d990e1a9af Mon Sep 17 00:00:00 2001 From: unkin-agent Date: Sun, 25 Oct 2026 20:27:27 +1100 Subject: [PATCH 1/2] Document the serialised-writer invariant behind the repodata fingerprint --- docs/rpm/remote-github.md | 4 +++- internal/database/rpm_metadata.go | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/rpm/remote-github.md b/docs/rpm/remote-github.md index 6180e8a..35e8be6 100644 --- a/docs/rpm/remote-github.md +++ b/docs/rpm/remote-github.md @@ -92,4 +92,6 @@ Repodata is regenerated from the metadata rows on every request by default. Set `cache_repodata = true` on the remote to keep the generated `repomd.xml` and its `primary`/`filelists`/`other` files in memory per replica. Each request checks a cheap row fingerprint (count + newest id) in the database and rebuilds only when -it moved, so every replica picks up a scan's changes on its next request. +it moved, so every replica picks up a scan's changes on its next request. The +fingerprint relies on only one scan writing a remote's rows at a time, which the +sync lease guarantees across replicas. diff --git a/internal/database/rpm_metadata.go b/internal/database/rpm_metadata.go index ccde123..bffbfcd 100644 --- a/internal/database/rpm_metadata.go +++ b/internal/database/rpm_metadata.go @@ -42,6 +42,7 @@ func (db *DB) DeleteRPMMetadata(ctx context.Context, repoName, filePath string) // RPMMetadataFingerprint is a cheap change detector for a repo's rows: they are // only ever inserted or deleted, so (count, max id) moves whenever they change. +// Sound only while a remote's writers are serialised (github_rpm sync lease), so ids commit in order. func (db *DB) RPMMetadataFingerprint(ctx context.Context, repoName string) (count, maxID int64, err error) { err = db.Pool.QueryRow(ctx, `SELECT count(*), coalesce(max(id), 0) FROM rpm_metadata WHERE repo_name = $1`, repoName).Scan(&count, &maxID) return count, maxID, err -- 2.47.3 From df4c397db92bbbb3cec0992fc336c7658257e6af Mon Sep 17 00:00:00 2001 From: unkin-agent Date: Sun, 25 Oct 2026 20:31:59 +1100 Subject: [PATCH 2/2] Scope the fingerprint invariant to the wired syncer and pin lease > scan timeout --- docs/rpm/remote-github.md | 4 ++-- internal/database/rpm_metadata.go | 2 +- internal/provider/rpm/syncer_test.go | 6 ++++++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/rpm/remote-github.md b/docs/rpm/remote-github.md index 35e8be6..6a525dd 100644 --- a/docs/rpm/remote-github.md +++ b/docs/rpm/remote-github.md @@ -93,5 +93,5 @@ Repodata is regenerated from the metadata rows on every request by default. Set `primary`/`filelists`/`other` files in memory per replica. Each request checks a cheap row fingerprint (count + newest id) in the database and rebuilds only when it moved, so every replica picks up a scan's changes on its next request. The -fingerprint relies on only one scan writing a remote's rows at a time, which the -sync lease guarantees across replicas. +fingerprint relies on only one scan writing a remote's rows at a time; in the +deployed server, the sync lease serialises scans across replicas. diff --git a/internal/database/rpm_metadata.go b/internal/database/rpm_metadata.go index bffbfcd..1f56345 100644 --- a/internal/database/rpm_metadata.go +++ b/internal/database/rpm_metadata.go @@ -42,7 +42,7 @@ func (db *DB) DeleteRPMMetadata(ctx context.Context, repoName, filePath string) // RPMMetadataFingerprint is a cheap change detector for a repo's rows: they are // only ever inserted or deleted, so (count, max id) moves whenever they change. -// Sound only while a remote's writers are serialised (github_rpm sync lease), so ids commit in order. +// For github_rpm remotes it relies on the sync lease serialising scans. func (db *DB) RPMMetadataFingerprint(ctx context.Context, repoName string) (count, maxID int64, err error) { err = db.Pool.QueryRow(ctx, `SELECT count(*), coalesce(max(id), 0) FROM rpm_metadata WHERE repo_name = $1`, repoName).Scan(&count, &maxID) return count, maxID, err diff --git a/internal/provider/rpm/syncer_test.go b/internal/provider/rpm/syncer_test.go index 2ef0f1e..ff3713a 100644 --- a/internal/provider/rpm/syncer_test.go +++ b/internal/provider/rpm/syncer_test.go @@ -510,3 +510,9 @@ func TestSyncerScheduledPrime(t *testing.T) { t.Fatalf("cold-cache prime: attempts=%d freshness=%v interval=%v, want 1/0/0", store.attempts, store.freshness, store.interval) } } + +func TestSyncLeaseOutlivesScanTimeout(t *testing.T) { + if syncLeaseDuration <= defaultScanTimeout { + t.Fatalf("syncLeaseDuration %v must exceed defaultScanTimeout %v so scans stay serialised", syncLeaseDuration, defaultScanTimeout) + } +} -- 2.47.3