Skip to content

perf(edge): reduce unnecessary Vercel edge invocations#1176

Merged
koala73 merged 3 commits into
mainfrom
review/edge-traffic-audit
Mar 7, 2026
Merged

perf(edge): reduce unnecessary Vercel edge invocations#1176
koala73 merged 3 commits into
mainfrom
review/edge-traffic-audit

Conversation

@koala73

@koala73 koala73 commented Mar 7, 2026

Copy link
Copy Markdown
Owner

Summary

  • Fix 1: Remove predictions double getHydratedData read — fetchPredictions() handles hydration internally (saves 1 edge call)
  • Fix 2: Add GetHumanitarianSummaryBatch RPC — replaces 20-request HAPI fanout with single batch call (saves 19 edge calls on cold miss)
  • Fix 3: Add GetFredSeriesBatch RPC — replaces 7-request FRED fanout with single batch call (saves 6 edge calls on cold miss)
  • Fix 4: Fix UCDP delete-on-read race — read hydratedUcdp once in data-loader, pass to both consumers (saves 1 edge call)
  • Fix 5: Add service statuses seed script + AIS relay warm-ping loop (15 min interval, TTL/2) — prevents cold-miss explosion of ~30 upstream checks
  • Fix 6: Two bootstrap edge calls — accepted as trade-off (per-tier cache freshness + fault isolation)

Net savings: up to 28 edge calls eliminated on cold miss per page load.

Batch RPCs

Both batch handlers use getCachedJsonBatch() for Redis pipeline lookups + cachedFetchJson() for cache misses with 100ms inter-request delays. Deploy-skew 404 fallback to per-item calls ensures backward compatibility during rollout.

Test plan

  • tsc --noEmit passes (verified)
  • Edge function tests pass (verified via pre-push hook)
  • Predictions render from bootstrap without hitting prediction RPC
  • UCDP classifications AND events both render from single getHydratedData read
  • Test batch RPCs: curl -X POST api.worldmonitor.app/api/conflict/v1/get-humanitarian-summary-batch -d '{"countryCodes":["AF","IQ","SY"]}'
  • Test batch RPCs: curl -X POST api.worldmonitor.app/api/economic/v1/get-fred-series-batch -d '{"seriesIds":["WALCL","FEDFUNDS","T10Y2Y"],"limit":10}'
  • Verify batch 404-fallback works (returns same data as individual calls)
  • Monitor Vercel edge function invocation count post-deploy

Phase 1 — Client-only fixes:
- Remove predictions double getHydratedData read from data-loader.ts;
  fetchPredictions() handles hydration internally
- Fix UCDP delete-on-read race: read hydratedUcdp once in data-loader,
  pass to both fetchUcdpClassifications() and fetchUcdpEvents()

Phase 2 — Batch RPCs (proto + server + client):
- Add GetHumanitarianSummaryBatch RPC: replaces 20-request HAPI fanout
  with single batch call (getCachedJsonBatch + per-key Redis caching)
- Add GetFredSeriesBatch RPC: replaces 7-request FRED fanout with
  single batch call (same pattern)
- Both batch RPCs have 404 deploy-skew fallback to per-item calls

Phase 3 — Seed gap:
- Add seed-service-statuses.mjs standalone seed script
- Add 15-min warm-ping loop in AIS relay for service statuses
- Remove serviceStatuses from ON_DEMAND_KEYS in health.js

Net savings: up to 28 edge calls eliminated on cold miss per page load.
@vercel

vercel Bot commented Mar 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
worldmonitor Ready Ready Preview, Comment Mar 7, 2026 8:26am

