クラスター
ここまでのすべて——分類、オークション、選択、ペーシング、フロア——は、サイトごとの、状態を持つ仕事です。アーキテクチャの問いは、その状態がどこに住むか。Promovolve の答えは、アクターの中です。現実世界の1つの物につき1つ、それぞれが自分の状態の唯一の書き手として。
エンティティ
システムは Apache Pekko のクラスターシャーディングの上で動きます。各エンティティは ID で宛先指定されるアクターで、常にちょうど1つのノード上に生きています。ノードの参加・離脱に応じてシャードコーディネーターが配置と移動を行います。
| エンティティ | 1つあたり | 所有するもの |
|---|---|---|
SiteEntity | サイト | 検証、スロット、分類 |
AdServer | サイト | 配信、ペーシング、クリエイティブ統計、フロア |
AuctioneerEntity | サイト | オークション、ページキャッシュ、承認キューへの供給 |
CampaignEntity | キャンペーン | 入札、クリエイティブ、予算 |
AdvertiserEntity | 広告主 | キャンペーン、承認、日予算 |
CategoryBidderEntity | カテゴリー×シャード | 需要の台帳 |
サイトの配信経路の全体——ペーシング状態、トンプソンの窓、フロアの掃引——は、メッセージを1つずつ処理する単一のアクターです。これは偶然ではなく設計上の立場です。ロックなし、キャッシュ一貫性プロトコルなし、read-modify-write の競合なし。競合する相手となる並行性が、そもそも存在しないからです。引き換えはサイトあたりのスループット上限——1アクターのメールボックス——であり、この立場が成り立つのは、1日数百万インプレッションのサイトを単一アクターが余裕で吸収できるからです。いつか1アクターの容量を超えるサイトが現れたら、それは設計がサイト内部のシャーディングで応えるべきスケーリング問題であって、状態をステートレスにすることで応える問題ではありません。
ノードはロールを担います——api(HTTP)、entity(シャーディングされたアクター)、singleton(クラスター全域のディレクトリ)、そして crawler。この歴史的な名前にもかかわらず、いまはランディングページ解析ワーカーのホストとして存在します。ブロッキングする仕事(Playwright、LLM 呼び出し、JDBC)は専用ディスパッチャーで走るので、負荷を受けたエンティティが配信経路を飢えさせることはできません。
ServeIndex:読みは複製、書きは単一の書き手
配信の読み取りはネットワークを渡ってはなりません。候補プールは ServeIndex——Pekko Distributed Data(CRDT)の上に築かれたもの——に住みます。全ノードが完全なレプリカを持つので、配信時の参照はローカルなマップ読み取りです。エントリーは site|slot のハッシュで32個の名前付きマップに分散されます——複製はマップ単位で働くので、巨大な1枚のマップでは変更のたびに全体が再ゴシップされてしまうのです。
一貫性は意図的に非対称です。書き込みはローカルで確認(WriteLocal)して外へゴシップします——オークション結果が他ノードに見えるのが数十ミリ秒遅れても無害です。例外はキー全体の削除で、これはリトライ付きの過半数書き込みを使います。ゴシップ競争に負けた削除は消したはずの候補を蘇らせ、(より悪いことに)削除のトゥームストーンは、分岐したレプリカが破壊的に食い違い得る唯一の場所だからです。速い経路は結果整合で、破壊的な経路は確実性に対価を払います。
レプリカは永続です——DData は指定キーをローカルの LMDB ストアに永続化します——ので、再起動したノードは世界を再ゴシップする代わりにディスクからキャッシュを回復します。起動時の分類再生(サイトエンティティが新しいオークショニアを冪等に教え直す)と合わせて、クラスター全体の再起動はおよそ1分で自己治癒します。スロットが束の間空で配信され、やがて勝手に埋まっていく「再起動後の暗転窓」です。
痛い目を見て学んだ規則
ノード間メッセージはマーカートレイトによって Jackson-CBOR でシリアライズされ、この規律が厳格なのは失敗が無音だからです。マーカーを欠いた応答ペイロードは、ネットワーク境界でただ消えます。裸のタプルをはじめ未登録の形は、ノード間プロトコルから追放されています。永続状態はフィールドのエイリアスで進化させ、永続化済みフィールドの改名は決して行いません。そして Future が必要とする状態は、必ずメッセージとしてアクターに送り返されなければなりません——アクターの状態に対して Future をインラインで完了させることこそ、このアーキテクチャ全体が防ぐために存在する並行性バグなのです。
コアの周りのプラットフォーム——ダッシュボード、承認キュー、課金、メンバー管理——は、サーバーサイドテンプレートをレンダリングする別の Go サービスです(SPA フレームワークなし)。コアとは HTTP で、プロジェクションと台帳のためには Postgres と話します。認証はパスキーのみ。しかし、この本に書かれたすべての配信判断は、アクターの中で起きています。