ABEMA広告とGCPの近況について

# まえがき
株式会社AbemaTVでABEMAの広告プロダクト開発チームでSREをしているkosakaと言います。
ABEMAやアメーバをはじめ、サイバーエージェントが運営する各メディアをプラットフォームとした広告プロダクト・広告配信システムを開発しているチームが社内にはいくつかあります。それらを横軸で束ねる、PTA（Publisher adTech Associations）というアライアンスが存在し、所属エンジニアやビジネス職のメンバーを中心に、広告配信を支える技術や広告業界のドメイン知識に関する情報共有、勉強会、[公開イベント](https://cyberagent.connpass.com/event/227271/)などを定期的に開催しています。
今回はその[PTAのアドベントカレンダー2022](https://adventar.org/calendars/7539)のエントリとして記事を書きたいと思います。

ABEMAでは主にGCPとAWSが利用されていますが、広告関連プロダクトはほぼGCPで稼働しています。あまり歓迎すべきことではありませんが、システムでトラブルが起こることがGCPの各プロダクトについて詳しく知る機会に繋がっていると感じています。この記事では、GCPのいくつかのプロダクトについて、ここ数ヶ月で新たな学びとなったことをまとめてみたいと思います。

# Reservations
広告プロダクトではGKE上でマイクロサービスを運用しています。多数の同時視聴が予想されるライブイベントに向けて多量のGKEノードを追加で確保する必要があり、今回初めて[ゾーンリソースの予約](https://cloud.google.com/compute/docs/instances/reservations-overview)を利用しました。予約によって、マシンタイプ、リージョン、ゾーンを指定しリソースキャパシティの確保（=利用の確約）を行うことができます。ノードを立てようとしたら在庫が枯渇していてスケールアウトできなかった、といった事態を避けることが目的です。  
マシンタイプ、リージョン、ゾーンが一致する場合は、すでに利用中のインスタンスもこの予約枠を消費することになるため、予約するインスタンス数は、`運用中のインスタンス数+追加で確保したいインスタンス数`となることに注意が必要です。また、実際に予約期間中にリソースを利用したか利用しなかったに関わらず料金が発生します。コストを最適化するために、精度の高いキャパシティプランニングが求められることになります。  
少し話が逸れますが、組織や運用規模が大きくなると直接コストを意識することが少なくなる傾向がある気がしています。ABEMAはまだ単体黒字化を達成していませんので、その昔マネタイズ模索中のスタートアップ企業にいた頃、当時の社長から毎日のように言われていた「今手元にあるお金のすべてはベンチャーキャピタル、投資家、株主からのお金だ」という言葉を思い出すことが度々あります。資金調達がいかに大変なことかを間近で見てきた経験から、無駄なコストを可能な限り減らすこともエンジニアとして重要な視点だと考えています。もちろん、必要なところにはどんどん使うべきです。しかし、安易に「お金で解決！」で片付けるべきではないですし、他に手段はないか、本当にそれが最善かは少なくとも議論する必要があるでしょう。

# Cloud Storage
CDNを利用しているため直接的に高負荷状態となることがなかったためトラブルシューティングする機会がいままでほとんどありませんでした。エラーが返却されている可能性があるという報告を受け、メトリクスを確認していたところ、`FAILED_PRECONDITION`というレスポンスを返却しているリクエストが一定数あったため、原因の調査を行いました。解決までに数日を要したのですが、結論としては、実際は304レスポンスであり、メトリクス上では`FAILED_PRECONDITION`エラーとして観測されているものの、クライアント側ではただの`304 NOT MODIFIED`に過ぎないことが判りました。これは[Precondition Criteria](https://cloud.google.com/storage/docs/request-preconditions#precondition_criteria)に記載されているヘッダを持ったリクエストがGCSオブジェクトに対して行われた際の挙動であり、304または412レスポンスコードとして返却されるものが`FAILED_PRECONDITION`としてメトリクス上記録されているようです。

# Cloud Pub/Sub
広告配信の視聴実績の収集にはpub/subを利用しています。ライブイベントの配信では、多数の視聴者様に同時にCMが配信されるため、特定のタイミングで負荷がバーストする傾向にあります。そのため、pub/sub側のスケーリング/ウォーミングが追いつく前にピークを過ぎるような負荷状況が見られ、publisher側のレイテンシが一時的に悪化し、`deadline_exceeded`（タイムアウト）がメトリクス上で散見されました。pub/sub SDKがpublishをリトライをしてくれるため、結果的にはリトライ試行中にpublish成功しているのですが、リトライ中にpodが落ちた場合にはpublishされず、視聴実績が失われる可能性が出てきます。（実際のところは、同内容を別途ログ出力しているため、そこからサルベージできるような冗長性を持たせています）また、期待するスループットが出ていないこともあり、将来を見据えるとアーキテクチャ変更が必要だと考えています。
原因としては、CMが流れていない間はpub/sub topicの負荷がほぼゼロとなり、CMのタイミングのみでバーストが起こっていることにあると思われるため、常にある程度の負荷がかかり続けている状態を維持するか、直前にウォーミングを行うことが解決となりそうです。現状はライブイベント専用にpub/subを分けていますが、通常放送やオンデマンド配信のpub/subと統合し、subscriber側で処理を分岐するなどを検討してもよいかもしれません。ただし、それによって単一障害点が生まれ得るため慎重に議論をしたいと思います。または、CMのある程度前に意図的に処理不要なメッセージを流すことも効果があるのでは？と考えています。

# Cloud Dataflow
上で述べたpub/subのsubscriberに該当します。上記バーストに備え、ある程度のワーカー数を事前に準備してライブイベントに臨んでいるのが現状ですが、pub/subに依存した負荷の緩急があるため、負荷の少ない時間帯にはワーカーが無駄になってしまいます。視聴実績の処理には即時性が求められていないことと、pub/subメッセージの存続時間内（デフォルトでは7日間）にackされれば十分であることから、ある程度の時間に処理しきれるだけのワーカー数に調整を行うか、検証・実装しきれていないオートスケールへの切り替えを行う予定です。

# Managed Prometheus
セルフデプロイのPrometheusのメンテナンスが十分に行えていなかったため、GMPへの移行を現在進行系で行っています。セルフデプロイではあまり気にしていませんでしたが、メトリクス数とpod数（メトリクスのエミッター数）とスクレイピング頻度に比例してmonitoringコストが増えるため、監視に利用していないメトリクスをフィルタしたり、リアリタイム性が必要のないメトリクスでは頻度を下げるなどの対応を行います。各メトリクスの用途とオリジンを特定するために、各エクスポーターのドキュメントや場合によってはソースコードを読み込むことになるため、どこまでストイックにフィルタするかは悩むところです。  
同様に、AlertmanagerについてもGCPマネジドへ切り替えを検討しています。利用はGKEバージョン1.22以降から可能となっているようです。ただし、カスタムテンプレートファイルをマウントする手段が提供されていないため、カスタムテンプレートを利用している場合は、テンプレートを展開するような修正が必要になります。  
GMPのPrometheusフロントエンドはセルフデプロイする必要がありますが、`/graph`以外のUIは利用できないように見えます。`/alerts`でしきい値を超えているアラートやアラート設定ができているかを確認していたので、地味に困っています。何かソリューションはないでしょうか。APIだけでも…と思っていますが、まだ調べきれていません。    
また、これを機にGrafanaの設定をAPI経由のJSON管理からConfigMapでのYAML管理へ切り替える作業も行っています。

# あとがき
ここ数ヶ月間はいままでにないエキサイティングな時間を過ごしました。ストレスもありましたが、新しい知識が得られたりとよい経験を得られたと感じています。特にGCPのサポートケースをはじめ、Googleのみなさんには大変お世話になりました。限られたリソースの中で我々だけでは調査しきれない部分を迅速に解決していただけましたし、英語でのやりとりやディスカッションのハードルもなんとか超えられたので、いままで以上にサポートを利用しようと思うようになりました。
今後の新機能、機能拡充も楽しみですね。