mirror of
https://github.com/koala73/worldmonitor.git
synced 2026-04-25 17:14:57 +02:00
fix(seed-bundle-resilience): drop Resilience-Scores interval 6h to 2h so refresh runs (#3126)
Live log 2026-04-16 09:25 showed the bundle runner SKIPPING Resilience-Scores (last seeded 203min ago, interval 360min -> 288min skip threshold). Every Railway cron fire within the 4.8h skip window bypassed the section entirely, so refreshRankingAggregate() -- the whole point of the Slice B work merged in #3124 -- never ran. Ranking could then silently expire in the gap. Lower intervalMs to 2h. The bundle runner skip threshold becomes 96min; hourly Railway fires run the section about every 2h. Well within the 12h ranking TTL, and cheap per warm-path run: - computeAndWriteIntervals (~100ms local CPU + one pipeline write) - refreshRankingAggregate -> /api/resilience/v1/get-resilience-ranking?refresh=1 (handler recompute + 2-SET pipeline, ~2-5s) - STRLEN + GET-meta verify in parallel (~200ms) Total ~5-10s per warm-scores run. The expensive 222-country warm still only runs when scores are actually missing. Structural test pins intervalMs <= 2 hours so this doesn't silently regress. Full resilience suite: 378/378.
This commit is contained in:
@@ -1,7 +1,14 @@
|
||||
#!/usr/bin/env node
|
||||
import { runBundle, HOUR, DAY } from './_bundle-runner.mjs';
|
||||
|
||||
// intervalMs note: the bundle runner skips sections whose seed-meta is newer
|
||||
// than `intervalMs * 0.8`. The Resilience-Scores section must run more often
|
||||
// than the ranking/score TTL (12h / 6h) so refreshRankingAggregate() can keep
|
||||
// the ranking alive between Railway cron fires. A 2h interval → 96min skip
|
||||
// window, so hourly Railway fires run this ~every 2h. The seeder is cheap on
|
||||
// warm runs (~5-10s: intervals recompute + one /refresh=1 HTTP + 2 verify
|
||||
// GETs); the expensive warm path only runs when scores are actually missing.
|
||||
await runBundle('resilience', [
|
||||
{ label: 'Resilience-Scores', script: 'seed-resilience-scores.mjs', seedMetaKey: 'resilience:intervals', intervalMs: 6 * HOUR, timeoutMs: 600_000 },
|
||||
{ label: 'Resilience-Scores', script: 'seed-resilience-scores.mjs', seedMetaKey: 'resilience:intervals', intervalMs: 2 * HOUR, timeoutMs: 600_000 },
|
||||
{ label: 'Resilience-Static', script: 'seed-resilience-static.mjs', seedMetaKey: 'resilience:static', intervalMs: 90 * DAY, timeoutMs: 900_000 },
|
||||
]);
|
||||
|
||||
@@ -217,6 +217,33 @@ describe('ensures ranking aggregate is present every cron, with truthful meta',
|
||||
});
|
||||
});
|
||||
|
||||
describe('seed-bundle-resilience section interval keeps refresh alive', () => {
|
||||
// The bundle runner skips a section when its seed-meta is younger than
|
||||
// intervalMs * 0.8. If intervalMs is too long (e.g. 6h), most Railway cron
|
||||
// fires hit the skip branch → refreshRankingAggregate() never runs →
|
||||
// ranking can expire between actual runs and create EMPTY_ON_DEMAND gaps.
|
||||
// 2h is the tested trade-off: frequent enough for the 12h ranking TTL to
|
||||
// stay well-refreshed, cheap enough per warm-path run (~5-10s).
|
||||
it('Resilience-Scores section has intervalMs ≤ 2 hours', async () => {
|
||||
const { readFileSync } = await import('node:fs');
|
||||
const { fileURLToPath } = await import('node:url');
|
||||
const { dirname, join } = await import('node:path');
|
||||
const dir = dirname(fileURLToPath(import.meta.url));
|
||||
const src = readFileSync(
|
||||
join(dir, '..', 'scripts', 'seed-bundle-resilience.mjs'),
|
||||
'utf8',
|
||||
);
|
||||
// Match the label + section line, then extract the intervalMs value.
|
||||
const m = src.match(/label:\s*'Resilience-Scores'[\s\S]{0,400}?intervalMs:\s*(\d+)\s*\*\s*HOUR/);
|
||||
assert.ok(m, 'Resilience-Scores section must set intervalMs in HOUR units');
|
||||
const hours = Number(m[1]);
|
||||
assert.ok(
|
||||
hours > 0 && hours <= 2,
|
||||
`intervalMs must be ≤ 2 hours (found ${hours}) so refreshRankingAggregate runs frequently enough to keep the ranking key alive before its 12h TTL`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('handler warm pipeline is chunked', () => {
|
||||
// The 222-country pipeline SET payload (~600KB) exceeds the 5s pipeline
|
||||
// timeout on Vercel Edge → handler reports 0 persisted, ranking skipped.
|
||||
|
||||
Reference in New Issue
Block a user