Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
2bec760
fix: drive osspckgs monitor total ETA off file progress
themarolt Jun 30, 2026
7c45233
fix: drop diluted-rate fallback in osspckgs monitor merge ETA
themarolt Jun 30, 2026
66c89b7
feat: resume partially-merged package_dependencies ingest by job id
themarolt Jul 2, 2026
d9244ba
Merge remote-tracking branch 'origin/main' into fix/osspckgs-monitor
themarolt Jul 2, 2026
92bef2c
fix: show 'merging' phase step during osspckgs merge loop
themarolt Jul 3, 2026
1fb1932
fix: eliminate flicker in osspckgs monitor with buffered redraw
themarolt Jul 3, 2026
f1192cb
refactor: remove index drop/recreate from osspckgs ingest (CM-1296)
themarolt Jul 3, 2026
6311438
feat: add rubygems ecosystem to bq-dataset-ingest (CM-1296)
themarolt Jul 3, 2026
d9b3d38
Merge remote-tracking branch 'origin/main' into fix/osspckgs-monitor
themarolt Jul 3, 2026
18f79ef
Merge branch 'fix/osspckgs-monitor' into feat/add-rubygems-ingest-CM-…
themarolt Jul 3, 2026
dfafcbe
fix: restore merge dedup and relax deps resume guards (CM-1296)
themarolt Jul 3, 2026
a103b06
fix: resume deps ingest with original export settings (CM-1296)
themarolt Jul 3, 2026
ac7eee9
docs: fix stale comments after merge dedup + resume changes (CM-1296)
themarolt Jul 3, 2026
f41eb3a
Merge remote-tracking branch 'origin/main' into feat/add-rubygems-ing…
themarolt Jul 4, 2026
ec0e676
fix: stamp meta:fill on deps export reuse (CM-1296)
themarolt Jul 4, 2026
b92d4d0
docs: sync deps incremental byte-ceiling note for rubygems (CM-1296)
themarolt Jul 4, 2026
9065aa6
fix: clear stale meta:fill on non-fill deps export reuse (CM-1296)
themarolt Jul 4, 2026
3ade70a
fix: fail deps resume when parquet file count changed (CM-1296)
themarolt Jul 4, 2026
c805516
Merge remote-tracking branch 'origin/main' into feat/add-rubygems-ing…
themarolt Jul 5, 2026
359a4cb
fix: raise versions merge activity timeout to 4h (CM-1296)
themarolt Jul 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
findExportedJobByGcsPrefix,
findLatestExportedJobByKind,
markJobStatus,
mergeJobTableRowCounts,
} from '@crowd/data-access-layer'
import { getServiceChildLogger } from '@crowd/logging'

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

export interface BqExportToGcsOutput {
Expand All @@ -53,6 +60,7 @@ export async function bqExportToGcs(input: BqExportToGcsInput): Promise<BqExport
exportName,
ecosystems,
isScript,
isFill,
Comment thread
cursor[bot] marked this conversation as resolved.
} = input

