PNUTS: Yahoo!'s Hosted Data Serving Platform の Architecture

せっかく、ちょっと読んだので、アーキテクチャも見ておこう。論文のFigure1をみてね。

システムは、regionという単位に分割される。regionは、各テーブルの完全なコピーを持ってる。各regionは、地理的に離れている方がよい(というかそういう運用ということだと思う)。信頼性とレプリケーションのために、pub/subシステムを使う。ようだが、信頼性とレプリケーションってならべていいのか?伝統的なログやアーカイブ情報は持ってない。なるほど、よくわからなんが、ダウンしたときにはお互い助けあうような感じかな。でも、timeline consistencyだと、ダウンしたときに最新のデータが割と吹き飛ぶ気がするがどうなんだろう。

テーブルは、水平分割する、分割されたひとつの単位をtabletと呼ぶ。bigtableと同じ。tabletは、各サーバに数千のオーダで配置されるが、あるregionにおける、あるtablet管理実体はひとつだけ。tabletの大きさは、数百MBから数GBで、数千から数万のレコードを保持。サーバがこけたら、うまいこと別サーバにtabletsを分配してリカバリ。レプリカは、もうひとつ下のレイヤでおこなてるのかな?

tabletの管理をする三つのモジュールがある。ストレージユニット、ルータ、タブレットコントローラ。ストレージユニットは、get()/scan()/set()なんかを持っている。更新がかかったときには、メッセージブローカーに少なくともひとつかければコミット、多分このとき自分のregionのストレージユニットにも書き込んでいると思う。ストレージユニットは、様々な実装ができて、ハッシュにしてみたり、InnoDBなんかを使ってみたりできる。

書き込みがあったときに、まずその書き込みがどのtabletなのか、また、そのタブレットがどのストレージユニットに書き込むべきかを制御するのがルータ。あるテーブルを考えると、テーブルを構成するtabletとはキーの範囲を持っている。その範囲とどのストレージユニットで管理しているかのマッピングを持っていて、それらは、B-treeでいうところのROOTノードの巨大なやつなので、二分探索して高速化している。(うーん、というか、B+-treeとかを実装すれば良いのではないだろうか、ボトルネック化するのが眼に見えている気がするがそうでもないのかな。)と思ったら、つづきがあった。1000台のサーバ、各サーバに1000のtablets、キーが100bytesだとすると、マップテーブルは、高々数百MB。ただ、かなり巨大な場合は、ディスクベースのマップテーブルを考えないといけない。

実は、ルータが持っているマップテーブルはキャッシュであって、本当の管理主体はタブレットコントローラ。タブレットコントローラは、リバランスやtabletの分割、リカバリなどを司る。ユーザがクエリを発行すると、ルータが古いキャッシュを持っていたら間違えた結果を返す、この時、間違えたことにユーザからのフィードバックがあるので、ルータは古いことに気づける。というような、ざっくりした運用なのでルータが死んでも、別に頑張ってリカバリする必要はない。タブレットコントローラは、Active/Standby構成にしているが、データのやり取りのメインルートにいないので、別にボトルネックになったりしない(なるほど、構成の考え方はスマート)。現状の構成におけるボトルネック箇所は、ディスクユニットとブローカー間のI/O。なので、客ごとにクラウスを構成する。将来的には、バースト的なクエリに一時的なリソースを割り当てることを考えると、
ひとつのクラスタのほうがいい。

このシステムでは、更新性能を上げるために非同期レプリケーションを採用。pub/subシステムである、メッセージブローカーは、redo logやレプリケーションカニズムとしても利用可能。更新クエリがコミットされるのは、メッセージブローカにpublishしたとき。コミットがされたあと、別のregionsへ非同期に更新情報が拡散されて、レプリカに反映される。ので、レプリカの一貫性が保証されないことになるので、Timeline Consistencyという概念を創りだした。(なるほど、ぶっちゃけてるなー。でもたしかに、プログラマに概念としてのゆるい一貫性が提示出来れば、それを利用する方は、利用出来る時にはすれば良いということになるね、アーキテクトとして勉強になった)。メッセージブローカをレプリケーションとコミットログとして利用出来る理由は二つある。第一に、メッセージブローカーは、受け取ったデータをデータベースに入れる前に、複数のステップを踏むことにより、データロストを防いでいる。メッセージブローカーは、送信されたデータが必ず受信側に届くことを保証する。たとえメッセージブローカを構成するサーバが1台落ちてもへっちゃら。その理由は、複数のサーバ上の複数のディスクに保存しているから(うーん、というか、GFSと違うのかなあ。完全にロギング用だから、シンプル?そもそも、ディスクに保存するところまでやったら、スループットでないから、実際にはダーティーフラグたてるところまでだよな、うん)。デフォルトでは、この「複数」の数は、2。PNUTSが必要なレプリカ更新が終わったら、このログは削除される。

メッセージブローカは、送信されたメッセージの到着に関して半順序性を保証している(逆に言うと、順序性がわからないことがあるということ。)。あるメッセージブローカが送信したメッセージは、必ず送信した順序で、受信側に届きます。ただし、別々のメッセージブローカが送信したメッセージの順序性は、何も保証できない。そこで、レプリカに対して「マスタ」という概念が必要になった。つまり、あるレコード関してマスタを決めてあげて、更新情報を送信は全てそのマスタから行わないと、Timeline Consistencyは実現できないことになる。

とりあえず、ここまで。続きは気が向いたら。やる場合には、3.2.2以降。