Request Review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9c2281ee90

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/services/conflict/index.ts Outdated
Comment on lines +295 to +299
const resp = await hapiBatchBreaker.execute(async () => {
return client.getHumanitarianSummaryBatch(
{ countryCodes: [...HAPI_COUNTRY_CODES] },
{ signal: AbortSignal.timeout(30_000) },
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve 404 deploy-skew fallback for HAPI batch calls

This hapiBatchBreaker.execute(...) call cannot trigger the catch fallback below, because CircuitBreaker.execute swallows thrown errors and returns the provided default value instead of rethrowing (see src/utils/circuit-breaker.ts). In the rollout-skew case where get-humanitarian-summary-batch still returns 404, this path now yields an empty batch response and skips the existing per-country fallback RPCs, causing humanitarian summaries to disappear until all backends are updated.

Useful? React with 👍 / 👎.

Comment thread src/services/economic/index.ts Outdated
Comment on lines +157 to +161
const resp = await fredBatchBreaker.execute(async () => {
return client.getFredSeriesBatch(
{ seriesIds: FRED_SERIES.map((c) => c.id), limit: 120 },
{ signal: AbortSignal.timeout(30_000) },
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve 404 deploy-skew fallback for FRED batch calls

The fredBatchBreaker.execute(...) invocation also prevents the catch-based 404 fallback from ever running, because breaker failures are converted into emptyFredBatchFallback rather than thrown. When clients hit an older backend that lacks /get-fred-series-batch, this code will return no FRED series instead of falling back to the existing per-series getFredSeries calls, so macro data can go blank during deployment skew.

Useful? React with 👍 / 👎.

P1: Fix dead 404 deploy-skew fallback — circuit breaker was swallowing
the ApiError before the catch block could detect it. Move 404 fallback
inside the breaker callback so it executes before the breaker catches.

P2: Replace 172-line seed-service-statuses.mjs (duplicated parser logic)
with a 60-line warm-ping that triggers the existing RPC handler.

P2: Extract shared ISO2_TO_ISO3 mapping to conflict/v1/_shared.ts,
eliminating duplication between single and batch HAPI handlers.

P3: Remove unnecessary UPSTASH_ENABLED guard from relay warm-ping
(it calls Vercel RPC, not Redis directly).

P3: Clean up unused per-series FRED breakers and fetchSingleFredSeries
(replaced by batch breaker). Update getFredStatus() accordingly.
HAPI batch: replace serial loop with groups of 5 concurrent fetches
using Promise.allSettled for partial-success resilience. Bump client
timeout to 60s (4 rounds × 15s upstream timeout worst case).

FRED batch: replace serial loop with fully parallel Promise.allSettled
(max 10 series, each hits separate FRED endpoint).

Both changes prevent empty-result regression on cold cache that the
serial approach caused when upstream latency exceeded the client timeout.
@koala73
koala73 merged commit 327f499 into main Mar 7, 2026
3 checks passed
@koala73
koala73 deleted the review/edge-traffic-audit branch March 8, 2026 19:32
aldoyh pushed a commit to aldoyh/worldmonitor that referenced this pull request Mar 15, 2026
* perf(edge): reduce unnecessary Vercel edge invocations (koala73#6 findings)

Phase 1 — Client-only fixes:
- Remove predictions double getHydratedData read from data-loader.ts;
  fetchPredictions() handles hydration internally
- Fix UCDP delete-on-read race: read hydratedUcdp once in data-loader,
  pass to both fetchUcdpClassifications() and fetchUcdpEvents()

Phase 2 — Batch RPCs (proto + server + client):
- Add GetHumanitarianSummaryBatch RPC: replaces 20-request HAPI fanout
  with single batch call (getCachedJsonBatch + per-key Redis caching)
- Add GetFredSeriesBatch RPC: replaces 7-request FRED fanout with
  single batch call (same pattern)
- Both batch RPCs have 404 deploy-skew fallback to per-item calls

Phase 3 — Seed gap:
- Add seed-service-statuses.mjs standalone seed script
- Add 15-min warm-ping loop in AIS relay for service statuses
- Remove serviceStatuses from ON_DEMAND_KEYS in health.js

Net savings: up to 28 edge calls eliminated on cold miss per page load.

* fix(edge): address code review findings (P1–P3)

P1: Fix dead 404 deploy-skew fallback — circuit breaker was swallowing
the ApiError before the catch block could detect it. Move 404 fallback
inside the breaker callback so it executes before the breaker catches.

P2: Replace 172-line seed-service-statuses.mjs (duplicated parser logic)
with a 60-line warm-ping that triggers the existing RPC handler.

P2: Extract shared ISO2_TO_ISO3 mapping to conflict/v1/_shared.ts,
eliminating duplication between single and batch HAPI handlers.

P3: Remove unnecessary UPSTASH_ENABLED guard from relay warm-ping
(it calls Vercel RPC, not Redis directly).

P3: Clean up unused per-series FRED breakers and fetchSingleFredSeries
(replaced by batch breaker). Update getFredStatus() accordingly.

* fix(edge): use concurrent fetches in batch handlers

HAPI batch: replace serial loop with groups of 5 concurrent fetches
using Promise.allSettled for partial-success resilience. Bump client
timeout to 60s (4 rounds × 15s upstream timeout worst case).

FRED batch: replace serial loop with fully parallel Promise.allSettled
(max 10 series, each hits separate FRED endpoint).

Both changes prevent empty-result regression on cold cache that the
serial approach caused when upstream latency exceeded the client timeout.
nichm pushed a commit to nichm/worldmonitor-private that referenced this pull request Jul 1, 2026
* perf(edge): reduce unnecessary Vercel edge invocations (koala73#6 findings)

Phase 1 — Client-only fixes:
- Remove predictions double getHydratedData read from data-loader.ts;
  fetchPredictions() handles hydration internally
- Fix UCDP delete-on-read race: read hydratedUcdp once in data-loader,
  pass to both fetchUcdpClassifications() and fetchUcdpEvents()

Phase 2 — Batch RPCs (proto + server + client):
- Add GetHumanitarianSummaryBatch RPC: replaces 20-request HAPI fanout
  with single batch call (getCachedJsonBatch + per-key Redis caching)
- Add GetFredSeriesBatch RPC: replaces 7-request FRED fanout with
  single batch call (same pattern)
- Both batch RPCs have 404 deploy-skew fallback to per-item calls

Phase 3 — Seed gap:
- Add seed-service-statuses.mjs standalone seed script
- Add 15-min warm-ping loop in AIS relay for service statuses
- Remove serviceStatuses from ON_DEMAND_KEYS in health.js

Net savings: up to 28 edge calls eliminated on cold miss per page load.

* fix(edge): address code review findings (P1–P3)

P1: Fix dead 404 deploy-skew fallback — circuit breaker was swallowing
the ApiError before the catch block could detect it. Move 404 fallback
inside the breaker callback so it executes before the breaker catches.

P2: Replace 172-line seed-service-statuses.mjs (duplicated parser logic)
with a 60-line warm-ping that triggers the existing RPC handler.

P2: Extract shared ISO2_TO_ISO3 mapping to conflict/v1/_shared.ts,
eliminating duplication between single and batch HAPI handlers.

P3: Remove unnecessary UPSTASH_ENABLED guard from relay warm-ping
(it calls Vercel RPC, not Redis directly).

P3: Clean up unused per-series FRED breakers and fetchSingleFredSeries
(replaced by batch breaker). Update getFredStatus() accordingly.

* fix(edge): use concurrent fetches in batch handlers

HAPI batch: replace serial loop with groups of 5 concurrent fetches
using Promise.allSettled for partial-success resilience. Bump client
timeout to 60s (4 rounds × 15s upstream timeout worst case).

FRED batch: replace serial loop with fully parallel Promise.allSettled
(max 10 series, each hits separate FRED endpoint).

Both changes prevent empty-result regression on cold cache that the
serial approach caused when upstream latency exceeded the client timeout.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant