PLAIDのインターンシップでリアルタイムユーザー解析基盤の移行に携わった話

こんにちは、エンジニアの土佐です。

PLAIDには2021年の3月からインターンとして開発に携わり、2022年度にソフトウェアエンジニアとして新卒で入社しました。

この記事では、一年間開発インターンとしてどのような開発を行っていたのかについてお話ししていきたいと思います。

自己紹介

大学2年生の頃からプログラミングにハマり、趣味で自作のWebアプリケーションを作成したり、サマーインターンシップや開発バイトやISUCONへ参加もしたりしていました。
基本的にコードを書くのが大好きな人間なのでひたすらコードを書いています。
特技は反射的に駄洒落を言うことです。

PLAIDのインターンに参加した理由

開発を続けていく中、また同世代のエンジニアと話している中で、自分の技術に対する知識不足や開発力の未熟さを自覚するようになり、技術的に尖った環境の中で自分の力を試しながらより難易度の高い課題を解決していきたいと思いインターンシップを探していました。

そんな中で自分が運用している学生のエンジニア向けサービスでPLAIDの名前を見つけて調べてみたことが初めの応募のきっかけでした。

PLAIDはインターンに求められている水準が非常に高いことや、すぐに運用しているプロダクトの開発チームに配属させてもらえること、何よりサイト上のユーザーの動きを把握して、よりよい提案や行動をサポートできるKARTEと呼ばれるサービスに興味を持ったので応募しました。

KARTEの仕組み

KARTEとは、サイトやアプリの訪問者の行動や感情をリアルタイムに解析し、
一人ひとりに合わせた体験の提供を可能にするCXプラットフォームです。

初めに、自分が開発に携わったKARTEがどのようにサイトやアプリの訪問者の行動を解析しているかについてお話しします。

Step1. タグの取得

初めにwebサイト上にtracker.jsというサードパーティスクリプトを呼ぶ scriptタグを埋め込んでもらいます。

サイトが表示された際にCDN上からサードパーティスクリプトの取得を行います。

Step2. イベントの送信

読み込んだサードパーティスクリプトを実行して、クライアントのサイト上でのユーザーの行動ログをイベントとしてサーバーに送信します。イベントには具体的にはサイトを読み込んだ際の閲覧イベントや商品を購入した際の購入イベントなどがあります。ユーザーが自由にイベントの内容をカスタマイズすることができます。[参考]

Step3. リアルタイム解析

イベントはサーバーに送信されてリアルタイムに解析され、ユーザーの現在の状態を把握します。

解析基盤には秒間約13万件のイベントが飛んできています。

Step4. アクションの配信

解析結果に応じてユーザーに対して接客アクション(ポップアップやバナー)を配信します。

例えば、購入のイベントが起こった際に以前までどの商品を閲覧していたのかの閲覧イベントを解析して興味がありそうな商品のクーポンを配信することができます。

スクリーンショット2022-05-3022.29.47.png
ランディングページより接客アクションの例

以上が、KARTEの主な機能です。

解析基盤とタグの移行

自分はKARTEのコア機能であるリアルタイム解析基盤を刷新するチームに配属されました。

刷新する動機としては、KARTEの初期からずっと開発し続けているためシステムが古くなり機能追加・変更のコストが掛かるようになってしまったこと、現在のアーキテクチャだと解析基盤のパフォーマンス・コスト改善に限界があったことがあげられます。

新しく作り替えるにあたってより高速な解析ができ、接客アクション、タグも刷新して省サイズで配信できるようにすることを目標として移行が開始されました。

インターンとして携わった機能

移行の中で私は主に現在の接客アクションの互換性を保つための開発に携わりました。

新解析基盤での旧接客アクション形式の配信

新解析基盤では、新接客アクション形式のもののみ配信できる状態でした。

