Idempotent Consumer: Store Processed IDs in Python

Implement an idempotent consumer that persists processed message IDs to a JSON file, skipping duplicates on restart.

Easy Python 3.9+ Aug 9, 2026 System design patterns 15 views 0 copies

Python code

36 lines
Python 3.9+
import 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

stdout
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

  1. Replace JSON file storage with SQLite for better concurrency and crash safety
  2. 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

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from System design patterns

Related tutorials and quizzes for this topic.