// Named exports use a stable GCS path independent of runId so they survive across bootstrap runs.
Expand All @@ -79,6 +87,12 @@ export async function bqExportToGcs(input: BqExportToGcsInput): Promise<BqExport
{ jobKind, exportName, jobId: prior.id, gcsPrefix: prior.gcsPrefix },
'exportName match — skipping BQ, loading from named export',
)
// The reusing run drives the chunk merge on this same job row, so meta:fill must reflect
// THIS run's intent — write it both ways. Setting it (fill run) stops a later --resume-job
// reverting to ON CONFLICT DO NOTHING and skipping the version_constraint backfill; clearing
// it (non-fill run reusing a row an earlier fill run set) stops resume forcing an unintended
// upsert. Absent and 0 both read back as fill=false (COALESCE in getIngestJobForResume).
await mergeJobTableRowCounts(qx, prior.id, { 'meta:fill': isFill ? 1 : 0 })
return {
gcsPrefix: prior.gcsPrefix,
rowCount: prior.rowCountBq,
Expand Down Expand Up @@ -115,6 +129,7 @@ export async function bqExportToGcs(input: BqExportToGcsInput): Promise<BqExport
tableRowCounts: {
'bq:export': 0,
...(ecosystems ? { 'meta:ecosystems': ecosystems } : {}),
...(isFill ? { 'meta:fill': 1 } : {}),
},
})
return { gcsPrefix: namedPrefix, rowCount: 0, bqBytesBilled: 0, jobId }
Expand All @@ -135,6 +150,9 @@ export async function bqExportToGcs(input: BqExportToGcsInput): Promise<BqExport
{ jobKind, jobId: prior.id, gcsPrefix: prior.gcsPrefix },
'reuseExports=true — skipping BQ, loading from prior export',
)
// See named-export path above: write THIS run's fill intent both ways so a later
// --resume-job matches the most recent run instead of a stale meta value.
await mergeJobTableRowCounts(qx, prior.id, { 'meta:fill': isFill ? 1 : 0 })
return {
gcsPrefix: prior.gcsPrefix,
rowCount: prior.rowCountBq,
Expand Down Expand Up @@ -163,6 +181,9 @@ export async function bqExportToGcs(input: BqExportToGcsInput): Promise<BqExport
{ jobKind, jobId: existing.id, gcsPrefix },
'GCS files already exist — reusing export',
)
// Same-runId reuse (Temporal retry): re-stamp THIS run's fill intent both ways so the flag
// always matches the most recent run and can never be lost or left stale on reuse.
await mergeJobTableRowCounts(qx, existing.id, { 'meta:fill': isFill ? 1 : 0 })
return { gcsPrefix, rowCount: existing.rowCountBq, bqBytesBilled: 0, jobId: existing.id }
}
}
Expand Down Expand Up @@ -212,9 +233,13 @@ export async function bqExportToGcs(input: BqExportToGcsInput): Promise<BqExport
const provisionalDate = snapshotAt ? new Date(snapshotAt) : null
const jobId = await createIngestJob(qx, jobKind, syncMode, provisionalDate, exportName)

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

// From here the row is 'exporting'; any BQ failure (incl. script-mode maximumBytesBilled aborts)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { getPackagesDb } from '../../db'

const log = getServiceChildLogger('createVersionsLookup')

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

// Builds a persistent UNLOGGED lookup table in the staging schema for the root-version JOIN in
// ingestDependencies. Using a temp table per chunk would rebuild 4GB for every chunk on npm
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { getIngestJobForResume } from '@crowd/data-access-layer'

import { getPackagesDb } from '../../db'

export interface GetResumeExportInput {
jobId: number
}

export interface GetResumeExportOutput {
jobId: number
jobKind: string
status: string
syncMode: string
gcsPrefix: string | null
progressDone: number
progressTotal: number
rowCountPg: number
ecosystems: string[] | null
fill: boolean
}

// Pure fetch of a prior job's resume-relevant fields (export path, status, file-load progress,
// rows merged). Returns null if the job id does not exist. All validation — kind, status, presence
// of an export, and that the parquet files still exist — is done by the caller (ingestDependencies)
// so it can fail fast with non-retryable errors instead of retrying a bad-input activity.
export async function getResumeExport(
input: GetResumeExportInput,
): Promise<GetResumeExportOutput | null> {
const qx = await getPackagesDb()
const job = await getIngestJobForResume(qx, input.jobId)
if (!job) {
return null
}
return {
jobId: job.id,
jobKind: job.jobKind,
status: job.status,
syncMode: job.syncMode,
gcsPrefix: job.gcsPrefix,
progressDone: job.progressDone,
progressTotal: job.progressTotal,
rowCountPg: job.rowCountPg,
ecosystems: job.ecosystems,
fill: job.fill,
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
export * from './bqExportToGcs'
export * from './setJobStep'
export * from './createVersionsLookup'
export * from './managePackageDepsConstraints'
export * from './managePackageDepsIndexes'
export * from './manageVersionsConstraints'
export * from './manageVersionsIndexes'
export * from './listParquetFiles'
export * from './gcsParquetToStaging'
export * from './mergeStagingToTable'
export * from './getLastSnapshot'
export * from './getResumeExport'
export * from './checkDependentCountsGuard'
export * from './checkEdgeSnapshotQuality'
export * from './probePartitionExists'
Expand Down

This file was deleted.

Loading
Loading