1"""Chapter 43 (case study: flash-sale ticket booking, Ticketmaster / IRCTC Tatkal) deep-dive code.
21. Waiting room: admit users at the rate the booking system can handle; queue position and wait.
32. Seat locking: a short hold per seat, with expiry.
43. Bots: per-account limits in front of the queue.
54. Fairness: first-come by milliseconds favours bots; randomising the opening burst doesn't.
6Run: python3 ch43_flash_sale.py"""
7import random
8
9
10# ---- 1. waiting room ---------------------------------------------------------------
11
12def wait_minutes(position, admit_per_s):
13 return position / admit_per_s / 60
14
15
16def admit_schedule(queue_len, admit_per_s, seconds):
17 admitted = min(queue_len, admit_per_s * seconds)
18 return admitted, queue_len - admitted
19
20
21# ---- 2. seat holds ------------------------------------------------------------------
22
23class Seats:
24 def __init__(self, n, hold_s=300):
25 self.free = set(range(n))
26 self.held = {} # seat -> (user, expires)
27 self.sold = {}
28 self.hold_s = hold_s
29
30 def hold(self, seat, user, now):
31 self._expire(now)
32 if seat in self.free: # one atomic check-and-take in a real DB
33 self.free.discard(seat)
34 self.held[seat] = (user, now + self.hold_s)
35 return True
36 return False
37
38 def pay(self, seat, user, now):
39 self._expire(now)
40 h = self.held.get(seat)
41 if h and h[0] == user:
42 del self.held[seat]
43 self.sold[seat] = user
44 return True
45 return False
46
47 def _expire(self, now):
48 for seat, (_, exp) in list(self.held.items()):
49 if exp <= now:
50 del self.held[seat]
51 self.free.add(seat)
52
53
54# ---- 3/4. fairness -----------------------------------------------------------------
55
56def simulate_sale(seats, humans, bots, bot_requests, per_account_limit, randomise_window_s, seed=43):
57 """Returns how many tickets went to bots. Humans arrive over 0-30 s with 0.2-0.8 s latency;
58 bots fire many requests at t = 0.001 s with tiny latency."""
59 rnd = random.Random(seed)
60 arrivals = []
61 for h in range(humans):
62 arrivals.append((rnd.uniform(0, 30) + rnd.uniform(0.2, 0.8), f"h{h}"))
63 for b in range(bots):
64 for _ in range(bot_requests):
65 arrivals.append((0.001 + rnd.uniform(0, 0.01), f"b{b}"))
66 if randomise_window_s:
67 seen, early = set(), []
68 for a in sorted(arrivals): # one queue spot per account, however many requests
69 if a[0] <= randomise_window_s and a[1] not in seen:
70 seen.add(a[1])
71 early.append(a)
72 late = sorted(a for a in arrivals if a[0] > randomise_window_s)
73 rnd.shuffle(early) # everyone in the opening window is equal
74 order = early + late
75 else:
76 order = sorted(arrivals) # pure first-come, by milliseconds
77 sold_to, bought = [], {}
78 for _, account in order:
79 if len(sold_to) == seats:
80 break
81 if bought.get(account, 0) >= per_account_limit:
82 continue
83 bought[account] = bought.get(account, 0) + 1
84 sold_to.append(account)
85 return sum(1 for a in sold_to if a.startswith("b"))
86
87
88# ---- checks ------------------------------------------------------------------------
89
90def run_checks():
91 # 1. 2 million people in the room at 10:00; booking can admit 2,000 a second
92 assert round(wait_minutes(600_000, 2_000), 1) == 5.0 # position 600,000 waits ~5 minutes
93 admitted, left = admit_schedule(2_000_000, 2_000, 600)
94 assert admitted == 1_200_000 and left == 800_000 # after 10 minutes
95
96 # 2. seat holds
97 s = Seats(3, hold_s=300)
98 assert s.hold(0, "asha", 0) and not s.hold(0, "ravi", 1) # one holder per seat
99 assert s.pay(0, "asha", 200)
100 assert s.hold(1, "kiran", 0) and not s.pay(1, "kiran", 301) # too late: hold expired
101 assert s.hold(1, "ravi", 302) # seat back on sale
102
103 # 3/4. 1,000 seats, 10,000 humans, 500 bots firing 10 requests each
104 pure_fifo = simulate_sale(1_000, 10_000, 500, 10, per_account_limit=10, randomise_window_s=0)
105 with_limit = simulate_sale(1_000, 10_000, 500, 10, per_account_limit=1, randomise_window_s=0)
106 fair = simulate_sale(1_000, 10_000, 500, 10, per_account_limit=1, randomise_window_s=30)
107 assert pure_fifo == 1_000 # bots take every seat
108 assert with_limit == 500 # one each, still first in line
109 assert fair < 80 # ~ their share of accounts (500/10,500)
110
111 return {"fifo_bots": pure_fifo, "limit_bots": with_limit, "randomised_bots": fair}
112
113
114if __name__ == "__main__":
115 print("All checks pass.", run_checks())