Skip to content

Commit 8565964

Browse files
authored
feat: add rubygems ecosystem to bq-dataset-ingest (CM-1296) (#4307)
Signed-off-by: Uroš Marolt <uros@marolt.me>
1 parent 78abab5 commit 8565964

16 files changed

Lines changed: 581 additions & 873 deletions

services/apps/packages_worker/src/deps-dev/activities/bqExportToGcs.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
findExportedJobByGcsPrefix,
77
findLatestExportedJobByKind,
88
markJobStatus,
9+
mergeJobTableRowCounts,
910
} from '@crowd/data-access-layer'
1011
import { getServiceChildLogger } from '@crowd/logging'
1112

@@ -32,6 +33,12 @@ export interface BqExportToGcsInput {
3233
// is replaced by a server-side maximumBytesBilled cap, since a dry-run only validates the first
3334
// statement and cannot predict the WHILE loop's total scan. See ADR-0004.
3435
isScript?: boolean
36+
// Fill-constraints run (package_dependencies only): the export is a full Option-A scan but the
37+
// downstream merge upserts version_constraint (ON CONFLICT DO UPDATE) instead of DO NOTHING. Full
38+
// and fill produce identical parquet, so sync_mode alone can't tell them apart — persist it in the
39+
// job meta so a --resume-job run reprocesses the export with the correct merge instead of silently
40+
// reverting to DO NOTHING (which skips the backfill).
41+
isFill?: boolean
3542
}
3643

3744
export interface BqExportToGcsOutput {
@@ -53,6 +60,7 @@ export async function bqExportToGcs(input: BqExportToGcsInput): Promise<BqExport
5360
exportName,
5461
ecosystems,
5562
isScript,
63+
isFill,
5664
} = input
5765

5866
// Named exports use a stable GCS path independent of runId so they survive across bootstrap runs.
@@ -79,6 +87,12 @@ export async function bqExportToGcs(input: BqExportToGcsInput): Promise<BqExport
7987
{ jobKind, exportName, jobId: prior.id, gcsPrefix: prior.gcsPrefix },
8088
'exportName match — skipping BQ, loading from named export',
8189
)
90+
// The reusing run drives the chunk merge on this same job row, so meta:fill must reflect
91+
// THIS run's intent — write it both ways. Setting it (fill run) stops a later --resume-job
92+
// reverting to ON CONFLICT DO NOTHING and skipping the version_constraint backfill; clearing
93+
// it (non-fill run reusing a row an earlier fill run set) stops resume forcing an unintended
94+
// upsert. Absent and 0 both read back as fill=false (COALESCE in getIngestJobForResume).
95+
await mergeJobTableRowCounts(qx, prior.id, { 'meta:fill': isFill ? 1 : 0 })
8296
return {
8397
gcsPrefix: prior.gcsPrefix,
8498
rowCount: prior.rowCountBq,
@@ -115,6 +129,7 @@ export async function bqExportToGcs(input: BqExportToGcsInput): Promise<BqExport
115129
tableRowCounts: {
116130
'bq:export': 0,
117131
...(ecosystems ? { 'meta:ecosystems': ecosystems } : {}),
132+
...(isFill ? { 'meta:fill': 1 } : {}),
118133
},
119134
})
120135
return { gcsPrefix: namedPrefix, rowCount: 0, bqBytesBilled: 0, jobId }
@@ -135,6 +150,9 @@ export async function bqExportToGcs(input: BqExportToGcsInput): Promise<BqExport
135150
{ jobKind, jobId: prior.id, gcsPrefix: prior.gcsPrefix },
136151
'reuseExports=true — skipping BQ, loading from prior export',
137152
)
153+
// See named-export path above: write THIS run's fill intent both ways so a later
154+
// --resume-job matches the most recent run instead of a stale meta value.
155+
await mergeJobTableRowCounts(qx, prior.id, { 'meta:fill': isFill ? 1 : 0 })
138156
return {
139157
gcsPrefix: prior.gcsPrefix,
140158
rowCount: prior.rowCountBq,
@@ -163,6 +181,9 @@ export async function bqExportToGcs(input: BqExportToGcsInput): Promise<BqExport
163181
{ jobKind, jobId: existing.id, gcsPrefix },
164182
'GCS files already exist — reusing export',
165183
)
184+
// Same-runId reuse (Temporal retry): re-stamp THIS run's fill intent both ways so the flag
185+
// always matches the most recent run and can never be lost or left stale on reuse.
186+
await mergeJobTableRowCounts(qx, existing.id, { 'meta:fill': isFill ? 1 : 0 })
166187
return { gcsPrefix, rowCount: existing.rowCountBq, bqBytesBilled: 0, jobId: existing.id }
167188
}
168189
}
@@ -212,9 +233,13 @@ export async function bqExportToGcs(input: BqExportToGcsInput): Promise<BqExport
212233
const provisionalDate = snapshotAt ? new Date(snapshotAt) : null
213234
const jobId = await createIngestJob(qx, jobKind, syncMode, provisionalDate, exportName)
214235

