プレイドインターン体験記:エンジニアとしての学びを深めた3ヶ月

はじめに

こんにちは!6月から8月までの3ヶ月間、プレイドでエンジニアインターンとして勤務しておりました伊藤孝太朗です。

私は現在、カナダ・バンクーバーにあるブリティッシュコロンビア大学(University of British Columbia, UBC)で学部3年生としてコンピュータサイエンスを専攻しています。

このブログでは、私がプレイドでインターンとしてどのような課題に取り組んだか、そこから何を学んだかについてお話しさせていただきたいと思います。

どのようにプレイドを知ったか・なぜ応募したか

普段カナダの大学で勉強している私がプレイドのインターンシップを知ったきっかけは、大学の先輩が何人かプレイドでのインターンとして働いた経験があることを聞いたためです。

そこからエンジニアブログなどを読み、プレイドで行われる業務内容の質の高さや、提供されるインターンが成長できる環境を知って興味を持ちました。

私は以前にもエンジニアとしてインターンシップの経験があったのですが、それはクライアントから相談を受け、要望に沿ったプロダクトを開発する受注開発のような業務が主でした。
そのため、自分たちでクライアントが望んでいることを分析し、プロダクトを通して提供できる価値を定義できる、KARTEのような自社プロダクト開発に憧れがありました。

また、ペタバイト単位のデータを常に捌く解析基盤や蓄積された膨大なデータの活用など、技術的にも気になる点が多くあり、リサーチしていくうちにプレイドにインターンとして携わりたいという気持ちが強くなり、ポジションに応募してみることにしました。

応募してからインターンとして勤務を開始するまで

応募してから書類選考を経て、プレイドのエンジニアメンバーの方々と2回ほど面接させていただく機会がありました。

面接では、自分の技術的な関心やこれまでの経験を聞いていただいたり、プレイドという会社そのものについて説明していただく機会もありました。

また、2回目の面接では、「インターンとして働く上で、どのような分野に携わってみたいか」というような希望を聞いていただく時間もありました。

インターンのオファーをいただいてから勤務が始まるまでの期間では、面接で伺った使用している技術についてのキャッチアップを主に行いました。特にBigQueryやBigTableなどGoogle Cloudに関しては、私が興味のある分野であったこともあり重点的に勉強しました。

インターン序盤(最初の1、2週間)

プレイドのインターンシップでは、インターン生はどこかのチームに所属して実際の業務に携わることができます。

私は、KARTEの中でもコアとなる部分を開発するWebチームに迎えていただき、エンジニアとして開発業務を進めていきました。私が開発でよく使用した言語・技術・ツールとしては、

  • TypeScript
  • Vue 3
  • BigQuery
  • MongoDB

などが挙げられ、他にもRedis, Argo CD, Circle CI, Datadogなどは開発中耳にしたり実際に触れることのあるツールでした。

インターン初期の段階では、KARTEというプロダクトの概観を掴んだり、変更をリリースするためのプロセスに慣れるためにシンプルなタスクをいくつか任せていただきました。

例えば、フロントエンドの簡単な改修タスクでKARTEの大きなコードベースの中で変更点をもれなく見つける経験を積んだり、実際にKARTEのユースケースで起こりうるバグの改修タスクに取り組んで、KARTEへの理解を深めることができました。

インターン中盤(開始2週間〜1ヶ月)

プレイドの開発に慣れてきた開始2週間あたりから、KARTE分析機能の拡張に取り組みました。

KARTEでは、ユーザーが特定のゴールを設定し、施策を通してゴールの達成者数やその推移をモニタリング・分析することができる機能があります。私は、その機能の拡張開発に携わることになりました。

この段階でメンターやチームメンバーの方々から希望を聞いていただき、幅広い領域に携わりながらもバックエンドに重心をおいた開発を任せていただきました。

特にKARTEの膨大なデータを活用するためのクエリを書くタスクが多く、自分にとってとても意味のある経験となりました。

印象的なタスク

中でも印象的なタスクとして、イベント間の同日における前後関係を考慮したゴール集計タスクがありました。

KARTEでは、集積されたイベントデータをセッション単位で管理して別テーブルとして保存しています。今回開発した機能では、そのテーブルを活用して事前に

  • ユーザーがゴールとして設定したイベント
  • ゴールに至るまでの中間ステップとして設定したイベント

を抽出し、それらの前後関係を見ながらファネルを集計します。

例として、仮に「商品購入」というゴールと「商品閲覧」という中間ステップを設定した場合、必ず「閲覧→購入」という一方向の流れを担保し、その関係を守っているイベントのみを集計することが求められます。

しかし、開発を進めていく中で、既存のテーブルでは日付よりも詳細な時間に関する情報を持っておらず、同日間の前後関係をみることができない、という課題に直面したため、解決に向けて本タスクに取り組みました。

image.png

