Skip to content

Commit c0b6d2b

Browse files
authored
feat: add packages ranking workflow and dependent_counts row-count guard (CM-1222) (#4217)
Signed-off-by: Uroš Marolt <uros@marolt.me>
1 parent 7e6243b commit c0b6d2b

16 files changed

Lines changed: 233 additions & 30 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-- Drop CHECK constraints on job_kind and sync_mode so new kinds can be added
2+
-- without a migration every time.
3+
ALTER TABLE osspckgs_ingest_jobs
4+
DROP CONSTRAINT osspckgs_ingest_jobs_job_kind_check,
5+
DROP CONSTRAINT osspckgs_ingest_jobs_sync_mode_check;

pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

scripts/cli

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -218,12 +218,18 @@ function multiselect {
218218
SELECTED_SERVICES=()
219219

220220
function select_services() {
221-
# Get all services
221+
# Get all deployable services from builder definitions
222222
SERVICES=()
223-
while IFS= read -r file; do
224-
filename=$(basename "$file" .yaml)
225-
SERVICES+=("$filename")
226-
done < <(find "$CLI_HOME/services" -name '*.yaml')
223+
for env_file in "$CLI_HOME/builders/"*.env; do
224+
services_line=$(grep "^SERVICES=" "$env_file" 2>/dev/null || true)
225+
if [[ -n "$services_line" ]]; then
226+
services_val="${services_line#SERVICES=}"
227+
services_val="${services_val//\"/}"
228+
for svc in $services_val; do
229+
SERVICES+=("$svc")
230+
done
231+
fi
232+
done
227233

228234
# Use fzf if available, fall back to built-in multiselect
229235
if command -v fzf &>/dev/null; then
@@ -251,7 +257,7 @@ function select_services() {
251257

252258
function deploy_staging() {
253259
REPOSITORY="CrowdDotDev/crowd.dev"
254-
WORKFLOW_FILE="lf-staging-deploy.yaml"
260+
WORKFLOW_FILE="lf-oracle-staging-deploy.yaml"
255261
CURRENT_BRANCH=$(git branch --show-current)
256262
SERVICES="$(
257263
IFS=" "
@@ -263,7 +269,7 @@ function deploy_staging() {
263269

264270
function deploy_production() {
265271
REPOSITORY="CrowdDotDev/crowd.dev"
266-
WORKFLOW_FILE="lf-production-deploy.yaml"
272+
WORKFLOW_FILE="lf-oracle-production-deploy.yaml"
267273
CURRENT_BRANCH=$(git branch --show-current)
268274
SERVICES="$(
269275
IFS=" "

services/apps/packages_worker/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"@crowd/data-access-layer": "workspace:*",
5151
"@crowd/database": "workspace:*",
5252
"@crowd/logging": "workspace:*",
53+
"@crowd/slack": "workspace:*",
5354
"@dsnp/parquetjs": "^1.7.0",
5455
"@google-cloud/bigquery": "^8.3.1",
5556
"@google-cloud/storage": "7.19.0",

services/apps/packages_worker/src/activities.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ export {
1313
export * from './deps-dev/activities'
1414
export { osvSyncEcosystem, osvDeriveCriticalFlag } from './osv/activities'
1515
export { processMavenCriticalBatch, processMavenNonCriticalBatch } from './maven/activities'
16+
export { criticalityComputePageRank, rankPackages } from './criticality/activities'

services/apps/packages_worker/src/criticality/activities.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Context } from '@temporalio/activity'
22

3+
import { createIngestJob, markJobStatus } from '@crowd/data-access-layer'
34
import { getServiceChildLogger } from '@crowd/logging'
45

56
import { getPackagesDb } from '../db'
@@ -63,3 +64,34 @@ export async function criticalityComputePageRank(
6364

6465
return { ecosystem, nodeCount: graph.N, edgeCount, iterations, durationMs: Date.now() - start }
6566
}
67+
68+
export async function rankPackages(): Promise<{ scoredRows: number; rankedRows: number }> {
69+
const qx = await getPackagesDb()
70+
71+
// On retry, a pending row from the prior attempt may already exist — reuse it.
72+
// Do NOT reuse a done row: it belongs to a previous bootstrap run and ranking must re-execute.
73+
const existing = await qx.selectOneOrNone(
74+
`SELECT id FROM osspckgs_ingest_jobs
75+
WHERE job_kind = 'ranking' AND status = 'pending'
76+
ORDER BY id DESC LIMIT 1`,
77+
)
78+
79+
const jobId = existing?.id ?? (await createIngestJob(qx, 'ranking', 'ranking', null))
80+
try {
81+
const [result] = await qx.select(`SELECT * FROM rank_packages()`)
82+
const scoredRows = Number(result.scored_rows ?? 0)
83+
const rankedRows = Number(result.ranked_rows ?? 0)
84+
await markJobStatus(qx, jobId, 'done', {
85+
rowCountPg: scoredRows,
86+
tableRowCounts: { scored: scoredRows, ranked: rankedRows },
87+
finishedAt: new Date(),
88+
})
89+
return { scoredRows, rankedRows }
90+
} catch (err) {
91+
await markJobStatus(qx, jobId, 'failed', {
92+
errorMessage: (err as Error).message,
93+
finishedAt: new Date(),
94+
})
95+
throw err
96+
}
97+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { proxyActivities } from '@temporalio/workflow'
2+
3+
import type * as critActivities from './activities'
4+
5+
const { rankPackages } = proxyActivities<typeof critActivities>({
6+
startToCloseTimeout: '30 minutes',
7+
retry: { maximumAttempts: 2 },
8+
})
9+
10+
export async function rankPackagesWorkflow(): Promise<void> {
11+
await rankPackages()
12+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { getLastCompletedJobRowCount as dalGetLastCompletedJobRowCount } from '@crowd/data-access-layer'
2+
import { SlackChannel, SlackPersona, sendSlackNotification } from '@crowd/slack'
3+
4+
import { getPackagesDb } from '../../db'
5+
6+
export interface CheckDependentCountsGuardInput {
7+
currentRowCount: number
8+
snapshotDate: string
9+
}
10+
11+
export interface CheckDependentCountsGuardOutput {
12+
ok: boolean
13+
prevRowCount: number | null
14+
dropPct: number | null
15+
}
16+
17+
const DROP_THRESHOLD = 0.05
18+
19+
export async function checkDependentCountsGuard(
20+
input: CheckDependentCountsGuardInput,
21+
): Promise<CheckDependentCountsGuardOutput> {
22+
const qx = await getPackagesDb()
23+
const prevRowCount = await dalGetLastCompletedJobRowCount(qx, 'dependent_counts')
24+
25+
if (prevRowCount === null || prevRowCount <= 0) {
26+
return { ok: true, prevRowCount, dropPct: null }
27+
}
28+
29+
const dropPct = (prevRowCount - input.currentRowCount) / prevRowCount
30+
31+
if (dropPct >= DROP_THRESHOLD) {
32+
sendSlackNotification(
33+
SlackChannel.CDP_CRITICAL_ALERTS,
34+
SlackPersona.CRITICAL_ALERTER,
35+
':warning: dependent_counts row count anomaly detected',
36+
[
37+
{
38+
title: 'Snapshot',
39+
text: input.snapshotDate,
40+
},
41+
{
42+
title: 'Row count',
43+
text: `${input.currentRowCount.toLocaleString()} (prev max: ${prevRowCount.toLocaleString()}, drop: ${(dropPct * 100).toFixed(1)}%)`,
44+
},
45+
{
46+
title: 'Action',
47+
text: 'Ingest aborted — existing `dependent_count` values preserved. Check deps.dev `Dependents` table for this snapshot date.',
48+
},
49+
],
50+
)
51+
return { ok: false, prevRowCount, dropPct }
52+
}
53+
54+
return { ok: true, prevRowCount, dropPct }
55+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ export * from './listParquetFiles'
88
export * from './gcsParquetToStaging'
99
export * from './mergeStagingToTable'
1010
export * from './getLastSnapshot'
11+
export * from './checkDependentCountsGuard'
1112
export * from './probePartitionExists'
1213
export * from './resolveSnapshotDate'

services/apps/packages_worker/src/deps-dev/queries/dependentCountsSql.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ WITH purl_map AS (
1414
)
1515
SELECT
1616
pm.purl AS purl,
17-
COUNT(DISTINCT IF(d.MinimumDepth = 1, CONCAT(d.Dependent.System, ':', d.Dependent.Name), NULL)) AS dependent_count,
18-
COUNT(DISTINCT IF(d.MinimumDepth > 1, CONCAT(d.Dependent.System, ':', d.Dependent.Name), NULL)) AS transitive_dependent_count,
17+
COUNT(DISTINCT IF(d.MinimumDepth = 1 AND d.Dependent.Name NOT LIKE '%>%', CONCAT(d.Dependent.System, ':', d.Dependent.Name), NULL)) AS dependent_count,
18+
COUNT(DISTINCT IF(d.MinimumDepth > 1 AND d.Dependent.Name NOT LIKE '%>%', CONCAT(d.Dependent.System, ':', d.Dependent.Name), NULL)) AS transitive_dependent_count,
1919
COUNT(DISTINCT pvp.ProjectName) AS dependent_repos_count
2020
FROM \`bigquery-public-data.deps_dev_v1.Dependents\` d
2121
JOIN purl_map pm ON pm.System = d.System AND pm.Name = d.Name

0 commit comments

Comments
 (0)