Skip to content

Commit dc14b30

Browse files
committed
fix: make packagist 30d window insert atomic
Signed-off-by: anilb <epipav@gmail.com>
1 parent 6251909 commit dc14b30

3 files changed

Lines changed: 58 additions & 25 deletions

File tree

services/apps/packages_worker/src/packagist/__tests__/downloads.test.ts

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,21 @@
11
import { beforeEach, describe, expect, it, vi } from 'vitest'
22

3-
import {
4-
getExistingLast30dEndDates,
5-
upsertLast30dDownload,
6-
} from '@crowd/data-access-layer/src/packages'
3+
import { insertLast30dDownloadIfAbsent } from '@crowd/data-access-layer/src/packages'
74
import type { QueryExecutor } from '@crowd/data-access-layer/src/queryExecutor'
85

96
import { monthlyWindowFor, persistPackagist30dWindow } from '../downloads'
107

118
vi.mock('@crowd/data-access-layer/src/packages', () => ({
12-
getExistingLast30dEndDates: vi.fn().mockResolvedValue([]),
13-
upsertLast30dDownload: vi.fn().mockResolvedValue([]),
9+
insertLast30dDownloadIfAbsent: vi.fn().mockResolvedValue([]),
1410
}))
1511

16-
const mockExistingWindows = vi.mocked(getExistingLast30dEndDates)
17-
const mockWindow = vi.mocked(upsertLast30dDownload)
12+
const mockWindow = vi.mocked(insertLast30dDownloadIfAbsent)
1813

1914
const qx = {} as QueryExecutor
2015
const PURL = 'pkg:composer/monolog/monolog'
2116

2217
beforeEach(() => {
2318
vi.clearAllMocks()
24-
mockExistingWindows.mockResolvedValue([])
2519
})
2620