新接客アクションは新しいタグでしか動作しないため、タグを入れ替えなければ新解析基盤が移行できない状態でした。タグを入れ替えるのは時間とコストがかかります。そこで旧タグとの互換性を保ちながら移行できる状態を作成しタグを入れ替えなくても解析基盤側だけは移行できる状態を目指すことになりました。

スクリーンショット2023-03-0315.39.33.png
移行の概要図

互換性を保ちながら移行するため、まずは新解析基盤から旧接客アクション形式のアクションを配信できるようにする必要がありました。

接客アクションには、ユーザーの解析結果などを変数として接客アクションの中に埋め込むことができるテンプレート機能と呼ばれる機能があります。

旧接客アクションのテンプレート展開の機能を新解析基盤に移行させる必要があったのでこの部分の移行を担当しました。

旧解析基盤が言語としてはNode.jsで書かれていましたが、新解析基盤はJavaで書かれています。旧解析基盤ではテンプレート展開のためにLodash、Nunjucksなどのライブラリが使用されており、ロジックも複雑になっていたため、Javaへ書き換える移行が難しいと判断しました。

そこで、旧解析基盤のテンプレート展開部分の機能をサーバーとして切り出し、新解析基盤側から必要な変数やユーザーデータを渡して、展開してもらう形で機能を実現しました。

実装を行う前にKARTEを使う人が読むKARTE Developer Portalと呼ばれるサイトに機能がまとめられているのでここで概要を把握し、どのように実装されているのかを調査するためコードリーディングを行いました。

一通り終えると、必要な機能を網羅できるようにリストアップしてきちんと変数が展開されているかどうか?変数の値は正しいかどうかをテスト用の接客アクションを用意して、逐一動作確認をしながら足りない機能を補っていきました。

コードリーディングの際にお世話になったripgrep。埋め込む変数名で調べて使用されている箇所やどこからデータを取ってきているのかを把握する際に使用しました。

スクリーンショット2022-05-3022.43.13.pngdeveloper portalのユーザー情報変数の記載:リンク

スクリーンショット2022-05-3022.46.59.png
ユーザー情報変数以外にも使用できる変数があるのでこちらも忘れずに移行

旧接客アクションのテンプレート展開を行い、新解析基盤から無事に既存の基盤とレスポンスの互換性を保ちながら配信することができるようになりました。

配信ができるようになった後、解析基盤移行に伴い既存の解析基盤とのレスポンスに差分がないかどうかを確認するミラーリング環境をチームで作成していました。新解析基盤と旧解析基盤のレスポンスが比較できるようにBigQueryに保存されていたので、クエリを書きテンプレート展開に差分が生じて配信していた接客アクションのバグを潰していきました。この作業のおかげで最終的にほぼ差分がなくなり移行がスムーズに進みました。

大変だったこと

新旧両方の仕様の把握が大変でした。また、ミラーリング環境でレスポンスの差分を調査している際にその原因が、マイグレーション上の実装の問題なのか、解析の結果としてのズレなのか、アルゴリズム上ランダムなものや時刻によるものなのか?を判断していくことにも時間がかかりました。

スクリーンショット2022-05-3023.20.54.pngMongoDBに保存されているActionの中身から実際に実行の際に使用されている変数を調査したりしていました。最終的には削るのではなく全ての変数を返却するような実装になりましたが、どの変数がどのようなことに使用されているのかgrepしつつ、社内ドキュメントを漁ることで知識がついてきました

client-action.png

スクリーンショット2022-07-2110.40.19.pngmirroringしている際のレスポンスの検証内容

インターンをしている際に大きな移行をする際には現状のメトリクスの把握と可視化が非常に大切だと実感しました。開発チームのこのような環境整備を行いその後改善していくプロセスがすごく学習になりました。

旧接客アクション形式を新タグで動作させられるように移行

上のセクションの新解析基盤での旧接客アクション形式の配信を実装したことにより、タグを移行しなくても裏側が新解析基盤に移行したとしても互換性を保って動作させられる状態にすることができました。

