FarmBalance: Webファームシステムにおけるデータ件数やトランザクション量等の均等分散
はじめに
はじめまして!
dukkiedukkie と申します。みなさん、意識は高まっていますか?私は当麻紗綾のように「高まるぅ〜」です。
先日test Tracksに初めて投稿させていただきましたが、こちらのTrackはレベルの高い皆さんばかりで本当にお恥ずかしい次第ですが、最近作成し今なおより良いものに改良していこうと考えております拙作のモジュール、FarmBalancehttps://github.com/dukkie/FarmBalance/blob/master/lib/FarmBalance.pmについて今日はお話させてくださいまし。
大量トラフィックに抗しうるWebファーム
インターネット業界でのお仕事をされているかたですと、利用者からの過密リクエストやトランザクションに対して、「いかにシステムを分散構成するか?」を悩まれたご経験があることと思われます。3〜4台のアプリケーションサーバやDBサーバ構成を設計したり、場合によっては100台超、あるいは1000台に及ぶほどのクラスタ構成を設計・開発したり、運用している方もいらっしゃるでしょう。数万〜数千億のデータをそのファームのデータストア(KVSやRDBM)に分散配置して、膨大なデータと過密トランザクションを許容できるシステムを構築する。
負荷分散ロジック
さて、その各ファームノードにどのようにデータ等を分散配置するか?例えば会員制のデータベースを仮定すると、ユーザ名称からMD5やSHA1や独自ロジックによって不可逆ハッシュを生成してそのハッシュの頭文字数ごとにそのユーザの参加ノードを決定する。
あるいは、会員番号をmysqlならauto incremet, Oracle ならsequenceで割り当てて、
それを全体ノード数で割った剰余で参加ノードを決定する。。。。
とまあ、こういったハッシングロジックによってファームの決定をしているケースが多いのではないかと、小生の経験では思います。
ハッシングロジックの欠点
まあ私が書かずとも賢明な読者のみなさんはお気づきでしょうが、このハッシングロジックには「データ量がノードごとにばらつく」とか「1ファームだけ極端にデータ量が多くなってしまって、そこでの検索速度が劣化してしまった。。。」などの事態を引き起こします。中年エンジニアの私も、そのような事態に15年超のエンジニア人生の中でたびたび遭遇して、眠れぬ夜を過ごしたものでございます。。。
「どうしたら、ファームごとに極力偏らないデータの持ち方を出来るだろうか?データ量が均等分散されれば自ずとトランザクション量も平均化されるはず。。。またサーバのリソースも分散のための考慮に入れたい」
と考えて昨年作ったのがこのFarmBalanceロジックhttps://github.com/dukkie/FarmBalance/blob/master/lib/FarmBalance.pmとなります!
FarmBalanceについて
例えば、オンライントランザクションで新規ユーザの追加リクエストがあり、参加ファームを決定するとき、アプリケーションの方で以下のように「どのファーム番号に参加すると最も負荷分散されるか?」とFarmBalancenに聞きます。
use FarmBalance; my $farms = 4; #- ファーム数 my $stats = { #- ファームごとのデータ量やTPS,QPSなど(バランスキー)統計を与える 'a_table_rows' => [1000, 700, 629, 800], 'b_table_rows' => [300, 70, 26, 200], 'server_tps' => [1000, 300, 5, 200], }; my $input = { #- 新規ユーザに想定されるデータ量等。 'a_table_rows' => 20, 'b_table_rows' => 33, 'server_tps' => 10, }; my $df = FarmBalance->new( farms => $farms, stats => $stats, input => $input, ); $df->define_farm; print "DefineNode:" , $df->{effective_farm} , "\n"; #- 参加すべきノード決定! #- そのノードに対してデータ登録したり等の処理を続ける ....
「ファームごとのデータ量やTPS,QPSなど統計」の与え方は例えばDBに
create table farm_info ( farm_id int unsigned primary key, artist_table_rows int, cd_table_rows int, track_table_rows int, txn_r_avg int, txn_w_avg int, cpu_usage_avg int, mem_usage_avg int, );
みたいなファーム統計情報を持っていて、そこから取得してもいいですし、memcachedやKVS系でもいいでしょう。これらの統計値を「バランスキー」と呼びます。ここには主要テーブルのデータ件数や、サーバリソースなど何を与えても構いません。「新規ユーザに想定されるデータ量」が見積もれない場合は、引数として与えなければ、現状の統計値からの平均値を想定して分散をします。
FarmBalanceの分散ロジック
各統計値の総和を一旦100にして、ファームごとの統計値をならした上で、各ファームに参加した場合の標準偏差を求めます。その標準偏差が最も軽減されるファームに新規ユーザを参加させます。参加させたらその統計情報をupdateする必要がありますね。また、平均値を割り当てたユーザは実態とかけ離れる可能性があるので、実際、今年度このロジックを使って頂いたシステムでは、日次でデータ件数などをcountして上記farm_infoテーブルを更新するバッチを走らせながらの実装となりました。
膨大データマイグレーションにも活用
数億件を超える大量なデータを持つデータベースからのデータ移管においてもFarmBalanceロジックは役に立つと思います。
例えばマイグレ用データで、ユーザIDをキーに各エンティティのデータ件数がわかっている場合、その統計をタブ区切テキストなどで記述した上で、
use FarmBalance; my $farms = 4; my $src = 'migration.tsv'; #- ユーザIDをキーとするデータ件数TSV my $stats = { #- マイグレーションなのでゼロからスタート 'a_table_rows' => [0, 0, 0, 0], 'b_table_rows' => [0, 0, 0, 0], 'server_tps' => [0, 0, 0, 0], }; my $df = FarmBalance->new( farms => $farms, stats => $stats, ); open (IN, $file ); #- 適当でゴメンナサイ while ( <IN> ) { chomp; my ( $row, $keyA, $keyB, $keyC ) = split (/\t/, $_); my $input = +{ 'a_table_rows' => $keyA, 'b_table_rows' => $keyB, 'server_tps' => $keyC, }; $df->{input} = $input; $df->define_farm; print "Row: $row => DefineNode:" , $df->{effective_farm} , "\n"; #- 上記で決定されたファームに割り振る } close(IN);
みたいなかたちで、割り振りします。
今年とある100億件超規模のデータベースの国内マイグレーションを行った際にこのロジックでデータマイグレを行い、他社連携システムにもほぼほぼ均等なデータ&トランザクション分散を行うことができて「素敵なロジックをありがとう!」と言われたときには本当に嬉しくなりました(しがないオッサンの”自慢”でごめんなさいね(^^ゞ。
これからのFarmBalance
さて、このモジュールには、「膨大データ量な場合いちいち標準偏差計算をしていたら遅いので、バルクデータのうち何%かはラウンドロビンで参加ノードを決定して割り当てを高速化する」「バランスキーのうち、特に偏って欲しくない項目には優先重み付けをつける」「サーバデーモン化する」などなど、個人的には仕事では実装できたけど個人モジュールには未実装の課題が山ほどあります。
今後もっとよくして巨大システムの味方になれるようなロジックにしていきたいです!!
それが巨大システムに関わるエンジニアの幸せ(例:安心して夜も眠れる。。。)につながるでしょうから。
FarmBalance、まだイマイチな点ばかりですが、何卒よろしくお願いいたします!