このタスクは、

  • テーブルのスキーマを更新し、日付よりも詳細な時間情報をテーブルの各列に持たせる
  • 過去に集計されたデータにも最新のスキーマを反映するため、再集計を行う
  • ゴール機能の集計時に、同日間での前後関係を見られるようにクエリを更新

などの細かいタスクが複合されたものでした。

特に集計やスキーマの変更に関しては、KARTEの分析機能を構築している複雑なテーブル群について理解する必要があったので、メンターの方にKARTE全体図を見ながら解説していただき、構造を把握しタスクに着手することができました。

ここでの開発を通して、「課題を細分化して解決までの道のりをクリアにする」という手法の重要性を再認識することができました。

インターン終盤(開始から1〜3ヶ月)

インターンが中盤〜終盤に差し掛かってくるにあたり、長い期間をかけて検討を重ねながら開発するような、複雑度の高いタスクにアサインしていただきました。

それは、ユーザーが新しくゴールを設定する際に、どのようなファネルになるのかプレビューするための機能開発です。

開発段階の機能では、ファネルを設定してから集計結果を確認するまでに数時間待たないといけないというUXにとって致命的な問題点がありました。これは、集計元となる中間テーブルに作成したゴールの設定を反映する処理がリアルタイムではなくバッチで行われていることに起因していました。このタスクの目標としては、数時間かかっていたプレビューを30秒程度まで大幅に短縮したいというものでした。

データの取得先

まず、私は集計する際に使用するデータの取得元を変更する方法を採択しました。

現状プレビューに数時間かかる理由である中間テーブルを介さず、生のイベントデータから直接集計することで、ボトルネックとなっていた部分の根本的な解消に取り組みました。

私は、イベントの生データが格納されているテーブルから直接集計する方法を考え、クエリを書いて実現可能であることを確認しましたが、今度は「生イベントデータのサイズ」という新しい課題に直面しました。

イベントデータのサンプリング

KARTEに集積されるイベントデータは膨大で、クライアントによっては1日で数百GB程度のデータが蓄積されます。そのため、このデータをフルサイズで参照して集計するのは時間的・空間的コストが大きいことが懸念点となりました。

そこで私は、扱うデータ量を絞ることで実行時間を縮める方法を検討しました。
具体的には、イベントデータをサンプリングすることでデータ量を抑える方策を考えました。

  1. TABLESAMPLE SYSTEM

最初に私が検討したのは、BigQueryが提供するサンプリング機能である、TABLESAMPLE SYTEMです。この機能はテーブルのフルスキャンを回避しながらサンプリングを行うことが可能なため、パフォーマンス・コストの両面において最適な方法と考えました。

しかし、検討を進めるにあたり、いくつかのハードルや懸念点が洗い出されました。

まず、TABLESAMPLE SYSTEMは行ではなくデータブロック(行の集合)をサンプリングし、サンプリングされたブロック全体を読み取る仕様となっています。

image(1).png

そのため、クラスタリングやパーティション化がなされたテーブルに対して行うと、均一なサンプリングにならないという問題がありました。

例えば、日付でパーティションされたテーブルに対して3日分のデータをサンプリングした場合、3日のうちどこか1日のみからデータがサンプリングされるという下図のような事象が起こります。

-- サンプリングしたテーブルから、日付ごとにレコード数をカウントするクエリ
SELECT
	date, COUNT(*)
FROM 
	sample_table TABLESAMPLE SYSTEM (10 PERCENT)
WHERE
	date BETWEEN '2024-07-01' AND '2024-07-03'

image(2).png
date列でクラスタリングされているテーブルでは、取得されるレコードの日付に偏りが見られる

このサンプルデータの不均一は、プレビューにおいては重大な欠陥となり得ることがわかりました。

さらに、フィルタリング処理をサンプリング前に行うことができない点も懸念点でした。

例として、本タスクで私は、メンターやチーム内外のエンジニアの方々との会話から、
「ゴールの達成者数をプレビューするという機能の特性上、ユーザー単位でのサンプリングが適切ではないか」というアドバイスをいただき、プレビューの正確性を担保するために「全イベントのn%」ではなく、「全ユーザーのn%が行ったイベント」という形でのサンプリングを行うことを目指していました。
そのため、イベントデータをユーザーによって絞り込んでからサンプリングができないこの仕様は、大きな技術的課題でした。

  1. RAND()

当初最適とみられた方法で課題に直面したため、代替案の検討を始めました。その結果、RAND関数をサンプリングに活用することにしました。

image(3).png

前述のTABLESAMPLE SYSTEMと比較しても、フルスキャンが必要というデメリットがあるものの、

  • 行ごとにサンプリングされるため均一な散らばりのサンプルが撮れる
  • フィルタリングも適用可能

というような利点があり、本タスクの要件を十分に満たしていると判断しました。

サンプリング割合の調整

サンプリングの方式は確定しましたが、そこでまた1つ新しい課題が生まれました。

KARTEを利用するクライアント間においてデータ量に差があるため、固定の割合でサンプリングするとサンプルデータのサイズ、ひいてはプレビューの実行時間に開きが出てしまうというものです。

