Design a Ten-Minute In-Memory Notification Deduplication Engine
Goldman SachsSoftware Engineer · OnsiteLast updated
Design a high-throughput, low-latency in-memory deduplication service that identifies and suppresses duplicate notifications within a 10-minute sliding window.
Scenario
Financial platforms emit high volumes of trade alerts, market events, and compliance triggers. Due to retries, network glitches, and at-least-once message delivery from upstream publishers, identical notifications are frequently emitted in bursts.
Design an in-memory notification deduplication engine that receives incoming notification events and determines whether a notification has already been processed within the last 10 minutes.
Requirements
Functional Requirements
- Deduplication Check & Record: Given a unique deduplication key (e.g., derived from user ID, event type, and payload hash), the engine must return whether the event is
NEWor aDUPLICATE. - 10-Minute Window Policy: The deduplication window is 10 minutes. Clarify and support policies for:
- Fixed window from first seen: The key expires strictly 10 minutes after initial observation.
- Sliding refresh: Every incoming duplicate resets the 10-minute retention timer.
- Cleanup: Expired keys must be purged predictably without triggering memory exhaustion or latency spikes.
Non-Functional Requirements
- Latency: p99 latency under 2 ms.
- Throughput: Handle up to 100,000 notification checks per second at peak.
- Concurrency: Thread-safe operations without lock contention bottlenecks under multi-core environments.
- Memory Footprint: Predictable and bounded memory usage suitable for in-memory operation.
- Clock Handling: Resilient to wall-clock skew and NTP synchronization adjustments.
Scale Assumptions
- Peak ingestion rate: 100,000 events/second.
- Window duration: 10 minutes (600 seconds).
- Unique keys in memory at peak: Up to 60,000,000 active keys.
- Key size: 128-bit hash (16 bytes) or UUID representation.
Key Discussion Points
- Data Structure Selection: Trade-offs between concurrent hash maps with TTLs, segmented circular time-buckets, and linked hash maps.
- Cleanup Strategy: Proactive background eviction vs. passive eviction on read vs. atomic rotation of time-sliced buckets.
- Clock Source: Local monotonic clock (
clock_gettime(CLOCK_MONOTONIC)) vs. event payload timestamps. - Partitioning & Sharding: Key-based partitioning (consistent hashing) across CPU cores or instances to eliminate cross-thread lock contention.
- High Availability & Fault Tolerance: Trade-offs between pure volatile in-memory operation and append-only write-ahead logging (WAL) or standby replicas.
Hints
Hint 1
Consider bucketing keys into discrete time slices (such as 10-second or 1-minute rings) to make cleanup an O(1) pointer advance rather than managing millions of individual key timers.
Hint 2
Decide whether duplicate arrivals refresh the 10-minute expiration or expire strictly based on the first arrival, as this fundamentally shifts data structure choice.
Hint 3
Use a monotonic clock source for expiration logic to avoid errors caused by NTP adjustments or system clock jumps.
Follow-up questions
- How would your data structure and eviction strategy change if memory was restricted to 500 MB, forcing you to tolerate a low probability of false positives?
- If the deduplication engine must survive process crashes without duplicate notifications leaking through, how would you design persistence with minimal latency penalty?
- How would you scale this design to a distributed, multi-node cluster if total volume increases to 1,000,000 events per second?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.