The Double-Booking Problem
In a rental marketplace, double-booking is the cardinal sin. When two customers try to book the same item for overlapping dates, exactly one should succeed — instantly, consistently, and without race conditions.
Sounds simple? At scale, it's anything but. With 50,000+ active listings, 10,000+ daily booking attempts, and users spanning multiple time zones, our initial PostgreSQL-based availability check couldn't keep up. We experienced a 0.3% double-booking rate during peak hours — unacceptable for a trusted marketplace.
Architecture Overview
Our solution combines three layers:
1. Redis-Based Locking — When a booking attempt begins, we acquire a distributed lock keyed to the listing ID and date range. The lock has a 30-second TTL (time-to-live) to prevent deadlocks if a process crashes.
2. PostgreSQL with Range Types — We use PostgreSQL's native daterange type with exclusion constraints to enforce non-overlapping bookings at the database level. This serves as our source of truth and provides a safety net if the Redis layer fails.
3. Event-Driven Sync — All availability changes are published to a Kafka topic, consumed by our search index (Elasticsearch) and notification service. This ensures that listing availability is updated across the platform within 500ms of a booking confirmation.
Performance Numbers
After the migration:
- Double-booking rate: 0.000% (zero incidents in 6 months) - Average booking confirmation time: 180ms (down from 2.3 seconds) - Concurrent booking capacity: 5,000+ per second - Availability query latency (P99): 12ms
The system has handled peak loads during Diwali season (3x normal traffic) without any degradation.
Lessons Learned
Building this system taught us several valuable lessons:
1. Defense in depth matters — Redis handles the fast path, but PostgreSQL constraints catch edge cases. Never rely on a single layer for data integrity.
2. Idempotency is essential — Every booking operation is idempotent. Network retries, app crashes, and timeout scenarios all produce correct results.
3. Observability first — We built comprehensive dashboards before the migration. Every lock acquisition, release, and timeout is tracked in Prometheus and visualized in Grafana.
4. Test with chaos — We ran chaos engineering experiments (random Redis node failures, artificial network partitions) during staging. The system handled every scenario without data inconsistency.



