より良いサービスを目指して、ハイリスクなDB移行に挑戦
顧客体験を向上させることを目的としたプラットフォーム「KARTE」。サイトやアプリに今来訪している人をリアルタイムに解析〜可視化し、一人ひとりにふさわしい体験を提供しようというものだ。
具体的には、クライアントが「いつ・誰に・どのようなアクションをするか」を設定しておくと、ユーザーの行動履歴に応じて適切なアクションがなされるという仕組みになっている。そのリアルタイムでの対応を可能にしているのが、「リアルタイム解析基盤」だ。
ユーザーが何らかの行動を取ると、それがKARTEのイベントデータDBに格納され、同時にユーザーデータDBに蓄積される。そのユーザーデータの分析によって、来訪したユーザーが「どんな人か」認識され、ユーザーの新たなイベントがトリガーとなって、望ましいアクションが実行される仕組みになっている。
「リアルタイム解析基盤」では、このイベントが起きてから、ユーザー解析、アクション配信の判定までを1秒以内で実行しており、さらにデータ量が秒間13.4万リクエストに上った年末年始でも、まったく遅延なく稼働できていた。
そうした低レイテンシ&高拡張性を誇るKARTEの「リアルタイム解析基盤」だが、さらなるユーザーの増加に対応し、判定時間を高速化するために、ゼロベースでアーキテクチャやユーザーデータ構造を再設計することとなった。
ユーザーデータDBのデータ構造については、従来はあるユーザー(key)が来訪した期間別に1行ずつに分けて解析結果(Value)を保存していたが、1人のユーザーについて複数行を読み込む必要があり、非効率となっていた。そこで、1人のユーザーの期間ごとの解析結果を圧縮してコンパクトにし、1行に全てを格納する構造とした。そうすることで効率化されて反応時間も圧縮されるはずだったが、設計の段階ではまだ十分に確証が得られていなかった。
実際の移行については、解析基盤とユーザーデータDBを同時移行する「プランA」、もう1つはDB移行を済ませてから基盤移行をする「プランB」が想定され、日鼻氏らは「プランB」を選択した。
その理由について日鼻氏は、「リスクはあるが、先にDBを移行したほうが、(1)新データ構造が効率的かどうかを早めに確認でき、(2)実際に効率的ならメリットを早く享受できる。さらに変更要因が1つだけのほうが、(3)トラブルが生じたときの原因の究明がしやすいと考えた」と説明し、「ハイリスク・ハイリターンな変更であるほど、個別に変更をリリースすることによって複合的で複雑な問題が発生するリスクを軽減し、メリットが大きい。その学びを得られた意味は大きい」と評した。
ダウンタイムゼロのDB移行を実現する4つのステップ
それでは具体的にどのように移行したのか。まず、ユーザーデータDBの移行では、(1)ゼロダウンタイムが前提(リアルタイム解析サービスの停止は基本容認されていなかった)、(2)データが破損すればエンドユーザーの影響がクリティカル、(3)450TBという膨大なデータ量の3つが大きなハードルとなっていた。
これらを乗り越えるためのアプローチとして、まずは小規模なクライアントごとにユーザーデータを移行するようにした。小さな範囲から始めることで、問題発生時の影響範囲を限定的にできると考えたわけだ。そしてもう1つ、現DB・新DBの検証ステップを設け、できる限り見えている問題は潰してから移行に臨んだ。
DB移行については、4ステップを踏んで行われた。まず(1)データをコピーするレプリケーション、そして(2)切り替え前に新DBのread検証を実施し、(3)readを切り替えてから(4)writeを停止した。そしてこれをクライアントごとに行っていった。
(1)レプリケーションの設計と実装
レプリケーションの設計については、前提として「頻繁に読み書きされる」「過去来訪したユーザーデータも漏らさず移行する必要がある」、そしてマルチテナントで複数のクライアントのデータが分散して入っているため、「Big Tableではクライアントごとの移行対象ユーザーが絞り込めないこと」などがあった。
ここでは、「リアルタイムでの二重書き込みとバッチ書き込みを組み合わせること」と「Big Table以外のDBから移行対象を絞り込むようにすること」がポイントになってくる。続いて実装時について。まず「リアルタイムでの二重書き込み」は、ユーザーデータをread/writeするロジックを共通のインタフェイスでラップし、既存基盤はNodeJS、新ロジックはJavaであることから、新しいDBからの読み出しはJavaである必要がある。
writeのリクエストが来たら、クライアントごとにどのようなステップか判定し、レプリケーションステップの場合は既存ロジックで現在のDBに書き込むと同時に、新ロジックJavaプロセスを事前に動かし、そこにもwriteリクエストをプロセス間通信を通じて行っていく。その際、新ロジックでは現在のものから新形式に変換して新しいBigTableに書き込んでいく。つまり、移行ステップによって処理を振り分けた、というわけだ。
また「バッチでの書き込み」については、BigQueryから、クライアントごとに格納されているイベントデータの移行対象分を抽出。GCPのマネージドサービスからバッチの実行基盤「Dataflow」を使用して、現在のユーザーデータDBからReadして新形式に変換し、それを新しいユーザーデータにwriteした。なお「Dataflow」は並列するジョブ数を自由に変更できるので、書き込むスループットを調整できるのがポイントだ。
順番としては、二重書き込みを開始してからバッチの書き込みを行う流れだ。バッチの書き込みで加工データは全て移行され、既に新規分はレプリケーションで二重書き込みがなされているので、もれなく移行できるというわけだ。
(2)検証時
検証時は、現ユーザーデータを使いながら、裏側で検証することがポイントだ。つまり、readリクエストが来たら、Client Configを通じて既存のロジックから現在のデータ、同じく新ロジックを使って新しいデータを取り出してくる。その両者の一致確認を行い、新データ形式のreadのパフォーマンスを検証した。検証結果はDATADOGに送り、ダッシュボードでパフォーマンスや一致率を検証していった。
(3)Read切り替え
新ユーザーデータから読み込むというステップ。ここはシンプルながら、仮に問題があった場合でもステップ(2)に戻れるというのがポイントだ。
(4)Write停止
現ユーザーデータへの書き込みを停止する。ここではもうステップ(3)には引き返せない。