Deduplicate and Order Batch and Streaming Logs
GoogleSoftware Engineer · OnsiteLast updated
Design both a batch algorithm and a streaming processor to deduplicate and chronologically order log records under different resolution policies, accounting for out-of-order delivery, late data, and bounded memory.
Problem Statement
You are tasked with building a log processing component used across both batch ingestion and real-time streaming pipelines.
Each log entry is represented as:
class LogEntry:
timestamp: int # Unix timestamp in milliseconds
event_id: str # Unique identifier of the underlying event
payload: str # Content / message
Duplicate entries can arise due to network retries and distributed producer retries. Duplicates share the same event_id, but may have differing payloads or timestamps.
Part 1: Batch Ingestion
Implement a function deduplicate_batch(logs: list[LogEntry], policy: str) -> list[LogEntry]:
policycan be either"KEEP_FIRST"or"KEEP_LATEST".- Under
"KEEP_FIRST", keep the record with the smallesttimestamp. If there is a tie, keep the one that appeared first in the input list. - Under
"KEEP_LATEST", keep the record with the largesttimestamp. If there is a tie, keep the one that appeared latest in the input list. - The returned list must be sorted in ascending order of
timestamp. If two deduplicated records have the exact sametimestamp, break ties lexicographically byevent_id.
Part 2: Streaming Processor
Implement a streaming class StreamDeduplicator:
__init__(self, policy: str, allowed_lateness_ms: int): Initializes the processor.allowed_lateness_msdefines the watermark delay. A record arriving withtimestamp None: Ingests an incoming log record.advance_watermark(self, current_time_ms: int) -> list[LogEntry]: Advances the processor time tocurrent_time_ms. Any events whose timestamp is now strictly older thancurrent_time_ms - allowed_lateness_msare finalized, emitted in ascending timestamp order (ties broken byevent_id), and evicted from internal memory to prevent unbounded growth.
Example
# Batch Example
logs = [
LogEntry(100, "A", "v1"),
LogEntry(150, "B", "v1"),
LogEntry(90, "A", "v2"),
LogEntry(150, "C", "v1"),
]
# Under KEEP_FIRST: 'A' keeps timestamp 90 ("v2")
# Result: [LogEntry(90, "A", "v2"), LogEntry(150, "B", "v1"), LogEntry(150, "C", "v1")]
# Under KEEP_LATEST: 'A' keeps timestamp 100 ("v1")
# Result: [LogEntry(100, "A", "v1"), LogEntry(150, "B", "v1"), LogEntry(150, "C", "v1")]
Constraints
- For batch:
0 <= len(logs) <= 10^5. timestampis a non-negative 64-bit integer.event_idis a non-empty string of length up to 64 ASCII characters.- Streaming state must evict emitted entries completely to maintain
O(W)space complexity, whereWis the number of active entries within the watermark window.
Hints
Hint 1
For the batch phase, iterate through the logs while maintaining a hash map from event_id to the chosen LogEntry along with its original index to handle tie-breaking.
Hint 2
For the streaming phase, separate the active window buffer from the finalized output using a min-heap or balanced tree ordered by timestamp alongside an event_id index.
Hint 3
Be careful with post-finalization duplicates in streaming: decide whether an event arriving after its window has finalized should be dropped or treated as a new cycle if tombstones are bounded.
Follow-up questions
- How would you handle duplicate detection if an identical event arrives after its window has been evicted and finalized?
- How can you scale this architecture horizontally across multiple partition workers while guaranteeing strict chronological order per consumer partition?
- If storage memory is extremely constrained, how could you use Bloom filters or a two-tiered cache with disk spill to manage high-cardinality event IDs?
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.