Back to writing

The Classic problem that every distributed system must tackle. Curious what it is and how I solved it?


Recently, I've been working on a classic problem that every distributed system must tackle. Curious what it is and how I solved it?

Let me explain.

We're migrating our system to AWS ECS. We have several scheduled tasks that trigger at different times — some of which send emails to users. During testing, we noticed that our test users were receiving duplicate emails. As soon as I saw it, I realized we had missed a case that was already on our to-do list.

The problem: we were running multiple Celery workers, each with its own Beat scheduler.
For those unfamiliar, Celery Beat is a scheduler that periodically sends tasks to Celery workers — similar to a cron job in Python. The issue was that each Beat instance was independently triggering the same scheduled tasks, resulting in duplicate email deliveries.

I considered three possible solutions:


1. Run a single Beat instance

We could remove Beat from all Celery servers and run it on a dedicated instance.
Downside: If that server goes down, scheduled tasks won't run — introducing a single point of failure.


2. Implement leader election

Let the workers elect a leader responsible for running Beat. If the leader goes down, a new one is elected.
Challenge: What happens if a scheduled task is due during the election window? The new leader may miss it.


3. Use distributed locking

Let all instances attempt to schedule the task, but only one succeeds by acquiring a lock.
I implemented this using Redlock, a distributed locking algorithm backed by Redis. Before scheduling a task, each instance checks the cache. If the lock already exists, others skip execution. The lock expires after a certain time, ensuring the task can be scheduled again later.

This approach solved our duplication problem cleanly and without introducing a single point of failure.


If you have questions or suggestions, feel free to comment.
Since you've made it this far, we likely share similar interests — feel free to connect. I’d love to exchange ideas or even collaborate on something interesting.

Thanks for reading!

𝗦𝘆𝘀𝘁𝗲𝗺 𝗗𝗲𝘀𝗶𝗴𝗻 𝗞𝗲𝘆 𝗖𝗼𝗻𝗰𝗲𝗽𝘁𝘀:

  1. Scalability: https://lnkd.in/gpge_z76
  2. Latency vs Throughput: https://lnkd.in/g_amhAtN
  3. CAP Theorem: https://lnkd.in/g3hmVamx
  4. ACID Transactions: https://lnkd.in/gMe2JqaF
  5. Rate Limiting: https://lnkd.in/gWsTDR3m
  6. API Design: https://lnkd.in/ghYzrr8q
  7. Strong vs Eventual Consistency: https://lnkd.in/gJ-uXQXZ
  8. Distributed Tracing: https://lnkd.in/d6r5RdXG
  9. Sync vs Async Communication: https://lnkd.in/gC3F2nvr
  10. Batch vs Stream Processing: https://lnkd.in/g4_MzM4s
  11. Fault Tolerance: https://lnkd.in/dVJ6n3wA

𝗦𝘆𝘀𝘁𝗲𝗺 𝗗𝗲𝘀𝗶𝗴𝗻 𝗕𝘂𝗶𝗹𝗱𝗶𝗻𝗴 𝗕𝗹𝗼𝗰𝗸𝘀:

  1. Database: https://lnkd.in/gti8gjpz
  2. Horizontal vs Vertical Scaling: https://lnkd.in/gAH2e9du
  3. Caching: https://lnkd.in/gC9piQbJ
  4. Distributed Caching: https://lnkd.in/g7WKydNg
  5. Load Balancing: https://lnkd.in/gQaa8sXK
  6. SQL vs NoSQL: https://lnkd.in/g3WC_yxn
  7. Database Scaling: https://lnkd.in/gAXpSyWQ
  8. Data Replication: https://lnkd.in/gVAJxTpS
  9. Data Redundancy: https://lnkd.in/gNN7TF7n
  10. Database Sharding: https://lnkd.in/gMqqc6x9
  11. Database Indexes: https://lnkd.in/gCeshYVt
  12. Proxy Server: https://lnkd.in/gi8KnKS6
  13. WebSocket: https://lnkd.in/g76Gv2KQ
  14. API Gateway: https://lnkd.in/gnsJGJaM
  15. Message Queues: https://lnkd.in/gTzY6uk8

𝗔𝗿𝗰𝗵𝗶𝘁𝗲𝗰𝘁𝘂𝗿𝗮𝗹 𝗣𝗮𝘁𝘁𝗲𝗿𝗻𝘀:

  1. Event-Driven Architecture: https://lnkd.in/dp8CPvey
  2. Client-Server Architecture: https://lnkd.in/dAARQYzq
  3. Serverless Architecture: https://lnkd.in/gQNAXKkb
  4. Microservices Architecture: https://lnkd.in/gFXUrz_T

𝗟𝗼𝘄-𝗟𝗲𝘃𝗲𝗹 𝗗𝗲𝘀𝗶𝗴𝗻 𝗣𝗿𝗼𝗯𝗹𝗲𝗺𝘀:

  1. Design Parking Lot: https://lnkd.in/dQaAuFd2
  2. Design Splitwise: https://lnkd.in/dF5fBnex
  3. Design Chess Validator: https://lnkd.in/dfAQHvN4
  4. Design Distributed Queue | Kafka: https://lnkd.in/dQ6_B4_M

Share with someone who might need this.

FAANGTips