A production-grade system design implementation demonstrating both optimistic and pessimistic locking strategies with comprehensive tests, benchmarks, and REST API.
src/
├── domain/ # Core business logic & interfaces
│ ├── entities/ # Domain models (Product, etc.)
│ ├── locking/ # Lock manager interfaces
│ ├── repositories/ # Repository interfaces
│ └── exceptions/ # Domain exceptions
├── infrastructure/ # Implementation details
│ ├── locking/ # Lock manager implementations
│ └── repositories/ # Repository implementations
├── application/ # Use cases & orchestration
│ └── ProductService.ts # Business logic
├── api/ # HTTP layer
│ └── routes/ # Express routes
├── benchmark/ # Performance benchmarking
└── __tests__/ # Test suites
| Scenario | Recommendation | Reason |
|---|---|---|
| Low contention, frequent reads | Optimistic | Low overhead, no blocking |
| High contention, frequent writes | Pessimistic | Guarantees consistency |
| Long-running operations | Pessimistic | Prevents stale reads |
| Short, quick operations | Optimistic | Less overhead |
| Financial transactions | Pessimistic | Zero tolerance for conflicts |
| UI form updates | Optimistic | User can retry on conflict |
# Create product
POST /api/products
{
"name": "iPhone 15",
"description": "Latest iPhone",
"price": 999,
"quantity": 100
}
# Get product
GET /api/products/:id
# List all products
GET /api/products
# Update with optimistic lock
PUT /api/products/:id?version=1&lockType=OPTIMISTIC
{
"name": "Updated Name"
}
# Update with pessimistic lock
PUT /api/products/:id?lockType=PESSIMISTIC
# Purchase with optimistic locking
POST /api/products/:id/purchase/optimistic
{
"quantity": 2,
"version": 1,
"ownerId": "user-123"
}
# Purchase with pessimistic locking
POST /api/products/:id/purchase/pessimistic
{
"quantity": 2,
"ownerId": "user-123"
}
# Restock
POST /api/products/:id/restock
{
"quantity": 50,
"version": 1,
"lockType": "OPTIMISTIC"
}# Install dependencies
npm install
# Build
npm run build
# Run server
npm start
# Run tests
npm test
# Run with coverage
npm run test:coverage
# Run benchmarks
npm run benchmarkEach product has a version field that increments on every update. Client must provide expected version - if mismatched, conflict detected.
Lock tokens provide exclusive access. Other requesters wait in queue until lock released.
Configurable retry with exponential backoff for optimistic lock failures.
ProductService supports both strategies, choose per-operation based on use case.
- Separation of Concerns: Domain, Infrastructure, Application layers
- Interface Segregation: Clear abstractions for lock managers
- Dependency Injection: Easy to swap implementations
- Error Handling: Specific exception types for different failure modes
- Observability: Benchmarking and testing built-in
- Extensibility: Easy to add new lock strategies or persistence
This project implements a production-grade comparison of Optimistic Locking vs Pessimistic Locking strategies through practical implementation and comprehensive benchmarking.
| Parameter | Value |
|---|---|
| Total Operations | 100 |
| Concurrency Level | 10 |
| Lock Timeout | 5,000 ms |
| Max Retries (Optimistic) | 5 |
| Retry Delay | 50 ms |
| Metric | Optimistic | Pessimistic |
|---|---|---|
| Successful | 100 | 100 |
| Failed | 0 | 0 |
| Conflicts | 0 | 0 |
| Throughput | 100,000 ops/sec | N/A* |
*Pessimistic has overhead from lock acquisition in sequential scenario.
Verdict: Both strategies perform excellently. Optimistic slightly better due to no lock overhead.
| Metric | Optimistic | Pessimistic |
|---|---|---|
| Successful | 10 | 100 |
| Failed | 90 | 0 |
| Conflicts | 90 | 0 |
| Total Time | 5,056 ms | 3 ms |
| Throughput | 1.98 ops/sec | 33,333 ops/sec |
Verdict: Pessimistic wins decisively - 16,800x better throughput in high contention.
| Metric | Optimistic | Pessimistic |
|---|---|---|
| Successful | 67 | 100 |
| Failed | 33 | 0 |
| Conflicts | 33 | 0 |
| Total Time | 3,025 ms | 2 ms |
| Throughput | 22 ops/sec | 50,000 ops/sec |
Verdict: Pessimistic wins - 2,272x better throughput even with distributed contention.
- ✅ Low contention scenarios - Near-zero overhead
- ✅ Read-heavy workloads - No locks needed for reads
- ✅ Web interfaces - Users can retry on conflict
- ✅ Microservices - Reduces inter-service coordination
- ✅ High contention - Guarantees first-come-first-served
- ✅ Financial transactions - Zero tolerance for lost updates
- ✅ Inventory management - Prevents overselling
- ✅ Resource allocation - Ensures deterministic behavior
| Use Case | Recommended Strategy | Rationale |
|---|---|---|
| E-commerce checkout | Pessimistic | Prevent overselling during flash sales |
| User profile update | Optimistic | Low conflict, user can retry |
| Inventory sync | Pessimistic | High contention, consistency critical |
| Analytics dashboard | Optimistic | Mostly reads, occasional writes |
| Payment processing | Pessimistic | Zero tolerance for failures |
| Form submission | Optimistic | User-mediated retry acceptable |
// Use both strategies based on operation type
class HybridProductService {
async getProduct(id) {
// Read operations: Optimistic (no lock needed)
return this.repository.findById(id);
}
async purchaseCritical(productId, qty) {
// Critical writes: Pessimistic
const lock = await this.pessimisticLock.acquire(productId);
try {
return await this.repository.decrement(productId, qty);
} finally {
await this.pessimisticLock.release(lock);
}
}
async updateMetadata(productId, data) {
// Non-critical writes: Optimistic with retry
return this.optimisticUpdate(productId, data);
}
}- Repository Pattern - Abstracts data access
- Strategy Pattern - Swappable lock implementations
- Factory Pattern - Easy lock manager creation
- Unit of Work - Transaction-like operations
- ✅ 33 unit tests passing
- ✅ 100% test coverage on core logic
- ✅ Type-safe throughout
- ✅ Clean layered architecture
- ✅ Comprehensive error handling
- Monitor conflict rates - if >10%, consider pessimistic
- Implement exponential backoff for retries
- Add version field to all entities
- Log conflicts for analysis
- Set appropriate timeout values
- Implement dead lock detection
- Use connection pooling
- Consider fair queuing for waiters
- Redis for distributed locks
- Database row-level locks for persistence
- Circuit breaker for lock service failures
- Metrics on lock wait times
Both locking strategies have their place in modern systems:
-
Optimistic Locking: Best for scalability, low contention, and user-facing operations where retry is acceptable.
-
Pessimistic Locking: Best for correctness-critical operations, high contention scenarios, and when deterministic ordering matters.
The benchmark demonstrates that choosing the wrong strategy can result in 16,800x performance degradation in high-contention scenarios.
Final Recommendation: Implement both strategies and choose per-operation based on the specific use case's contention profile.