こんにちは、PLAIDでSREをやっている @tarr1124 です。この記事は、自分の所属しているPLAIDのアドベントカレンダーの記事になります。
https://qiita.com/advent-calendar/2018/plaid
KARTEでは、クライアントのサイトから、ユーザーの行動をeventという形でデータを送信してもらっています。
そのevent一つ一つに対して、解析処理をし、ユーザーのこれまでのeventから解析されたデータにマージされて、その結果を元に適切なresponseを返すようになっています。
このeventや解析されたデータは様々なデータベースに格納されますが、今回は、その中からBigTableに焦点を当てて、KARTEのデータ特性からどのようにBigtableのキーを設計すれば良いのかを書きます。
そもそもBigtableってなに
https://cloud.google.com/bigtable/docs/overview
Bigtableは下記のような特徴をもったKVSです。
- 大量のデータを保持できる(node数を増やすことで線形にデータ容量が増える)
- 大量のアクセスをさばくことができる(node数を増やすことで線形に処理能力が伸びる)
- 列指向であるので、一つのkeyに大量のcloumnを持たせることができる。(それらを特定のcolumnのみでfilterして取り出すことも可能)
このように、nodeを増やせば増やすほど、BigTableでは大量のデータを扱えるように、大量のリクエストをさばけるようになります。
しかし、そのためには、うまくリクエストやデータが分散されるように、キーを設計する必要があります。
どのようにキーを設計すれば良いのか
BigTableのデータは以下の特徴で扱えます。
https://cloud.google.com/bigtable/docs/overview
- keyの辞書順にソートされた状態で格納される。
- keyをrange指定で複数のrowで取得できる
- 一つのnodeにread/writeが集中しないように分散させたほうが良い。
- そのkeyが何を表しているのか、人間があとから追えるようにする必要がある。(key visualizerというツールでパフォーマンスを追うときに必要です)
つまり、どのようにデータを利用したいのか、read/writeのどちら側に寄せるのか、などを考慮した上でkeyを設計する必要があります。
KARTEのキー設計
KARTEがBigTableに格納しているデータは大きく2種類あります。
それぞれのデータ特性にあったキーの設計を紹介します。
解析済みuserデータ
- eventを解析した結果をmergeしていくデータ
- userごとに持っている。
- eventが来るたびにread/writeが走る。
- 管理画面からユーザーの詳細を見るときなどにreadが走る。
このような特徴のデータのkeyを設計してみます。
まず、userごとのkey-valueなので、user_id
をkeyにするのはどうでしょうか?
{user_id}
この状態だと、user_idに偏りがあったときに特定のnodeに負荷が集中してしまって、BigTableのパフォーマンスが落ちてしまいます。
それでは、hashにするとどうでしょう。
{user_idのhash値}
こうすれば、user_idによる偏りは防げますが、あとから問題が起こったときに人が追いづらいですね。
特にBigTableでは、Googleが提供しているkey visualizerというツールをつかって、keyに偏りがないか、パフォーマンスを確認することがあります。
その際に、keyがhash値などだと、何が原因でパフォーマンスが悪いのか追いづらいため、Googleも推奨していません。
そこで、KARTEでは、user_idから作るhash値をprefixにつける設計にしています。
{user_idのhash値}_{user_id}
これで、BigTable内で適切に負荷が分散されます。これは、keyがデータとして独立していて、他のデータと関連していないためにできる設計です。
eventデータ
- すべてのユーザーから送られたeventが格納されているデータ
- 管理画面からユーザーごとにeventのhistoryを見たいときに使われる。
- eventの詳細を見たいときに使われる。
eventデータは、上記のような特徴を持っており、これはupdateがなく、読み込みがメインとなる時系列データです。
また、このデータはユーザーごとで読み込んで使われるため、ユーザーのデータがBigTable内でまとまっている必要があります。
一方で、新しいuserなどのほうが書き込みが多いという特徴もありそうです。よって、userのデータ自体はnodeで分散させる必要があります。
そこで、KARTEでは下記のようにBigTableのkeyを設計しています。
{user_idからのhash値}_{user_id}_{timestampを反転したもの}
この設計であれば、下記のように、Bigtableと我々のKARTEの要望、両方に適したものになります。
- hash値をprefixにつけているので、user_idの偏りによって、特定のnodeにリクエストが集中することがない
-
user_idのhash値
とuser_id
を prefixとして持っているので、辞書順に並んだ際に、一人のuserのrowをまとめてとってくることができる -
user_id
自体もkeyに含まれているので、問題が起こった際に人間が追いやすい -
timestampを反転したもの
をpostfixとして持っているので、新しいrowが先にくるようにソートされる
このような形でBigtableのキーを設計することで、read/writeのパフォーマンスに加えて、node数を抑えることができ、コスト面にも効果がでます。
Bigtableを使う際に参考にしてみてください。