次に自分が取り組んだことは、旧接客アクションと新接客アクションでは表示する形式が異なっているため、新しいタグ(新タグ)を埋め込んだ際に旧形式の接客アクションが正常に動作させられるようにするための機能の作成を行いました。これにより、タグを移行しても今まで使用できたいた接客サービスがそのまま使用できるようになります。

新接客アクションはJavaScript(TypeScript)で書くことができ、アクションはビルドされてCDN上に置かれます。新解析基盤からその新接客アクションを保存しているURLとテンプレートに埋め込むことができる変数を渡します。タグはURLを用いてアクションを取得して、変数を埋め込んで実行する形になっています。

それに対して旧接客アクションでは、アクションの内容物(html、js、css)を変数がサーバー側で埋め込まれた状態で解析基盤側から返却されます。

新接客アクションではCDNから取得してきた後、以下のような関数を実行する規約により動作するようになっています。任意のJavaScriptの処理を記述できるため柔軟性が非常に高い設計になっています。

  • 新接客アクションの内容
// このactionがタグから呼ばれる
const action: KarteAction = (options) => {
	// JavaScriptの処理
}

上のようなJavaScriptファイルをビルドしてCDN上に置いています。

  • 旧接客アクションのイメージ
{
  html: "<htmlのコード>"
  js: "<jsのコード>"
  css: "<cssのコード>"
}

旧接客アクションではhtml、JavaScript、cssをレスポンスとして返却していました。

この仕様を利用して新接客アクション形式の中で旧接客アクション形式のものを動かすことにより移行の実現を行いました。具体的には新解析基盤側から変数展開済みのhtml、js、cssを新接客アクション形式の変数として渡します。新接客アクションは渡されたhtml、js、cssを用いて旧接客アクションのランタイムの機能を使用して実行することにより動作させるようにしました。

対応すべき問題として旧接客アクションから旧タグの機能を呼び出し可能ということがあげられました。そこで、旧タグと互換性のあるインターフェースを持っているクラス(コード内のDummyTracker)を作成し、DummyTracker内ではその処理を新タグの対応する機能にproxyするようにして実現しました。

DummyTrackerを実装するために各種APIの現在の動作を理解するために、現タグのコードを隅から隅まで理解できる限り読み込みました。このおかげで旧タグ周りでは社内の人の中でもかなり詳しくなりました。

このコードをビルドして新接客サービスと同様にCDNに置きます。解析基盤側からは旧接客アクションであるならばCDN上のこのコード参照するURLと、テンプレート展開済みのhtml、css、 JavaScriptを渡すことにより新タグ上で実行することができました。

const action: KarteAction = (options) => {
  // 旧アクションのランタイム
  const widget = new Widget() as WidgetType;
	// 接客アクションの内容物は解析基盤から配信される。
  const action = options.variables.legacy_message.action;
	// 現在のタグのインターフェースを模倣するもの。受け取った処理は新tagにproxyされる。
  const tracker: Tracker = new DummyTracker(options);
  const decode = decodeURIComponent(action.content?.script);
  (async () => {
		// 返却されたscriptを実行する。
		// 指定されているライブラリ等を使用できるように埋め込んでいます。
    const Runner = new Function(
      "window",
      "widget",
      "tracker", // 現在のタグのインターフェース
      decode
    );
    let dummyWindow: any = window;
    dummyWindow.tracker = tracker;
    Runner(dummyWindow, widget, tracker);
  })();
  const close = () => {
    if (widget) {
      widget.hide()
    }
  }
  return close
};

スクリーンショット2022-05-3023.12.15.png

外部に露出しているメソッド、アクションからも使用できるので対応が必要でした。

この規模のマイグレーション[1]に携われるのはインターンとしてどころかエンジニア人生の中でなかなかないできることのない経験だったので非常に面白く、うまく機能が移行できた時にはPCの前でガッツポーズをしていました。

