この記事で学んだことをメモる。
- For
HLD (3) Users should be able to book tickets to events, the status change fromRESERVEDtoBOOKEDshould be done via the webhook from Stripe. Make sure that the API is idempotent.
DD
How do we improve the booking experience by reserving tickets? 🔥🔥
座席を選択した後に支払い中にその席を他のユーザーに取られ、支払いが成功した後に予約に失敗する、ということがないようにする。
🟢 Great Solution: Implicit Status with Status and Expiration Time
Status (i) AVAILBLE (ii) RESERVED with reserved_until (iii) BOOKED.
OCC で row lock を取る。ロックを取れなかったトランザクションを待たせる必要はないので、Pessimistic Lock は使わない。
(TODO: フローを説明する)
Challenges:
- Payment system の 支払い完了のWebhook が遅れによりこのような問題が発生しうる。(1) 9:59 ユーザーの支払いがなされる (2) 10:00 ロックが解除される (3) 10:10 別のユーザーが席が空いてるのを確認し支払いフローに進む (4) 10:20 支払い完了のWebhookが届き、status=BOOKED になる (4) 10:30 別のユーザーの支払いが完了しWebhookが届くも、すでにその席はBOOKEDであるため、予約に失敗する。 -> これを防ぐためには、Redis のロックの時間を実際の支払い猶予時間より少し長め (10:30とか)に取っておけば良い。
🟢 Great Solution: Distributed Lock with TTL
Status (i) AVAILBLE (ii) BOOKED + Redis Distributed Lock with TTL
(TODO: フローを説明する)
(TODO: Redisのアトミックなコマンド書けるようにする)
Challenges:
-
RESERVEDstatus の席一覧の取得。-> ロック取得後に DBに status=RESERVED と expires_at を書き込みすれば良い。(大問題。そこまでするなら、Redis lock使わなくて良くないか) - Redis Lock がダウンした場合、ロック取得をすっ飛ばすことになる。つまり、支払いを済ませた後に予約に失敗するユーザーが発生する。なお、この場合を考慮して、Redis Lock を導入したとしても、
AVAILABLE→BOOKEDへのDBの値更新はOCCを使う必要がある。 - (上と同じ) Payment system の 支払い完了のWebhook が遅延問題。
DD
How is the view API going to scale to support 10s of millions of concurrent requests during popular events?
- Redis cache in front of DB
- For periodical refresh, set TTL. For immediate refresh, writer invalidates old cache. (ref. Scaling Reads - DD4)
- Load Balancer + Horizontal Scaling of servers
DD
How will the system ensure a good user experience during high-demand events with millions simultaneously booking tickets? 🔥 [HARD]
ものすごくユーザーが増えると、「画面上では空席があるように見えるが、その席をクリックした時に初めてその席が予約中あるいは予約完了した席であることが判明する」ということが起こりうる。
-> Virtual Waiting Queue を導入する。(ref. Dealing with Contentions - DD4)
- BookingQueue サーバーは、SSEで順番待ちのリクエストをしたクライアントと接続する。
- BookingQueue サーバーは、そのユーザーをBookingQeueue に入れる。
- BookingQueue サーバー上のプロセスが、定期的に各ユーザーの現在ポジションをユーザーへSSEで送信する。
- BookingQueue サーバー上のプロセスが、定期的にAdmittedUsersの数をカウントし、空いた人数分のuser_idをBookingQueueの先頭からBookingRoomへ移す。
- AdmittedUsers の user_id は、TTL か BookingService による削除によって消える。
- BookingService は、ペイメントに進む前のバリデーションとして、
admitted:{eventId}:{userId}がAdmittedUsers 内にあることを確かめる。 - BookingService は、予約完了後に、
admitted:{eventId}:{userId}を AdmittedUsers から削除する。
AdmittedUsers Redis のデータ構造
BookingQueue サーバーからの admitted:{eventId} のカウントと、
TTLによる削除のために、admitted:{eventId}:{userId}のTTL設定が必要となる。
このために、以下を使う。
📝 サーバー負荷を考えて、順番待ちのクライアントとサーバーはショートポーリングでなく、SSEで接続する。(ref. 負荷の比較)
DD
How can you improve search to ensure we meet our low latency requirements?
以下のようなテキスト検索のパフォーマンス改善をしたい。
WHERE artist LIKE '%Taylor%'WHERE description LIKE '%Taylor%'
🟢 Great Solution: PostgreSQL full-text indexes
tsvector (データ型) と GIN indexes (inverted index) を使う。
🟢 Great Solution: ElasticSearch
-
DB --CDC--> ESの非同期更新にする。 - fuzzy search が使えるようになる。
- デメリットは、additional infrastructure complexity and cost.
DD
How can you speed up frequently repeated search queries and reduce load on our search infrastructure?
🟢 Good Solution: Redis cache を使う
- キャッシュキーに含めるクエリパラメータのフィルタリング、ソートが必要
- キャッシュインバリデーションが大変。キャッシュを全舐めして、更新されたイベントを含むキーを全てinvalidateする必要がある。
🟢 Great Solution: ElasticSerach built-in cache を使う
- Shard レベルのキャッシング
- ESによる adptive caching strategie を使用できる
TODO: 理解
🟢 Great Solution: CDN を使う
AWS CloudFrontなど。