メンターの方と話し合い、各プロジェクトごとにイベントデータのサイズを取得し、実行時間が30秒におさまるようなサイズを閾値として、サンプリングの割合を動的に調整することにしました。これにより、全てのクライアントに対してプレビューにかかる時間を均一化することができました。

クエリ結果の再利用

また、パフォーマンスの向上にあたって、結果をなるべく再利用して無駄な計算を防ぎたいと考えました。

今回再利用したいと考えたのは、主に次の2つです。

  • サンプリングしたイベントデータ
  • 各チャート生成に必要なクエリに共通する部分
  1. サンプリングしたイベントデータ

サンプリングしたイベントデータは、プレビュー前に設定する情報に依存しません。固定された集計期間のイベントをサンプリングするだけなので、集計期間が同じであればどのプレビューにも再利用可能です。

今回、プレビューの集計期間を「前日から数日間」という期間で固定したため、当日中であれば再利用可能だという結論に至りました。

その再利用性の高さから、テーブルとしてこのデータを管理し、日次ジョブでこのデータを新しい集計期間のサンプルデータで上書きすることにしました。これによって、サンプリングをする際のフルスキャンというパフォーマンス上のデメリットがプレビューの実行時間に影響しないようにすることもできました。

  1. クエリに共通する部分

一方、各クエリの共通部分はプレビュー前の設定情報に依存するため、その一回限りの再利用となります。

そのため、上記のイベントデータのようにテーブルにして管理するという案はコスト面を考えても過剰な対策だと思い、別の可能性を検討しました。

まずは、BigQueryのビューでこの課題を解決できるかリサーチしました。しかし、ビューは参照のたびにクエリが実行されるという性質を保つため、再利用という目的からは外れたものでした。

そこで、次に「実体を持つビュー」である、マテリアライズドビューを検討しました。これはビューと違って保存された結果を参照できるので、パフォーマンス面の向上が見込めます。当初はこの案を採用しようと思ったのですが、マテリアライズドビュー生成時に分析関数が使用できない、などの技術的ハードルに直面してしまいました。

検討した案にそれぞれ課題が見つかり行き詰まってしまった中で、さまざまな方からアドバイスをいただき、自分でもリサーチを進めていく中で、これまでKARTEでは使用していなかったマルチステートメントクエリに目を向けました。

マルチステートメントクエリは、複数のクエリを単一のジョブとして実行することができるものです。それぞれのクエリには子ジョブとしてIDが与えられ、それらを包括する親ジョブに紐づけられています。これを利用して、すべてのクエリをまとめて1つのジョブとして実行することが可能になりました。

さらに大きなメリットとして、マルチステートメントクエリ内で作成した一時テーブルは、その中で再利用可能であるということです。そのため、「クエリの共通部分を一時テーブルとして保存し、各クエリから参照する」という方式をとることで、クエリの大部分を再利用可能にしました。

-- TEMP TABLEを作るクエリ
CREATE TEMP TABLE demo AS
SELECT
	user_id, session_id
FROM
	sample_table
WHERE
	date BETWEEN '2024-07-01' AND '2024-07-03'
;

-- 作成したTEMP TABLEからuser_idを取得
SELECT user_id FROM demo;

-- 作成したTEMP TABLEからsession_idを取得
SELECT session_id FROM demo;

マルチステートメントクエリの例

メンターをはじめ、チーム内外のさまざまなエンジニアの方々にアドバイスやフィードバックをいただいて、複数の側面について検討を重ねた結果、当初数時間かかっていたプレビューの時間を15秒弱まで短縮することができました。

最後に

3ヶ月という短い期間でしたが、複数の異なる開発スタイルに触れ、高い技術力を持つエンジニアの方々に刺激を受け続けることのできた、学び多く楽しいインターンシップだったと感じています。

粒度の小さいタスクを短いスパンで多くこなしていく「数」の開発と、複雑で検討が必要な大きなタスクに取り組む「質」の開発に並行して携わることで、異なる開発スタイルに同時に触れることができたため、エンジニアとしての柔軟性や対応力が上がったと感じます。

また、1つのタスクに対する掘り下げや検討の仕方についても、これまでとは違う経験を積むことができました。「ただ実装するだけ」ではいくつもの選択肢が存在しますが、その中からパフォーマンス・コスト・要件といった複数の要素を考慮して最善の方法を選びとるプロセスを学びました。職種に縛られないような、ビジネスや課題解決一般におけるコアとなる考え方が根付いた環境に3ヶ月通して身を置くことができ、とても貴重な体験となりました。

何よりも、自分の希望をできる限り聞いてくれ、必要とすればサポートをすぐに受けられる体制の中で、周りの方々に助けていただいて成長することができました。大変と感じるような開発に際しても、さまざまな方にサポートいただいて楽しく取り組むことができました。Webチームの皆様をはじめ、インターン期間中に関わった全ての方々に感謝しています。