PLAIDのインターンとして学べたこと

領域を問わない技術の知識

入社当時はどちらかといえばサーバーサイド周りは触ったことがありましたが、フロントエンド周りはJavaScriptがかけるくらいの温度感でした。PLAIDに入社してから、Webpack, Rollupが何をやっているのか?や、TypeScriptがどの様に使用されているのか?など実践を通しながら理解することができました。今回の移行でもサーバー、クライアント領域を問わずに開発に携われたことでかなり開発力が身についたと思います。

プロダクト・ドメインに対する理解の大切さ

移行作業に関してプロダクトのドメイン知識や仕様がわかっていないとコードが書けないという当たり前ですが、とても大切な事実に気がつくことができました。インターンシップに参加して最初の三ヶ月間はミーティングに出てくる用語が全くわからずに苦労していました(KARTEで使用されている用語: 接客アクション、セグメント、ディメンション、紐付けテーブル etc...)。プロダクトで使用されている用語を理解するために、わからない用語が出てきた際に社内ドキュメントを漁る、実装しているソースコードを読んで理解を深める、人に聞くことにより知識をつけることを意識的に行いました。この試みにより3ヶ月目以降は段々とミーティングの会話について行けるようになりました。今から、振り返って考えてみるとプロダクトの理解を早める一番の方法はプロダクトによく触ることだと思います。一部の機能の開発者であっても自分達が作成しているプロダクトがどのように動いているのか逐一触って動かして試してみることが、プロダクトの仕様把握をスムーズに進める方法なのではないかと考えます。
マイグレーションに携わる際には現在の仕様と新しい仕様の両方を把握する必要がありなかなか大変でした。仕様を正しく理解できないものは作れない・修正できないをとてもよく実感することができました。

技術的に尖った課題の解決のアイデア

新解析基盤、新タグ、新接客アクションのどれも社内のエンジニアの知識に裏付けされた非常に面白いアイデアに溢れていました。長くなるので技術的詳細には触れられませんが、ユーザーからの秒間13万件のイベントをどうやって強整合でリアルタイム解析を行なっているのか?新接客アクション、タグのscriptサイズを限界まで削りクライアント側のサイトやユーザーにいかに負担をかけずに体験を良くしてるのか?コードを読みながら学べることがとても多くてかなり楽しかったです。初めのキャッチアップで1on1でアーキテクチャ図の説明をしてもらった時は面白くて興奮しましたし、暇さえあれば社内システムのコードリーディングを行っていました。技術を学習して実践する大切さやモチベーションに繋がりました。

社内のエンジニアとのコミニュケーション

技術的なことばかり話してしまいましたが、インターンの最中も社内のエンジニアさんとコミニケーションが取りやすく一緒にお昼めぐりなどをして会社の様子から趣味の話まで色々お話ししていました。エンジニアさんと話していくうちに、尖った技術力を持っている人、ユーザーの課題を解くことに注力している人、一言にエンジニアと言っても色々な指向性があるんだなとキャリアに対しても解像度をあげることができました。

まとめ

PLAIDのインターンシップはいい意味でインターンとしてではなく一人のエンジニアとして際限なく挑戦させてもらえる環境が整っていると思います。
初めは開発に携われるか不安感もありましたが、やれるだけやってみようと挑戦し続けていくうちに知識がかなり身について実践できるようになった感覚がありました。
技術的な挑戦をしたい、プロダクトの開発チームに混ざりたい!と思っている方がいればぜひ以下のサイトから応募してみてください、一緒に開発しましょう!!

次世代SaaSを作り上げる!エンジニアインターン募集

参考

[1] 今回の移行の話はデブサミ2022で詳しく解説されています!興味がある方はぜひこちらも参照してみてください。
リアルタイムユーザー解析基盤のDBをゼロダウンタイムで新しいDBに移行したノウハウ【デブサミ2022】