2721
// Observed rolling window anchored on the 1st of the run month,
@@ -49,30 +43,31 @@ describe('monthlyWindowFor', () => {
4943
// The monthly downloads-30d lane: one window row per purl per month, mirrored to
5044
// packages.downloads_last_30d (npm parity). A window already recorded for the month
5145
// is never overwritten — the value is the observation closest to the boundary.
46+
// insertLast30dDownloadIfAbsent does the presence check and the write atomically, so
47+
// there is no separate "does it exist" call to assert on here — a race is exercised
48+
// directly against Postgres in downloadsLast30d's own DAL-level coverage.
5249
describe('persistPackagist30dWindow', () => {
5350
it('writes the window with the mirror and returns the changed fields when the month has no row yet', async () => {
5451
mockWindow.mockResolvedValue(['downloads_last_30d.count', 'packages.downloads_last_30d'])
5552

5653
const changedFields = await persistPackagist30dWindow(qx, PURL, 300, '2026-07-01')
5754

58-
expect(mockExistingWindows).toHaveBeenCalledWith(qx, PURL, '2026-07-01', '2026-07-01')
5955
expect(mockWindow).toHaveBeenCalledWith(qx, PURL, '2026-06-01', '2026-07-01', 300, true)
6056
expect(changedFields).toEqual(['downloads_last_30d.count', 'packages.downloads_last_30d'])
6157
})
6258

63-
it('does not overwrite an existing window for the month, and returns no changed fields', async () => {
64-
mockExistingWindows.mockResolvedValue(['2026-07-01'])
59+
it('returns no changed fields when a window for the month already exists', async () => {
60+
mockWindow.mockResolvedValue([])
6561

6662
const changedFields = await persistPackagist30dWindow(qx, PURL, 300, '2026-07-15')
6763

68-
expect(mockWindow).not.toHaveBeenCalled()
64+
expect(mockWindow).toHaveBeenCalledWith(qx, PURL, '2026-06-01', '2026-07-01', 300, true)
6965
expect(changedFields).toEqual([])
7066
})
7167

7268
it('writes nothing and returns no changed fields when the registry reported no monthly count', async () => {
7369
const changedFields = await persistPackagist30dWindow(qx, PURL, null, '2026-07-01')
7470

75-
expect(mockExistingWindows).not.toHaveBeenCalled()
7671
expect(mockWindow).not.toHaveBeenCalled()
7772
expect(changedFields).toEqual([])
7873
})

services/apps/packages_worker/src/packagist/downloads.ts

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
import {
2-
getExistingLast30dEndDates,
3-
upsertLast30dDownload,
4-
} from '@crowd/data-access-layer/src/packages'
1+
import { insertLast30dDownloadIfAbsent } from '@crowd/data-access-layer/src/packages'
52
import type { QueryExecutor } from '@crowd/data-access-layer/src/queryExecutor'
63

74
// Compute the monthly window for observed rolling 30d downloads.
@@ -21,8 +18,11 @@ export function monthlyWindowFor(runDate: string): { startDate: string; endDate:
2118

2219
// One window row per purl per month, mirrored to packages.downloads_last_30d
2320
// (npm parity). A window already recorded for the month is never overwritten —
24-
// the value is the observation closest to the boundary. Returns the changed
25-
// fields (like the sibling persist*/upsert* functions) — the caller audits them.
21+
// the value is the observation closest to the boundary. insertLast30dDownloadIfAbsent
22+
// does the presence check and the insert (+ mirror) atomically in one statement, so
23+
// concurrent runs for the same purl+month can't both pass a check-then-insert gap and
24+
// have the later one clobber the earlier one's count. Returns the changed fields (like
25+
// the sibling persist*/upsert* functions) — the caller audits them.
2626
export async function persistPackagist30dWindow(
2727
qx: QueryExecutor,
2828
purl: string,
@@ -32,9 +32,5 @@ export async function persistPackagist30dWindow(
3232
if (monthly == null) return []
3333

3434
const { startDate, endDate } = monthlyWindowFor(runDate)
35-
const existing = await getExistingLast30dEndDates(qx, purl, endDate, endDate)
36-
if (existing.length === 0) {
37-
return upsertLast30dDownload(qx, purl, startDate, endDate, monthly, true)
38-
}
39-
return []
35+
return insertLast30dDownloadIfAbsent(qx, purl, startDate, endDate, monthly, true)
4036
}

services/libs/data-access-layer/src/packages/downloadsLast30d.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,48 @@ export async function markLast30dHistoryBackfilled(
133133
)
134134
}
135135

136+
// Atomic "first observation wins" insert: the window row and its packages mirror are
137+
// written by one statement via ON CONFLICT (purl, end_date) DO NOTHING, so concurrent
138+
// callers for the same purl+month (overlapping Temporal retries, or two lanes racing)
139+
// can never both pass a check-then-insert gap and have the later one clobber the
140+
// earlier one's count — only the writer that actually inserts the row also mirrors.
141+
export async function insertLast30dDownloadIfAbsent(
142+
qx: QueryExecutor,
143+
purl: string,
144+
startDate: string,
145+
endDate: string,
146+
count: number,
147+
mirrorToPackages: boolean,
148+
): Promise<string[]> {
149+
const row: { inserted: boolean; mirrored: boolean } = await qx.selectOne(
150+
`WITH ins AS (
151+
INSERT INTO downloads_last_30d (purl, start_date, end_date, count, created_at, updated_at)
152+
VALUES ($(purl), $(startDate)::date, $(endDate)::date, $(count), NOW(), NOW())
153+
ON CONFLICT (purl, end_date) DO NOTHING
154+
RETURNING purl, count
155+
),
156+
pkg AS (
157+
UPDATE packages p
158+
SET downloads_last_30d = ins.count,
159+
last_synced_at = NOW()
160+
FROM ins
161+
WHERE p.purl = ins.purl
162+
AND $(mirrorToPackages)
163+
AND p.downloads_last_30d IS DISTINCT FROM ins.count
164+
RETURNING p.id
165+
)
166+
SELECT
167+
EXISTS (SELECT 1 FROM ins) AS inserted,
168+
EXISTS (SELECT 1 FROM pkg) AS mirrored`,
169+
{ purl, startDate, endDate, count, mirrorToPackages },
170+
)
171+
172+
if (!row.inserted) return []
173+
const changed = ['downloads_last_30d.start_date', 'downloads_last_30d.count']
174+
if (row.mirrored) changed.push('packages.downloads_last_30d')
175+
return changed
176+
}
177+
136178
export async function upsertLast30dDownload(
137179
qx: QueryExecutor,
138180
purl: string,

0 commit comments

Comments
 (0)