ニコニコ動画のコメントサーバーを引っ越した時の話


こんにちは。ニコニコ動画開発の多胡です。

今回は PHPerKaigi2023 向けの記事として、2021年に実施したニコニコ動画のコメントサーバーをお引越しした時のことを書いてみたいと思います。

文中の 5 つのフレーズをチャレンジトークンとしてみました。ぜひ探してみてください! (※ 記事の見出しにの横についている「#」はチャレンジトークンではありません。チャレンジトークンは文中に配置されています。紛らわしくてすみません!)

背景

実はニコニコ動画の #コメントサーバーを引っ越した のはこの時が二度目でした。

一度目は2014年から2016年にかけてのプロジェクトでファイルベースのシステムからの引っ越しでした。このファイルベースのシステムは、ニコニコ動画生誕当時から利用されており、当時のコメント参照や投稿にはすでに耐えられない状態になっていました。

そこで、このファイルベースのシステムから HBase を用いたクラスタベースのシステムに引っ越しを行いました。
このシステム刷新により致命的な状況から脱却することができましたが、互換性を優先したため独自のメッセージ形式でのやり取り方式は変わらないためクライアントの特殊なパース処理は残ったままであり、また動画だけではなく生放送などでも利用できる汎用的なシステムを目指したため機能追加の柔軟性が失われたりしていました。それでも当時の開発チームが頑張って「ニコる」などの機能追加も行われました。
このクラスタベースのシステムは前述の問題を抱えながらも大きな問題もなく稼働していたのですが、データセンターの移転を行う必要が出たタイミングでクラスタ構成のための特殊機材を調達することが難しく、一般的なアーキテクチャで構成されたシステムに乗り換える必要が出ました。

これが2021年のコメントサーバーの引っ越しを行うことになった背景です。

アーキテクチャ

データセンターの移転という期限に加えて管理主体が動画開発チームになった[1]ため、動画開発で開発・運用ノウハウが溜まっている PHP および MySQL を主とするシステムアーキテクチャを採用することにしました。
また、PHP はフレームワークとして Laravel を採用し、Eloquent や Laravel キューを用いることで開発速度の向上を目指しました。
なお、PHP のバージョンについては開発着手当時は PHP8.0 がちょうど出たくらいでしたが、安定度や Laravel の対応状況から PHP7.4 を当時は採用しました。

パフォーマンス

期限に間に合わせるために PHP を採用しましたが、引っ越し前のシステムは C++ で書かれておりパフォーマンス的に不利でした。とは言え、システムとしては DB から取得したコメントを返すという部分がほとんどであったので、全体の処理時間から見たプログラムパフォーマンスの占める割合は多くないとも考えていました。

しかしながら実際には「投稿者用 NG」というループ処理が多用される機能があり、これについて事前に検証用コードを作成し検証したところ致命的なパフォーマンス劣化[2]が確認されました。
検証用の4000件程度のコメントに対して2000件の NG 設定を反映させるようなフィルタ処理を行うと PHP では 2 秒弱かかってしまったのです。
これは小手先の対応ではどうやっても解決できそうになかったため、他の言語で処理することを検討しました。とは言え、チームのノウハウを考えると期限までに PHP 以外の言語を利用したシステムを作り上げて安定稼働させることは難しく、この部分の処理のみを移譲させることにしました。

他言語に部分的に処理を移譲させるための仕組みとして以下の3つを検討しました。

  • PHP-FFI を用いて C 言語で処理を行う
  • goridge を用いて Go 言語で処理を行う
  • ローカルに gRPC サーバーを立て、そこにリクエストを投げて処理を行う

PHP-FFI を用いて C 言語で処理を行う

FFI は Foreign Function Interface の略で、ある言語から他の言語の処理を呼び出す仕組みで Java など PHP 以外の言語にも存在します。 PHP の場合は基本的に FFI を用いると C 言語を呼び出すことができ、PHP7.4 から実装されています。
C 言語で書いた処理を FFI で呼び出すようなテストコードで試したところ、実際に処理速度は90%ほど改善しました。
しかしながらマルチバイトの取り扱いに不安があったり、PHP 側でメモリを大量に消費した際に FFI が不安定になることが観測できたなど懸念があったため採用しませんでした。
(なお、前述の C 言語でのテストコードもマルチバイトがうまく考慮できていない状態だったので参考程度の値となっています)

Goridge を用いて Go 言語で処理を行う

Goridge とは PHP と Go 言語を RPC でブリッジするためのライブラリです。これを用いて Go 言語に処理を移譲させることでパフォーマンス向上を狙いました。
こちらも結果としては処理速度が92%程度改善し、かつマルチバイトも問題なく取り扱えました。
実は検証した中ではこの goridge を用いた方式が一番パフォーマンスの良い結果となりました。これは goridge が極力オーバーヘッドを無くした感じの実装になっているためで、gRPC のようにスキーマ定義などを挟んでいないためと考えられます。
裏を返すと連携については独自のプロトコルであるためロックインされてしまうというリスクがあるため、チーム内で議論した結果採用を見送りました。

ローカルに gRPC サーバーを立て、そこにリクエストを投げて処理を行う

最終的に採用したのはこちらの方式でした。
この方式は Go 言語で書いたフィルタ処理を含む gRPC サーバーを PHP サーバーのローカルに立て、そこにリクエストを投げて処理をさせるというものです。
結果として goridge を用いるケースにわずかに劣りますが、PHP での処理からはパフォーマンス向上が行えることが分かりました。
この方式であれば goridge と違いロックインされることもなく速度向上も行え、また gRPC によるつなぎこみ部分のコードの自動生成などの恩恵も受けられることなどから今回はこの手法を選択することにしました。

本番での稼働

パフォーマンス的な大きな問題を片付け、PHP および一部 Go 言語を用いるというアーキテクチャで開発は順調に進んでいき、実際に本番で稼働させられる状態まで無事持っていきました。
今回の引っ越しはシステムの実装とは別に、既存データの移行や新規投稿される動画のコメントの向き先を変えるなどの作業も必要でした。
実装した新規システムの安定稼働実現のため、以下の順序で本番稼働を始めました。

  • phase1:一部の新規投稿動画のコメントを新システムへ
  • phase2:すべての新規投稿動画のコメントを新システムへ
  • phase3:既存のコメントを徐々に新システムへ移行[3]

phase.1 が始まり順調に動いているなと思っていたのですが、受け持つ動画数が増えていくと想像以上に負荷が高まっていき、またレスポンスタイムが悪化しているケースが出てきました。
そこで #みんな大好きXhprof を用いて原因究明に乗り出したところ、Laravel の Eloquent で利用している Carbon (厳密にはそこから呼び出される DateTimeZone)が悪さをしていることが分かってきました。
Laravel の Eloquent では、create_at や update_at などのカラム情報は Carobn クラスで値を取得することができ、これはフォーマット変換が容易だったりと非常に便利なため新システムでも利用していました。
しかしながら create_at や update_at といった情報はすべてのコメントに存在する情報で、これを取り扱うと各コメントごとに Carbon インスタンスが生成されることになります。要は4000コメントなら4000個の Carbon インスタンスが生成されます。
そしてこの Eloquent が生成する Carbon インスタンスは変化しない DateTimeZone が毎回初期化されており、これが処理時間の大半を占めていたのです。[4]
これを改善すべく、Carbon で取り扱っていたところを DateTime オブジェクトで取り扱うようにしたところ、負荷も無事下り、 #パフォーマンスも倍ほど改善 しました。

その後は致命的な問題もなく、無事全動画のコメントを移行し、コメントサーバーの引っ越しを完遂することができました。
引っ越しプロジェクトから少し後にはコメント投稿者の NG 永続化などの新機能も追加や、旧来の独自のメッセージ形式の I/F から json ベースの I/F への変更などができ、稼働も安定しています。[5]

おわりに

この引っ越しプロジェクトでは新システムの開発メンバーはもちろん、旧システムのメンバー、クライアント開発の方々、企画担当者の方々など多くの方の協力をもって進めることができました。この場をお借りして改めてお礼申し上げます。
また、引っ越し期間中はコメントの投稿や表示が行えない動画があったりなど、ユーザーの皆さんにもご協力頂きました。ありがとうございました。

今振り返ってみても PHP(Laravel)をベースに一部 Go 言語というアーキテクチャを選択したことは、期限厳守や安定稼働の観点からも当時としてはベストな選択だったと思っています。
また、ニコニコ動画システム本体も PHP で運用が続けられていますし、引き続き PHP と共に歩んでいこうと思っています。

皆様も #良きPHPライフ をお送りください!

[1]: それまでのシステムは専用のチームで開発、運用、保守を行っていました。
[2]: 「投稿者用 NG」機能とは、動画の投稿者が「NG ユーザー登録」や「NG ワード登録」によってコメントを非表示にする機能なのですが、これはサーバーサイドで各コメントの本文や投稿者 ID を正規化しながらチェックする必要があり、コメント数×NG設定数のループとなるためボトルネックとなっていました。
[3]: 既存のコメントの移行は、引っ越し前のシステムで HBase から json を出力し、それを引越し先のシステムでパースしながら DB へ INSERT していく、というような引っ越し用のバッチを作成することで実現していました。(この json はとても巨大なことがあるためストリームでパースする必要があるなど、やや工夫が必要な点があったりしましたが今回は割愛します)
[4]: どうやら DateTimeZone が内部的に timezone を検索する際に無駄な検索が走っていたなどのようで、それが影響していたものと思われます。なお、この問題は PHP8.1 で修正されているため現状は Eloquent で Carbon を取り扱ってもあまり問題にならないかもしれません。原因調査の中でこちらの記事を参考にさせて頂きました。ありがとうございます。
[5]: これらの機能追加や改善は PHP および Laravel を用いたことで、引っ越し完了後からそんなに時間をおかずに実施することができました。


ドワンゴでは、#ニコニコ動画を一緒に作ってくれる仲間を募集 しています。
PHP や Go 言語に興味ある方、ドワンゴに興味がある、または応募しようか迷っている方がいれば、気軽に応募してみてください。