215-
// H7: mark exporting before we start the BQ job; store ecosystems filter in table_row_counts JSONB.
236+
// H7: mark exporting before we start the BQ job; store ecosystems filter + fill flag in the
237+
// table_row_counts JSONB so --resume-job can restore the original export's settings.
238+
const exportMeta: Record<string, string | number | string[]> = {}
239+
if (ecosystems) exportMeta['meta:ecosystems'] = ecosystems
240+
if (isFill) exportMeta['meta:fill'] = 1
216241
await markJobStatus(qx, jobId, 'exporting', {
217-
...(ecosystems ? { tableRowCounts: { 'meta:ecosystems': ecosystems } } : {}),
242+
...(Object.keys(exportMeta).length > 0 ? { tableRowCounts: exportMeta } : {}),
218243
})
219244

220245
// From here the row is 'exporting'; any BQ failure (incl. script-mode maximumBytesBilled aborts)

services/apps/packages_worker/src/deps-dev/activities/createVersionsLookup.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { getPackagesDb } from '../../db'
44

55
const log = getServiceChildLogger('createVersionsLookup')
66

7-
const VALID_ECOSYSTEMS = new Set(['npm', 'go', 'maven', 'pypi', 'nuget', 'cargo'])
7+
const VALID_ECOSYSTEMS = new Set(['npm', 'go', 'maven', 'pypi', 'nuget', 'cargo', 'rubygems'])
88

99
// Builds a persistent UNLOGGED lookup table in the staging schema for the root-version JOIN in
1010
// ingestDependencies. Using a temp table per chunk would rebuild 4GB for every chunk on npm
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { getIngestJobForResume } from '@crowd/data-access-layer'
2+
3+
import { getPackagesDb } from '../../db'
4+
5+
export interface GetResumeExportInput {
6+
jobId: number
7+
}
8+
9+
export interface GetResumeExportOutput {
10+
jobId: number
11+
jobKind: string
12+
status: string
13+
syncMode: string
14+
gcsPrefix: string | null
15+
progressDone: number
16+
progressTotal: number
17+
rowCountPg: number
18+
ecosystems: string[] | null
19+
fill: boolean
20+
}
21+
22+
// Pure fetch of a prior job's resume-relevant fields (export path, status, file-load progress,
23+
// rows merged). Returns null if the job id does not exist. All validation — kind, status, presence
24+
// of an export, and that the parquet files still exist — is done by the caller (ingestDependencies)
25+
// so it can fail fast with non-retryable errors instead of retrying a bad-input activity.
26+
export async function getResumeExport(
27+
input: GetResumeExportInput,
28+
): Promise<GetResumeExportOutput | null> {
29+
const qx = await getPackagesDb()
30+
const job = await getIngestJobForResume(qx, input.jobId)
31+
if (!job) {
32+
return null
33+
}
34+
return {
35+
jobId: job.id,
36+
jobKind: job.jobKind,
37+
status: job.status,
38+
syncMode: job.syncMode,
39+
gcsPrefix: job.gcsPrefix,
40+
progressDone: job.progressDone,
41+
progressTotal: job.progressTotal,
42+
rowCountPg: job.rowCountPg,
43+
ecosystems: job.ecosystems,
44+
fill: job.fill,
45+
}
46+
}

services/apps/packages_worker/src/deps-dev/activities/index.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
export * from './bqExportToGcs'
22
export * from './setJobStep'
33
export * from './createVersionsLookup'
4-
export * from './managePackageDepsConstraints'
5-
export * from './managePackageDepsIndexes'
6-
export * from './manageVersionsConstraints'
7-
export * from './manageVersionsIndexes'
84
export * from './listParquetFiles'
95
export * from './gcsParquetToStaging'
106
export * from './mergeStagingToTable'
117
export * from './getLastSnapshot'
8+
export * from './getResumeExport'
129
export * from './checkDependentCountsGuard'
1310
export * from './checkEdgeSnapshotQuality'
1411
export * from './probePartitionExists'

services/apps/packages_worker/src/deps-dev/activities/managePackageDepsConstraints.ts

Lines changed: 0 additions & 93 deletions
This file was deleted.

0 commit comments

Comments
 (0)