Files
worldmonitor/server/_shared/cache-keys.ts
Elie Habib cd7d3b7501 perf(baseline): move temporal baseline for news+fires to server-side (#1194)
* perf(baseline): move temporal baseline for news+fires to server-side

Every browser client was calling record-baseline-snapshot (POST) +
get-temporal-baseline (GET) on every data refresh from 5 call sites.
With N concurrent users this created N identical writes and ~5N reads
per cycle — causing 429 rate limiting and statistically biased baselines.

Phase 1 moves news and satellite_fires to server-side computation:
- New ListTemporalAnomalies RPC reads counts from existing Redis keys
  (news:insights:v1, wildfire:fires:v1), computes anomalies against v2
  baselines, applies Welford update (1 sample/cycle), caches 15min
- Bootstrap hydration delivers anomalies on page load (zero extra calls)
- Client refreshes via RPC every 10min (1 cached call vs 5N before)
- Remaining 3 types (military_flights, vessels, ais_gaps) stay client-side
- Owner-guarded distributed lock prevents concurrent computation
- All reads/writes use prefix-aware getCachedJson/setCachedJson

Expected ~60% reduction in baseline-related Vercel invocations.

* fix(temporal): per-invocation lock owner and immediate refresh on cold cache

P1: When bootstrap has no temporal anomaly data (cold cache, expired
snapshot, fresh deploy), fire refreshTemporalBaseline() immediately
instead of waiting up to 10 minutes for the scheduled refresh.

P2: Generate lockOwner per invocation via makeLockOwner() instead of
once at module load. Prevents warm edge isolates from cross-releasing
each other's locks when one invocation outlives the 30s TTL.

* fix(temporal): use TTL-only lock instead of TOCTOU GET-then-DEL release

The non-atomic GET→check-owner→DEL release was vulnerable to a race
where the TTL expires between GET and DEL, allowing a new lock holder
to be evicted. Simplify by relying solely on the 30s TTL for lock
expiry — the computation completes well within that window.
2026-03-07 16:15:43 +04:00

58 lines
2.6 KiB
TypeScript

/**
* Static cache keys for the bootstrap endpoint.
* Only keys with NO request-varying suffixes are included.
*/
export const BOOTSTRAP_CACHE_KEYS: Record<string, string> = {
earthquakes: 'seismology:earthquakes:v1',
outages: 'infra:outages:v1',
serviceStatuses: 'infra:service-statuses:v1',
sectors: 'market:sectors:v1',
etfFlows: 'market:etf-flows:v1',
macroSignals: 'economic:macro-signals:v1',
bisPolicy: 'economic:bis:policy:v1',
bisExchange: 'economic:bis:eer:v1',
bisCredit: 'economic:bis:credit:v1',
shippingRates: 'supply_chain:shipping:v2',
chokepoints: 'supply_chain:chokepoints:v2',
minerals: 'supply_chain:minerals:v2',
giving: 'giving:summary:v1',
climateAnomalies: 'climate:anomalies:v1',
wildfires: 'wildfire:fires:v1',
marketQuotes: 'market:stocks-bootstrap:v1',
commodityQuotes: 'market:commodities-bootstrap:v1',
cyberThreats: 'cyber:threats-bootstrap:v2',
techReadiness: 'economic:worldbank-techreadiness:v1',
progressData: 'economic:worldbank-progress:v1',
renewableEnergy: 'economic:worldbank-renewable:v1',
positiveGeoEvents: 'positive-events:geo-bootstrap:v1',
theaterPosture: 'theater-posture:sebuf:stale:v1',
riskScores: 'risk:scores:sebuf:stale:v1',
naturalEvents: 'natural:events:v1',
flightDelays: 'aviation:delays-bootstrap:v1',
insights: 'news:insights:v1',
predictions: 'prediction:markets-bootstrap:v1',
cryptoQuotes: 'market:crypto:v1',
gulfQuotes: 'market:gulf-quotes:v1',
stablecoinMarkets: 'market:stablecoins:v1',
unrestEvents: 'unrest:events:v1',
iranEvents: 'conflict:iran-events:v1',
ucdpEvents: 'conflict:ucdp-events:v1',
temporalAnomalies: 'temporal:anomalies:v1',
};
export const BOOTSTRAP_TIERS: Record<string, 'slow' | 'fast'> = {
bisPolicy: 'slow', bisExchange: 'slow', bisCredit: 'slow',
minerals: 'slow', giving: 'slow', sectors: 'slow',
progressData: 'slow', renewableEnergy: 'slow',
etfFlows: 'slow', shippingRates: 'slow', wildfires: 'slow',
climateAnomalies: 'slow', cyberThreats: 'slow', techReadiness: 'slow',
theaterPosture: 'slow', naturalEvents: 'slow',
cryptoQuotes: 'slow', gulfQuotes: 'slow', stablecoinMarkets: 'slow',
unrestEvents: 'slow', ucdpEvents: 'slow',
earthquakes: 'fast', outages: 'fast', serviceStatuses: 'fast',
macroSignals: 'fast', chokepoints: 'fast', riskScores: 'fast',
marketQuotes: 'fast', commodityQuotes: 'fast', positiveGeoEvents: 'fast',
flightDelays: 'fast', insights: 'fast', predictions: 'fast',
iranEvents: 'fast', temporalAnomalies: 'fast',
};