0) Problem Restatement
Bloomberg asked: there's an API that answers "Is date D a holiday in market X?" (or "list holidays for market X in a range", or "what's the next business day?"). Today it calls a downstream service on every request, which is slow (tens to hundreds of ms) and sometimes unavailable. Many systems (trading, settlement date calculations) call it heavily. Make it much lower latency while keeping answers correct.
1) Key Observation
Holiday data is tiny and changes rarely: ~100 markets × ~15 holidays/year × a few years ≈ tens of thousands of dates, a few hundred KB in total. It changes a few times a year (an exchange announces a special closure). So there's no need to ask the downstream service per request. Load it all into memory.
2) Design
Architecture Diagram
flowchart LR
CL["Callers"] --> SVC["Holiday service - in-memory calendars"]
SVC -->|"lookup: hash set per market"| MEM["Memory: market to set of dates + version"]
SRC["Downstream holiday source"] -->|"periodic full load / change events"| LOAD["Loader"]
LOAD --> MEM
LOAD --> SNAP[("Local snapshot file - last good version")]- Preload: at startup, load the full calendars (e.g., next 5 years + last 5 years) into memory:
market → set of holiday dates, plus weekend rules per market (some markets have Friday–Saturday weekends). - Lookup = a hash set check, taking microseconds. "Next business day" = step forward until a date is neither a weekend nor in the set.
- Refresh: re-fetch the full data every few minutes (it's small), or subscribe to change events from the source. Swap atomically to the new version (build the new map, then replace the reference), so readers never see a half-updated calendar.
- Fallback: persist the last good snapshot to local disk. If the downstream is down at startup or refresh, keep serving the last good version and alert.
- Clients can embed it: for the lowest latency, ship a client library that keeps its own in-memory copy (with the same refresh logic), so callers don't even need a network hop.
3) Correctness and Freshness
- Every response can include the
calendar_versionandas_of. - Validation before swap: sanity checks on new data (no market suddenly missing, no absurd number of holidays). Reject bad updates and alert, rather than serving wrong calendars.
- Emergency closures (e.g., a market closed for an unexpected event): a push-based update path (change event → all instances refresh within seconds).
- Consistency across regions: all instances converge on the same version quickly. Monitor version skew.
4) If Data Were Large (general lesson)
For larger reference data, use a layered cache: an in-process LRU (fast) → a shared cache like Redis (shared warm data) → the source. Use TTLs plus explicit invalidation on change events, and request coalescing so a cache miss for a hot key triggers only one downstream call.
5) Wrap-Up
Holiday calendars are tiny and rarely change, so preload them fully into memory as per-market date sets (with weekend rules), answer lookups and next-business-day queries in microseconds, and refresh periodically or on change events with validation and atomic version swaps. Keep a local last-good snapshot for downstream outages, expose version metadata, and optionally embed the cache in a client library to remove the network hop entirely.