.png?tr=w-1000,h-1000,c-at_max?tr=w-1000,height-1000,c-at_max)
プレイドインターン体験記:大規模システムのリアーキテクチャと負荷検証
Posted on
こんにちは、藤井駆陸です。普段は化石について研究している大学院生です。9月の間、プレイドのJourneyチームにてインターンに参加しておりました。
振り返れば一瞬に感じられた1ヶ月でした。頭を抱えながら巨大なアーキテクチャの概要を掴み、タイムゾーンとSQLに苦戦しながら多くの実装をし、検証用環境を追い込んで問題点を洗い出す。そんな目まぐるしい日々でした。
この記事では、実際に関わったタスクや、インターンの率直な感想についてお伝えできればと思います。
導入
インターン参加の経緯
最初のきっかけは、プレイドで働く友人から、仕事がとても充実していると聞いたことでした。
面談や面接でお話を伺う中で、カルチャーなどに加えて、技術的に難易度の高い大規模データを扱う挑戦に魅力を感じました。
「自分もこの環境に飛び込み、挑戦したい」と強く感じ、このインターンシップに参加しました。
Journeyとは?
プレイドが提供するプロダクト「KARTE」において、ユーザーの辿る行動に応じて、異なる施策を実行することができる機能です。例えば、ECサイトで「カートに商品を入れたまま迷っているユーザーに、2時間後にクーポンの通知を送る」といった、ユーザーの状況に応じたシナリオを自動で実行してくれます。
現在、Journeyチームでは配信基盤周りの改善を行なっています。その中核としてユーザーのイベントが発火した瞬間に処理を行う「イベント処理基盤」と、指定時間の経過や条件の評価などをバッチ処理で行う「定期実行基盤」を開発しています。
今回は「定期実行基盤」にまつわるタスクを行いました。
業務内容紹介
時間経過の判定の処理のリアーキテクチャ
Journeyにおいてシナリオは、ユーザーの行動や通知の配信などといった1つ1つのステップに分けられます。その中には、「指定時間までユーザーを待つ」といったものもあります。そこでは、「2時間後」「毎週金曜の18:00」「月末の12:00」など、様々な条件でユーザーが留まる時間を指定することができます。
定期実行基盤の主要な部分として、各ステップの遷移対象となるユーザーを抽出するコンポーネントと、遷移対象のユーザーを実際にデータベースに書き込んで次のステップへ移すコンポーネントがあります。
従来の実装では、データ更新を担うコンポーネント(下図右側)が、ユーザーを待つ処理がある時には「ユーザーの待ち時間の終了時刻を計算する」という処理も行なっていました。本来書き込みに専念すべき場所に、時刻計算のビジネスロジックが混在して責務が曖昧になり、全体の見通しの悪さが課題となっていました。
そこで、遷移を実行するコンポーネントで書き込み処理に専念できるよう、遷移対象を抽出する段階で動的にSQLを生成するように実装を変更しました。*1
具体的には、TypeScript側で「現時点でいつの時刻まで流入してきた人が遷移対象となるべきか」を計算し、それを用いてクエリを書くといった実装でした。
*1: BigQueryのUDF(User defined function)も検討しましたが、行数を少なく実装できる一方で可読性やメンテナンス性が低かったため見送りました。
大変だったことは、網羅すべきパターンが多く、単純な計算なのにも関わらずコード量が膨大になったことです。初めはパターンごとにクエリを実装していましたが、SQLの構造が2つのパターンに集約できるのを見抜いて、計算部分を切り出しました。まだまだSQLの動的生成の可読性は課題ではあるものの、コードの見通しはある程度良い状態で実装ができました。
実装の細部にまで目を配ってレビューしていただき、非常に勉強になりました。例えば、待ち時間のパターンに対して Discriminated Union の導入を提案いただいたことで、より型安全に整理できました。このように、今まで知らなかった技術を活かして実践的なコード改善に繋げるという貴重な体験は、自身の成長に大きくつながりました。
定期実行基盤の負荷検証
次に、クラウドインフラのリソースを増やすことによって定期実行基盤が本番トラフィックを処理可能か、そのスケーラビリティを検証するための負荷テストを行いました。
目標実行時間を2分半以内と設定し、大量の検証用データを投入してテストを開始したのですが、現実は甘くありませんでした。実行時間は常に目標を大幅に上回り、ボトルネック探しの調査が始まったのです。
以下の流れで負荷検証を行いました。
- データ大量投入スクリプトの準備
- 検証
- デプロイ設定の変更(複数回)
- Datadogメトリクスの導入
仮説1:ユーザー抽出時のリソース不足
まず疑ったのは、コンピューティングリソースの不足です。
実際に、テストの初期段階ではcpu使用率が頭打ちしていて、メモリにも余裕がない状況でした。GKEのpod数を10程度まで増やしてリソースを潤沢に確保しました。しかし、CPU使用率は安定したものの、実行時間は依然として5分を超過したままでした。
仮説2:データベース処理の遅延
次に目を向けたのは、データベース、特にBigQuery(BQ)の処理です。Temporalのワークフローの実行記録(上の画像)から、BQでの一時テーブル作成やSpannerへのエクスポートの処理に時間がかかっている可能性が浮かび上がりました。
これを検証するため、Datadogで各BQクエリの実行時間を計測可能にしました。その結果、クエリの実行時間は最長で10秒程度であることがわかりました。5分以上の実行時間を説明するには不十分でした。
真相:直列実行の罠
八方塞がりかと思われた中で、GKEのログを見てみたところ、驚くべき事実が判明しました。
調査の結果、検証環境では本来並列で実行されるべき多数のタスクが、なぜか直列に一つずつ処理されていたのです。すなわち、BQやSpannerのIO待ちが積み重なっていたということです。
今回のボトルネックの正体は、「デプロイされた環境における並列処理の不具合」だったのです。
最終的に問題を完全に修正するところまで至りませんでしたが、この調査プロセスは非常に大きな学びとなりました。
「仮説を立て、データを用いてそれを検証し、次の打ち手を考える」というサイクルを高速で回すことの重要性を肌で感じました。巨大なシステムにおける問題解決のリアルな一端に触れられた、非常に価値のある経験でした。
インターン生活について
プレイドでの働く環境
全体的にオープンな雰囲気で、誰にでも気軽に質問や相談ができました。特に印象的だったのは、タスクに関する何気ない相談が、次第にプロダクトの設計といった、より深い議論に発展していく光景でした。1人の思考がチームとしての思考に拡張されてゆくような、新鮮な体験でした。
また、ランチの時間には他のチームの方とも交流することができ、毎日楽しく過ごすことができました。インターン生同士や新卒の方などとの繋がりもできてよかったです。
インターンを通しての学び
この1ヶ月で、エンジニアとして多方面にわたり大きく成長できたと感じています。
実装における細かい技術的な側面から、プロダクトの提供する価値といったビジネス的な側面まで、幅広い視点でインプットできました。
その中でも特に、データを大量に投入してパフォーマンスを観察するといったことは、自分一人ではできない新たな経験でした。大規模なデータを処理するアプリケーションのインフラの一端を覗くことができ、非常に勉強になりました。
最後に
この1ヶ月は、手厚いサポートを受けながらも手応えのあるタスクに没頭することができ、非常に充実した時間でした。改めて、Journeyチームの皆様には心から感謝しています。
このインターンは、刺さる人には深く刺さるものだと感じています。「手応えのあることに挑戦したい」「新しい技術に触れてみたい」といった方には最高の環境です。
そのワクワクするような経験を、ぜひ次はご自身で体験してみてください。