より良いサービスを目指して、ハイリスクな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)には引き返せない。
トライアルと横展開から得た教訓と知見
以上4つのステップをクライアントごとに進めるにあたり、順番としては「(1)トライアルフェーズ」で社内用サイトでまず試してから、中規模のクライアント数社を行い、リスクを早くつぶすため大規模なクライアント1社に挑戦。次に「(2)横展開フェーズ」で大規模クライアントを除く全てに着手し、「(3)最後の仕上げフェーズ」として、大規模クライアント数十社について実施した。それぞれのフェーズでさまざまな問題が発生し、多くの教訓が得られたという。
(1)トライアルフェーズ
ここではバグ潰し、パフォーマンスチューニングの繰り返しだった。レプリケーションは特にバッチでの書き込みに加えて検証を日々行ったが、新形式のread/writeの不備や、現在/新規の変換ロジックの不備など、予想以上にバグが出て、1カ月ほどを費やすことになった。
パフォーマンスのチューニングも行ったが、ここでのポイントは最低限にとどめることだ。初期段階ではDB内のデータ分散が十分でないこともあり、パフォーマンスが出にくかったが、ロジックに問題があるのかなど問題の切り分けがまだできていないこともあり、カリカリにチューニングすることは局所最適化にとどまる可能性があった。
ただ、新DBでのBigTableのフィルター条件が複雑すぎて逆に負荷が増大していることは明らかだったため、フィルター条件をシンプルにしてアプリケーション側でフィルタリングすることで対処した。そして最たる山場として日鼻氏があげたのが、検証からread切り替えの部分だ。特に最初のRead切り替えは、一定リスクテイクして進める必要がある。
日鼻氏は「現/新比較の一致率はなかなか100%にならず、初期段階だとデータが十分に分散しないためにパフォーマンスも若干悪くなってしまう。そこで、スピード感を持って実施していくために、最低限の目標値を達成し、その上で検証結果に自信を持ったら進めるしかないと判断した。またクライアントごとの移行のため、仮に問題があったとしても影響は限定的であり、問題が出てすぐ直せなくとも、検証ステップに引き返せばいいと考えた」と振り返り、「この段階ではクリティカルな問題はなく、結果論ながら進んで良かったと考えている」と語った。
(2)横展開フェーズ
トライアルでうまくいったものの、全てのクライアントの移行を手掛けた横展開時には2つの大きなトラブルに見舞われた。
まず、デプロイ時の解析処理速度が劇的に悪化した。これは、移行対象が増加したことによるClient ConfigのDB(Big Table)が過負荷状態となり、readパフォーマンスが劇的に悪化したからと考えられる。一定サイズ(不明)を超えるとBig Table内部でキャッシュされなくなることはDBではよくある問題だ。そこで移行対象について全てJSファイルにハードコーディングし、デプロイを繰り返すという地道な方法で乗り切った。
日鼻氏は「そもそも一気に行わず、10%など刻んで段階的に移行すれば防げたはず。さらに複数の行にConfigを書き込んで、read側を分散させて過負荷を防止する方法もとり得た」と語った。
そして2つめのトラブルは、振り分け過程でのバグにより、未移行のユーザーデータ更新が意図せずスキップされることが生じ、データが一部欠損するというクリティカルな事象が起きた。これについては、早々にバグを修正し、影響があったクライアントには連絡するなどの対応を行った。
DB移行の経験を活かし、さらなるサービス改善へ
(3)最後の仕上げフェーズ
横展開フェーズが厳しかったため、当初は全ユーザーデータ移行が現実的な時間内で終わるかという懸念があったが、2週間以内に全ての移行を完了することができた。パフォーマンスをあげるために、移行対象抽出用のBigQueryのリソース(Slot)を一時的に増やしたり、書き込みバッチのジョブ並列数を5倍にしてバッチの書き込みスループットを増やしたりといった工夫を行った。
日鼻氏は「移行が進むにつれ、新DBのデータが増えて負荷が分散され、大量の書き込みに耐えうる状況になっていった。当初450TBのDB移行は解決困難な問題に見えたが、やってみるとさほど問題ではなかった」と語った。
そして結果として、予想以上にパフォーマンス向上やコスト削減を実現した。不要なデータは移行対象から除外し、圧縮効果もあってデータ量も450TBから225TBへと大幅に圧縮することができた。また、パフォーマンスについては、ユーザー取得速度(95 percentile)が100ms程度も早くなった。そしてコストはBig Tableの消費リソース数を30%程度削減することができた。
こうしたユーザーデータDB移行にあたり、日鼻氏は「ハイリスク・ハイリターンな変更こそ、勇気を持って個別に早く実施すべきだと改めて感じた。450TBという量で大きな問題に見えても、小さくスタートして段階的に進めればなんとかなる。その中でも、クリティカルな影響がでうる場合は、検証ステップが有効であり、それがあったからこそスムーズに進められた。ただし、どんなに検証したとしても必ず見えていない角度から障害は起きうる。検証結果を過信することなく、影響範囲を限定的にする設計と、ふとした予兆や違和感を大事にすることが重要」と語った。
無事にDB移行を実現させたが、「リアルタイム解析基盤」のアーキテクチャを変えて、スケーラブルな基盤とするために、全体の移行はまだ続いていく。DB移行で得た学びを活用しながら、移行設計・検証を進めているところだ。
日鼻氏は「DB移行よりも多くの領域が変更対象となり、よりチャレンジングなものになると思われる。基盤全体の移行後に、これらの移行ノウハウを発表したい」と意欲を見せた。「さらにいえば、解析基盤の移行を完了させたら終わりではなく、移行させてからが勝負だと思っている。ブレイドの『データによって人の価値を最大化する』というミッションのもと、より良いものにしていきたい」と熱く語った。