Day 27 Β· Week 4

Design Airbnb

Availability calendars, double booking prevention via optimistic locking, geospatial search with PostGIS, and dynamic pricing signals at 6M+ listing scale.

6M+
Listings
3.5h
Study Time
2
Simulations
5
Quizzes
Availability Calendar Optimistic Locking Geospatial Search Double Booking Prevention Pricing Engine

Six Hard Problems at 6M Listings

Airbnb is a distributed inventory system where every listing is a unique, time-bounded resource. Unlike e-commerce (buy another unit), there is only one Beach House on June 15th. Getting this wrong means two guests, one room, and an emergency rebooking at Airbnb's expense.

πŸ“…

Availability

Calendar showing which dates are booked β€” must be accurate across 6M listings simultaneously. Stale availability leads to users booking unavailable dates.

πŸ”’

Double Booking

Race condition β€” 2 users book same listing same date simultaneously. Both see "available" β†’ both submit β†’ without locking, both succeed. Catastrophic UX failure.

πŸ—ΊοΈ

Search

Geospatial queries (listings near NYC) + date range availability + price filter + amenities + ratings. Combining all filters in one fast query is non-trivial.

πŸ’°

Pricing

Dynamic pricing based on demand, events, seasons. Smart Pricing suggests higher rates when search volume spikes for a date window with few available listings.

⚠️

The Double Booking Cost

When a double booking occurs, Airbnb must: find the guest alternative accommodation (often at Airbnb's cost), pay a travel credit of up to $100, and potentially pay hotel rates for the displaced guest. Preventing double bookings is not a nice-to-have β€” it has direct, large financial consequences.

Double Booking Race Condition

Compare what happens with and without locking when two users try to book the same listing simultaneously. Click "Book Now" for both users quickly in the left panel to trigger a race condition.

No Guard
Without Optimistic Locking
Beach House β€” Available Jun 15-20
Alice
Viewing listing...
Bob
Viewing listing...
Optimistic Lock
With Optimistic Locking (version column)
Beach House β€” Available Jun 15-20 (v3)
Alice
Loaded page (version=3)
Bob
Loaded page (version=3)
βœ…

How Optimistic Locking Works

Each listing has a "version" integer. When Alice loads the page, she sees version=3. When she submits, the DB runs: UPDATE listings SET version=4 WHERE id=X AND version=3. If another user already changed the version (Bob committed first), 0 rows are updated β†’ Alice's transaction detects this and retries or shows an error. Exactly one person wins the booking.

Reservation Schema & Optimistic Lock

The reservation table is the source of truth for all bookings. Date ranges with overlap detection using PostgreSQL's native range operators are the canonical approach.

FieldTypeNotes
iduuidPrimary key β€” globally unique booking identifier
listing_idbigintForeign key to listings table, partition key for sharding
guest_idbigintUser who made the booking
start_datedateCheck-in date (inclusive)
end_datedateCheck-out date (exclusive, standard range convention)
statusenumPENDING / CONFIRMED / CANCELLED β€” only CONFIRMED blocks dates
versionintOptimistic lock version β€” incremented on every write to listings row
booking_optimistic_lock.sql
-- Optimistic locking: check version matches before booking
BEGIN;

-- Step 1: Check availability (no lock yet)
SELECT COUNT(*) FROM reservations
WHERE listing_id = $1
  AND status = 'CONFIRMED'
  AND daterange(start_date, end_date) && daterange($2, $3);
-- If count > 0: reject (dates unavailable)

-- Step 2: Update listing version (optimistic lock)
UPDATE listings
SET version = version + 1
WHERE id = $1 AND version = $4;  -- $4 = version user saw on page load
-- If 0 rows updated: another booking won the race β†’ retry

-- Step 3: Insert reservation
INSERT INTO reservations (listing_id, guest_id, start_date, end_date, status)
VALUES ($1, $2, $3, $4, 'CONFIRMED');

COMMIT;
πŸ’‘

The && Operator is Key

PostgreSQL's daterange overlap operator && returns true if two ranges share any day. daterange('2024-06-15', '2024-06-20') && daterange('2024-06-18', '2024-06-22') = true. A GiST index on the daterange column makes this query fast even across millions of reservations per listing. Without the index: full table scan on every availability check.

Find Listings Within Radius

Drag the radius slider to see how spatial indexing narrows the search space. PostGIS's ST_DWithin function with a GiST index makes radius queries on 6M listings take milliseconds.

Geospatial Radius Visualizer

Listings inside the radius are returned by ST_DWithin. Click any listing pin to see its price and availability.

🏠
🏠
🏠
🏠
🏠
🏠
🏠
5 km 4 listings in range
ℹ️

PostGIS GiST Index Power

ST_DWithin(location, ST_Point(-74.0, 40.7), 50000) β€” find all listings within 50km of NYC in milliseconds. Without a spatial index: full table scan of 6M listings, comparing distance for every row. With a GiST index: the index prunes the search space to a bounding box, then checks exact distance only for nearby candidates. 10,000x speedup at scale.

geospatial_search.sql
-- Airbnb-style geospatial + availability query
SELECT l.id, l.title, l.price_per_night,
       ST_Distance(l.location, ST_Point($lon, $lat)) AS distance_m
FROM listings l
WHERE
  -- Geo: within 50km of search center
  ST_DWithin(l.location, ST_Point($lon, $lat), 50000)
  -- Price: within budget
  AND l.price_per_night BETWEEN $min_price AND $max_price
  -- Availability: no confirmed booking in requested range
  AND NOT EXISTS (
    SELECT 1 FROM reservations r
    WHERE r.listing_id = l.id
      AND r.status = 'CONFIRMED'
      AND daterange(r.start_date, r.end_date) && daterange($checkin, $checkout)
  )
ORDER BY distance_m
LIMIT 50;

Why Each Technology

Primary Database
PostgreSQL + PostGIS
ACID for booking consistency + PostGIS extension for native geospatial queries. daterange type with GiST index makes overlap detection fast. Optimistic locking via version column.
Not MySQL β€” no native range type or PostGIS support. Not MongoDB β€” no ACID multi-document transactions for atomic booking + version check.
Search Engine
Elasticsearch
Rich filtering: amenities (full-text), ratings range, property type facets. geo_point field + geo_distance query for radius search. Updated asynchronously from Postgres via Kafka.
Not Postgres for search β€” complex multi-filter queries with ranking are slow on relational DB at 6M listing scale. Elasticsearch is purpose-built for this.
Availability Cache
Redis Bitmaps
One bitmap per listing per month (31 bits). GETBIT for O(1) single-date availability. Fast pre-filter before the full DB query. Invalidated on every booking write.
Not database for pre-filter β€” querying reservations table on every search click is too slow. Redis bitmap is a 31-byte structure that fits in cache line.
Concurrency Control
Optimistic Locking
Better throughput than pessimistic (SELECT FOR UPDATE). No lock held during network round trips to payment processor. Retry on conflict is rare in practice (sub-second window per listing).
Not pessimistic β€” holding a row lock while waiting for Stripe authorization (300-2000ms) blocks all other booking attempts for that listing. Throughput collapse under load.

Quiz β€” 5 Questions

1. Two users simultaneously book the same Airbnb listing for the same dates. Without locking:
2. Optimistic locking uses a 'version' column. A booking attempt fails when:
3. PostGIS ST_DWithin is used in Airbnb's search because:
4. The availability calendar stores reservations as date ranges. The PostgreSQL overlap operator && checks if:
5. Airbnb's pricing engine updates listing prices dynamically. The signal that a big event (concert, New Year's) justifies higher prices comes from: