前回の(1)はこちらから。
アドサーバで用いているCPANモジュール
Perlで書かれているアドサーバでは、多くのCPANモジュールを用いています。主なものを表1にまとめました。
表1 アドサーバで用いている主なCPANモジュール
モジュール名 | 概要 |
Data::MessagePack | MessagePackのデータ構造へのコンバータ |
Gazelle | PSGI仕様のアプリケーションとWebサーバをつなぐPlackハンドラの一種。高パーフォマンスなPrefork型 |
Cache::Memcached::Fast | memcachedと接続するためのクライアントモジュール |
前述のとおり、広告配信サーバは3つの内部プロセスが連携して成り立っています。プロセスを分けることにより、プロセスごとに適した技術を選択できるという設計上のメリットがあります。一方で、プロセス間で通信する必要があるため、その際のオーバーヘッドによる処理速度の低下という運用面でのデメリットがあります。このため、アドサーバでは処理速度を一つの基準としてCPANモジュールを選択しています。以降では、それぞれのモジュールについて、選択理由を解説していきます。
Data::MessagePack──データ圧縮率の高いシリアライザ
アドサーバがフロントやRTBとやりとりするリクエスト/レスポンスのシリアライザには、Data::MessagePackを使っています。この対抗としては、JSON::XSを用いてJSON形式でシリアライズする案もあります。
両者を比較検討する場合、シリアライズ処理単体の速度だけでなく、データの圧縮効率も判断ポイントとなります。JSONは可読性に優れますが、圧縮効率についてはMessagePackのほうが優れています。今回はサーバ内部のプロセス間の通信ですので、データがより小さく、通信時間を短縮できるMessagePackのパフォーマンスのほうが良いと判断しました。
次のコードでは、JSONとMessagePackで、複数の型がそれぞれある程度の分量を持った集合データの圧縮を行っています。アドサーバでは通信前にBase64エンコードをしているため、その処理も含めています。対象にしたバージョンはそれぞれ、Data::MessagePackが0.49、JSON::XSが3.02です。
実行結果は以下です。MessagePackはJSONの70%の容量でデータを表現できています。
サンプル抽出した本番環境のデータで同様の算出を行ったところ、MessagePackはJSONの85%の容量でデータを表現できました。
Gazelle──Starletと互換性のある高速なPSGIサーバ
アドサーバはPSGI(Perl Web Server Gateway Interface)の仕様にのっとったPSGIアプリケーションとして実装されています。
plackup
で指定するサーバとして、以前はStarletを使っていました。StarletはPrefork型で高速なWebサーバとして実績のあるモジュールです。起動オプションには以下を用いていました。
そのあと、長野雅広さんが作ったGazelleがリリースされたため、アドサーバのアプリケーションとして両者を比較したところ、Gazelleのほうが約15%程度良い結果になりました。単体のベンチマークだと1.7倍の性能差があるとのことです。
このベンチマーク結果に加え、Starletと起動オプションの互換性があったため、乗り換えを決断しました。両者はKeepAliveのサポートなど一部実装差異がある部分以外は、オプションパラメータが同一です。そのためplackupコマンドの変更は、サーバ選択部分のみになります。
ほかとの互換性が考慮されているモジュールは、アプリケーション側を変更しなくてよく、検証や導入のハードルを下げてくれます。互換性が意識されている事例はほかにもあります。たとえばJSON::XSとJSON::PPはメソッドのインタフェースに高い互換性があり、モジュールの切り替えによるアプリケーションの修正が軽微になります。
モジュール切り替えのためにアプリケーションを修正する必要があると、修正過程でバグを仕込む可能性があります。また、アプリケーションを修正しないで切り替えられれば、動作検証において何らか不具合が発覚したとしても問題点の切り分けが容易になります。このことから、互換性の高さもCPANモジュールの導入時や切り替え時の検討要素の一つとしています。
Cache::Memcached::Fast──永続的なデータに高速アクセス
このモジュールは、ほかのキャッシュアクセスモジュールとの比較ではなく、RDBアクセスとの比較になります。詳しくは、広告配信のためのデータの管理方法とともに次節で説明します。なお、fluctでは、使用例として一般的な揮発性のあるキャッシュデータへのアクセスではなく、永続的なデータにアクセスするために用いています。
広告配信サーバでのスケールするデータ管理
図2で「案件情報」と言っていたデータは、具体的には次のものです。
- 広告枠種別情報
- オーバーレイ、インラインなどの広告枠の種別の情報
- 配信設定情報
- RTBやそのほかの配信の選択手段の情報
- 広告種類情報
- テキスト、バナー画像、動画といった広告の種類の情報
広告配信サーバはこの案件情報を用いて、どの広告枠(たとえばインライン)に、どの配信設定(たとえばRTBによる配信)で、どの種類の広告(たとえばバナー画像)を配信するのかを判別します。
fluctの広告配信サーバは複数のデータセンターに分かれて数百台のマシンで運用されています。このような規模でデータをどのように管理し、アクセスするかは、システム構築の肝となります。
スケールのやりかたとしては、極限まで冗長化しています。MySQLのマスタデータを書き出し、各広告配信サーバローカルで案件情報として保持することにより、スタンドアローンなシステムとしてサービスを提供できる構成になっています。以降で、その構成を処理の流れに沿って説明していきます(図3)。
MySQLからの案件情報の書き出し
案件情報のもととなるマスタデータは、MySQLで構築されたDBサーバに保存されており、管理画面から更新されます。このDBサーバに対して数百台の広告配信サーバがアクセスすると、DBサーバが処理上のボトルネックや単一障害点になります。
そこでまず、広告配信サーバからのアクセスをなくすために、マスタデータから案件情報を別なところに書き出します。これを行うのが案件作成サーバです。案件作成サーバは、DBサーバから案件情報を定期的にBerkeleyDB形式のファイルに書き出しています。もし書き出し処理が行われないと、特定期間に配信したい広告が、期日が来ても配信されないなどの事故につながります。そのため、案件作成サーバも冗長化して耐障害性を高めています。また、各案件作成サーバで書き出し処理のタイミングをずらすことで、DBサーバへの負荷を軽減させています。
書き出した案件情報の取得
各広告配信サーバは、案件作成サーバに案件情報ファイルを取得しにいきます。これは、広告配信サーバ内部のバッチの定期実行によって行われています。取得先はランダム化しており、取得タイミングも広告配信サーバごとにずらしているため、取引先が特定の案件作成サーバに偏ることはありません。
取得した案件情報の利用
取得した案件情報ファイルを扱うためのしくみがMemcacheDBです。よく聞くmemcachedは文字どおりキャッシュ機構であり、起動したデーモン上にある揮発的なデータを扱います。それに対してMemcacheDBは、BerkeleyDBファイルをデータソースとして永続的なデータを扱います。そして、Perlで動くアドサーバからこのデータにアクセスするために用いているのがCache::Memcached::Fastです。MemcacheDBもmemcahedと同じプロトコルが扱えるため、アプリケーションコードを読む際にデータのバックグラウンドを意識することなく扱えます。
データ管理のスケール方法として、MySQLをマスタ/スレーブ構成にしてスレーブを冗長化する案もあります。しかしこの方法だと、あるスレーブで障害があった場合、一時的にそこに接続する複数の広告配信サーバに影響が出る事態が避けられません。また、処理速度の面でも、広告配信サーバからMySQLサーバへ接続し問い合わせる速度は、広告配信サーバ自身への内部アクセスに比べて劣ります。このことから、スタンドアローンで立ち上げる方式を採用しています。
<続きの(3)はこちら。>
- 特集1
イミュータブルデータモデルで始める
実践データモデリング
業務の複雑さをシンプルに表現!
- 特集2
いまはじめるFlutter
iOS/Android両対応アプリを開発してみよう
- 特集3
作って学ぶWeb3
ブロックチェーン、スマートコントラクト、NFT