Idempotent Consumer: Store Processed IDs in Python
Implement an idempotent consumer that persists processed message IDs to a JSON file, skipping duplicates on restart.
Python code
36 linesimport json
from pathlib import Path
class IdempotentStore:
def __init__(self, storage_path: str = "processed_ids.json"):
self.storage_path = Path(storage_path)
self.processed_ids = self._load()
def _load(self) -> set:
if self.storage_path.exists():
with self.storage_path.open() as f:
return set(json.load(f))
return set()
def _save(self) -> None:
with self.storage_path.open("w") as f:
json.dump(sorted(self.processed_ids), f)
def is_processed(self, message_id: str) -> bool:
return message_id in self.processed_ids
def mark_processed(self, message_id: str) -> None:
self.processed_ids.add(message_id)
self._save()
if __name__ == "__main__":
store = IdempotentStore()
sample_id = "msg-2024-001"
if store.is_processed(sample_id):
print(f"Duplicate skipped: {sample_id}")
else:
store.mark_processed(sample_id)
print(f"Processed and stored: {sample_id}")
Output
Processed and stored: msg-2024-001
How it works
The store loads all previously processed IDs from a JSON file into a set for O(1) membership checks. mark_processed adds a new ID and flushes the entire set to disk, ensuring durability across restarts. Using a set guarantees uniqueness, so duplicate IDs are naturally ignored. This pattern is simple but sufficient for single-process consumers or low write rates. For higher throughput, consider using a database or Redis with TTL.
Common mistakes
- Using a list instead of a set, causing O(n) lookups and duplicates in storage
- Forgetting to save after each processed ID, losing state on crash
- Not handling file corruption, which raises JSONDecodeError and kills the consumer
- Assuming the store is thread-safe, but it needs a lock in concurrent environments
Variations
- Replace JSON file storage with SQLite for better concurrency and crash safety
- Use Redis with SETNX and expiration to manage idempotency keys with TTL
Real-world use cases
- Deduplicating webhook deliveries in a payment processor to avoid charging a card twice.
- Ensuring each Kafka message is processed exactly once by a worker that might restart.
- Tracking processed events in an ETL pipeline so reruns don't reprocess the same data.
Sponsored
More from System design patterns
- Build a BFF (Backend for Frontend) Mock Aggregator in Python medium
- Builder pattern for mocking complex objects in Python easy
- Circuit Breaker Pattern in Python: Closed, Open, and Half-Open States medium
- Create a Data Helper Class in Python easy
- Domain Driven Design Aggregate Root Example in Python medium
- Facade Pattern in Python with Mock Simplification medium
Keep learning
Related tutorials and quizzes for this topic.