If you've ever worked with microservices or distributed systems, you've probably hit this problem: two services trying to process the same job, update the same record, or access the same resource simultaneously. Race conditions in distributed systems aren't just annoying they cause data corruption, duplicate charges, and 3am pages.
The typical solution involves implementing locking logic, handling edge cases, worrying about deadlocks, and hoping you got it right. We decided to build something simpler.
The Core Problem
Imagine you have a payment processing system running across multiple servers. A customer submits an order, and two servers both try to process the payment at the same time. Without coordination, you could charge the customer twice.
Or consider background job workers pulling from a queue. Multiple workers grab the same job and process it simultaneously, wasting resources and potentially causing data inconsistencies.
These aren't edge cases, they're fundamental challenges in distributed systems. You need a way to coordinate across processes and ensure only one service handles a specific task at any given time.
TheLock: Distributed Locking via HTTP
We built TheLock to solve this with a simple HTTP API. Here's how it works:
Creating a Lock
curl --location 'https://api.thelock.io/api/v1/service/locks' \
--header "Authorization: Bearer $API_KEY" \
-X POST \
-H "Content-Type:application/json" \
--data '{ "lock_key": "payment-processor-123", "expiration": 1765999369000 }'
Response:
{
"lock_key": "payment-processor-123",
"created_at": 1765720501438,
"last_updated_at": 1765720501438,
"expiration": 1765999369000
}
You now have a distributed lock. Any other service trying to acquire the same lock will fail until you release it or it expires.
What Happens on Collision?
If another process tries to acquire the same lock:
curl --location 'https://api.thelock.io/api/v1/service/locks' \
--header "Authorization: Bearer $API_KEY" \
-X POST \
-H "Content-Type:application/json" \
--data '{ "lock_key": "payment-processor-123", "expiration": 1765999369000 }'
Response:
{
"detail": "Lock already exists"
}
Clean, simple error handling. Your code immediately knows whether it got the lock or should skip the work.
Checking Lock Status
Need to check if a lock exists or see when it expires?
curl --location 'https://api.thelock.io/api/v1/service/locks/payment-processor-123' \
--header "Authorization: Bearer $API_KEY" \
-X GET
Response:
{
"lock_key": "payment-processor-123",
"created_at": 1765720501438,
"last_updated_at": 1765720501438,
"expiration": 1765999369000
}
Extending Lock Duration
Need more time to complete your work? Update the expiration:
curl --location 'https://api.thelock.io/api/v1/service/locks/payment-processor-123' \
--header "Authorization: Bearer $API_KEY" \
-X PUT \
-H "Content-Type:application/json" \
--data '{ "expiration": 1767999369000 }'
This is useful for long-running tasks where you can't predict exactly how long the work will take.
Releasing the Lock
When your work is done, release the lock so other processes can acquire it:
curl --location 'https://api.thelock.io/api/v1/service/locks/payment-processor-123' \
--header "Authorization: Bearer $API_KEY" \
-X DELETE
Returns 204 No Content on success.
Real-World Use Cases
Background Job Processing
Multiple workers pull from the same job queue. Each worker tries to acquire a lock for the job:
# Worker 1 tries to acquire lock
curl --location 'https://api.thelock.io/api/v1/service/locks' \
--header "Authorization: Bearer $API_KEY" \
-X POST \
-H "Content-Type:application/json" \
--data '{ "lock_key": "job-12345", "expiration": 1765720800000 }'
# Success! Worker 1 processes the job
# Worker 2 tries the same lock
curl --location 'https://api.thelock.io/api/v1/service/locks' \
--header "Authorization: Bearer $API_KEY" \
-X POST \
-H "Content-Type:application/json" \
--data '{ "lock_key": "job-12345", "expiration": 1765720800000 }'
# Returns "Lock already exists" - Worker 2 skips this job
Preventing Duplicate Payments
Before processing a payment, acquire a lock with the order ID:
# Try to acquire payment lock
curl --location 'https://api.thelock.io/api/v1/service/locks' \
--header "Authorization: Bearer $API_KEY" \
-X POST \
-H "Content-Type:application/json" \
--data '{ "lock_key": "payment-order-789", "expiration": 1765720530000 }'
# If successful, process payment
# If it fails, payment is already being processed
# After payment completes, release the lock
curl --location 'https://api.thelock.io/api/v1/service/locks/payment-order-789' \
--header "Authorization: Bearer $API_KEY" \
-X DELETE
Even if a user double-clicks the "Pay" button or your load balancer sends requests to multiple servers, only one payment will process.
Coordinating Scheduled Tasks
Run the same cron job on multiple servers for redundancy. Only one instance actually executes:
# Daily report job runs on 3 servers at midnight
# Each server tries to acquire the lock
# Server 1 (succeeds)
curl --location 'https://api.thelock.io/api/v1/service/locks' \
--header "Authorization: Bearer $API_KEY" \
-X POST \
-H "Content-Type:application/json" \
--data '{ "lock_key": "daily-report-2025-01-26", "expiration": 1765721100000 }'
# Server 2 (fails - lock exists)
curl --location 'https://api.thelock.io/api/v1/service/locks' \
--header "Authorization: Bearer $API_KEY" \
-X POST \
-H "Content-Type:application/json" \
--data '{ "lock_key": "daily-report-2025-01-26", "expiration": 1765721100000 }'
# Server 3 (fails - lock exists)
# Only Server 1 generates the report
If Server 1 crashes before completing the report, the lock expires and another server can take over.
Leader Election
Implement simple leader election across multiple service instances:
# Each instance tries to become leader on startup
curl --location 'https://api.thelock.io/api/v1/service/locks' \
--header "Authorization: Bearer $API_KEY" \
-X POST \
-H "Content-Type:application/json" \
--data '{ "lock_key": "service-leader", "expiration": 1765720560000 }'
# Whichever instance gets the lock is the leader
# Leader periodically extends the lock to maintain leadership
curl --location 'https://api.thelock.io/api/v1/service/locks/service-leader' \
--header "Authorization: Bearer $API_KEY" \
-X PUT \
-H "Content-Type:application/json" \
--data '{ "expiration": 1765720620000 }'
# If the leader crashes, lock expires and another instance becomes leader
How It Works
Simple HTTP API
Standard REST operations: POST to create, GET to check, PUT to extend, DELETE to release. Works with any language or tool that can make HTTP requests.
Automatic Expiration
Every lock has a TTL. If your process crashes before releasing the lock, it automatically expires. No risk of permanent deadlocks.
When to Use Distributed Locks
Distributed locking is useful when you need to:
- Prevent duplicate processing of jobs or tasks
- Coordinate scheduled operations across multiple servers
- Ensure single-instance execution of critical operations
- Manage access to shared resources in microservices
- Implement leader election patterns
- Prevent race conditions in payment or financial operations
Try It Out
We are currently running a private beta for TheLock. If you're dealing with coordination challenges in distributed systems, We would love to have you try it.
Join the beta: https://thelock.io
The beta is free, and we are looking for feedback from engineers working on real distributed systems. Tell us what works, what doesn't, and what would make this more useful for your use case.
What's Next
Features we are working on:
- Client libraries for popular languages (Python, Go, JavaScript, Ruby)
- Webhook notifications when locks are released
- Lock monitoring and analytics dashboard
- Enhanced retry logic and failure modes
If you have other requirements or ideas, we would love to hear them. The goal is to make distributed coordination as simple as making an HTTP call.
Questions about distributed locking or TheLock? Drop a comment below or reach out at Contact Us.
Top comments (0)