小咄:blazeds+amf client+mysqlで実現するkey-value storage
Post on 25-May-2015
3.220 Views
Preview:
TRANSCRIPT
小咄:BlazeDS+AMF Client+MySQLで実現するKey-Value Storage
HBaseが重い
事の顛末クローリングしたブログ記事をHBaseに保存してた。
→2009/03ころからデータ参照処理が非常に時間がかかるようになる。(テーブル数:1000弱 データサイズ:5,000万レコード超)→今まで1時間で終わっていた処理が5時間くらいかかるように・・・・・・→キィィィィィィィィィ!!
元々HBaseって使い辛いよね
✓書き込んだデータがHDFSのノード上に伝播されるまでタイムラグがある。
✓カウント文が無い。(countコマンドはあるが、全レコードをgetしてその件数を数えてる)
✓まだバグが多い。
では何故HBaseを使っていた?
見栄
key-value store• 何を期待している?
- 処理パフォーマンス、シンプルさ- keyさえ分かれば値が取得できる- valueに何でも放り込んでおける(スキーマレス)
- スケーラビリティ- keyさえ分かればデータがどこにあっても良い
• 何を期待していない?- 高機能
- たとえばSQLみたいなDSLによる操作- ロールバック機構
key-value store• とは言えどもこれは有った方が良い
- 処理パフォーマンス - バックアップ機能- 手軽にレプリケーションできる- ある程度の信頼性
- データが高負荷時に突然破損しない程度
- データリスト/keyリストの取得機能- データ件数取得機能
比較•memcached•Tokyo Cabinet/Tokyo Tyrant•Voldemort•Apache CouchDB•HBase(一応)
比較(完全に主観)パフォーマンス レプリケーション バックアップ 信頼性
memcached ◎ ?cloneプロジェクトがいくつかある
× ◎
TokyoCabinet/TokyoTyrant ○ ◎ ◎ ○
Voldemort △ ○採用ストレージによる
○採用ストレージによる
△
CouchDB △ × × △
HBase × ◎ ◎ ×
TokyoTyrantは悪く無い• スループットもmemcachedの倍遅い程度• レプリケーション機能標準採用• 比較的堅牢、信頼性がある(mixiのログイン機能他多くの高トラフィックシステムに使われている)
• インターフェースmemcached互換
• ただし使い辛い部分も• テーブル単位でデータを保存するには個数分インスタンス立ち上げ
• keyリストは取れるがカウント文的なものは無い
こんな記事
訳
記事要約• スキーマ追加に伴うインデックス追加が大変• CouchDBみたいなスキーマレスな仕組みにしたい。しかしCouchDBは不安定
• MySQLで良いじゃん。• valueはJSONで保存• 実体テーブルと参照用インデックステーブルを分離実データは実体テーブル上にuuid-bodyの対で保存参照テーブルでは実体検索用のカラムを保持
• 負荷減った
これだ
構成CREATE TABLE entities ( added_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, id BINARY(16) NOT NULL, updated TIMESTAMP NOT NULL, body MEDIUMBLOB, UNIQUE KEY (id), KEY (updated)) ENGINE=InnoDB;
実体テーブル
参照テーブルCREATE TABLE index_user_id ( user_id BINARY(16) NOT NULL, entity_id BINARY(16) NOT NULL UNIQUE, PRIMARY KEY (user_id, entity_id)) ENGINE=InnoDB;
{ "id": "71f0c4d2291844cca2df6f486e96e37c", "user_id": "f48b0440ca0c4f66991c4d5f6a078eaf", "feed_id": "f48b0440ca0c4f66991c4d5f6a078eaf", "title": "We just launched a new backend system for FriendFeed!", "link": "http://friendfeed.com/e/71f0c4d2-2918-44cc-a2df-6f486e96e37c", "published": 1235697046, "updated": 1235697046,}※bodyの中身(value)はJSONで保存
アプリ
※user_id→entry_id(key)検索用
データ登録時CREATE TABLE entities ( added_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, id BINARY(16) NOT NULL, updated TIMESTAMP NOT NULL, body MEDIUMBLOB, UNIQUE KEY (id), KEY (updated)) ENGINE=InnoDB;
実体テーブル
参照テーブルCREATE TABLE index_user_id ( user_id BINARY(16) NOT NULL, entity_id BINARY(16) NOT NULL UNIQUE, PRIMARY KEY (user_id, entity_id)) ENGINE=InnoDB;
{ "id": "71f0c4d2291844cca2df6f486e96e37c", "user_id": "f48b0440ca0c4f66991c4d5f6a078eaf", "feed_id": "f48b0440ca0c4f66991c4d5f6a078eaf", "title": "We just launched a new backend system for FriendFeed!", "link": "http://friendfeed.com/e/71f0c4d2-2918-44cc-a2df-6f486e96e37c", "published": 1235697046, "updated": 1235697046,}※bodyの中身(value)はJSONで保存
アプリ
※user_id→entry_id(key)検索用
1.実体を実体テーブルに保存
2.検索用のレコードを参照テーブルに保存
※非同期で登録も可能
データ参照時CREATE TABLE entities ( added_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, id BINARY(16) NOT NULL, updated TIMESTAMP NOT NULL, body MEDIUMBLOB, UNIQUE KEY (id), KEY (updated)) ENGINE=InnoDB;
実体テーブル
参照テーブルCREATE TABLE index_user_id ( user_id BINARY(16) NOT NULL, entity_id BINARY(16) NOT NULL UNIQUE, PRIMARY KEY (user_id, entity_id)) ENGINE=InnoDB;
{ "id": "71f0c4d2291844cca2df6f486e96e37c", "user_id": "f48b0440ca0c4f66991c4d5f6a078eaf", "feed_id": "f48b0440ca0c4f66991c4d5f6a078eaf", "title": "We just launched a new backend system for FriendFeed!", "link": "http://friendfeed.com/e/71f0c4d2-2918-44cc-a2df-6f486e96e37c", "published": 1235697046, "updated": 1235697046,}※bodyの中身(value)はJSONで保存
アプリ
※user_id→entry_id(key)検索用
1.keyが明示な場合は直接実体テーブルに参照に行く
2.keyが不明な場合、参照テーブルから条件に合致する
key情報を取得する
FriendFeed版スキーマレスDBの利点
• key-value storeのメリットを享受しながら、MySQLの性能(パフォーマンス、利便性、信頼性)や実績/ノウハウをそのまま使用できる
• 参照テーブルを用意する事で普通のDBと同じような操作が可能になる
• インデックス追加のコストが低い• 検索用の参照テーブルを非同期で作成すれば良い• 要件によっては非同期に更新しても良い。I/O負荷の分散/平板化がしやすい
データをJSON形式で保存する功罪
• メリット• スキーマレス(何でも放り込める)• 可視性。何か有った時に目で確認→直接編集できる
• 標準フォーマット(RFC4627)• デメリット
• バイナリ形式に比してデータが冗長になる• バイナリを扱うには文字列符号化が必要• BLOBに可変長のデータを放り込むためフラグメントが起こりやすい
• 生理的に嫌
参照テーブルを非同期で更新することの功罪
• メリット• 更新処理を非同期にすることができる。• インデックスの追加やデータリカバリが容易• スループットの向上。I/O負荷の平板化
• デメリット• 更新したデータが、更新直後に参照できない可能性がある(ACIDモデルの崩壊)
CAP定理• Consistency(一貫性)、Availability(可用性)、Partition
tolerance(並列性)の3つ全てを同時には満たせない、という定理。http://www.cs.berkeley.edu/~brewer/cs262b-2004/PODC-keynote.pdf
Consistency
Availability Partition tolerance
CAP定理• Webサービスでは「A」「P」は必須• 「C」で妥協するしかない
ここを工夫するConsistency
Availability Partition tolerance
Consistency• Strong Consistency
- 誰かがデータを更新したら、次の人は必ず最新のデータにアクセスできる
• Weak Consistency- 誰かがデータを更新したら、次の人は必ず最新のデータにアクセスできる「とは限らない」
• Eventual Consistency- データが複製されるための十分な時間を経て、その後更新がされていなければ必ず最新のデータにアクセスできる
- MySQLのレプリケーションなんかもそうだよね
ACIDからBASEへ?• ACID
Atomicity (原子性)/Consistency (一貫性)/Isolation (独立性、隔離性)/Durability (永続性)
• BASE- Basically (基本的に大丈夫)- Avaiable(使用可能であること) - Soft State (柔軟な状態管理。通信中に絶えず相手の動作を確認するインターネットのようなモデル)
- Eventual Consistency
ACIDからBASEへ?• http://subtech.g.hatena.ne.jp/mala/20090303/1236054662
「正確さのために無駄なロックが発生したり、インデックスを肥大化させて、速度を犠牲にしてしまう。(よくあるパターンだと思う) 細かいことを気にしなければコンピューターの性能はもっと引き出せるはず。」(mala氏)
• どうしてもStrong Consistencyにしたい場合は実体/参照テーブルを同期書き込み、そうでもない場合は非同期書き込みにしておけば皆が幸せになれる。
ここまでのまとめ• MySQLをkey-value storeとして使う、というアイデア
• 実際にFriend Feedで採用されている• 他のkey-value storeを採用するより信頼性やパフォーマンス、何より実績がある安心感
• 実体テーブルと参照テーブルを分けることにより一般的なRDBと同じような使い方ができる
MySQLでkey-value storeってありだよね!
つづく
MySQLへの接続問題• JDBCドライバ初期化→DB接続のオーバーヘッドが
Javaだと結構負担になる• 軽量言語では無問題かもしれないが・・・
• アプリ→MySQLで直接繋ぐと色々と問題が• セキュリティ問題(3306ポートを空ける?)• DBCPなどでpoolingさせると基本的にコネクションがステートフル状態になる
• あまり分散環境には向かない
proxy
拠点B
拠点A
Proxy的な機能が有った方が便利なケースが多い
MySQL
MySQL
MySQL
MySQL
Proxy
Proxy
memcache
アプリ
アプリ
アプリ
負荷分散 負荷分散
クラスタリング
接続プール
キャッシュ
HTTPベースの接続
Proxyのインターフェースをどうするか
• できればHTTP/ステートレス通信ベース• 標準技術である、NAT越え問題など
• とはいえREST、JSONなどのテキストベースではデータサイズのオーバーヘッドが大きい
こんな記事
こんな記事
要約• AMF(Action Message Format)→AdobeがFlashで採用しているデータフォーマット バイナリベース。HTTP上でやり取りできる。 現在オープンソース化
• BlazeDS→LifeCycle Data Service ES(Flash リモーティングサーバ)のオープンソース版ミドルウェア。 AMF経由でのRPC、メッセージパッシングに対応
• BlazeDSはJava実装。Tomcat上で動作。• AMFはオープンソースのため、Java用のクライアント実装も公開されている
• Ajax+JSONより4倍速い?
これだ
パフォーマンス測定(BlazeDS × JSON)
• RPCサーバ側からクライアント側にHello World文字列を返却する処理を以下の方式で実装- 一般的なWebアプリケーション(SpringMVC。
GETのレスポンスで文字列を返却)- BlazeDS + SpringFramework + Java AMF Client で文字列返却(AMF経由でバイナリ通信)
- BlazeDS + SpringFramework + Java AMF Client でJavaオブジェクトをシリアライズして返却
パフォーマンス測定(BlazeDS × JSON)
0
10,000
20,000
30,000
40,000
HTTP文字列 AMF文字列 AMオブジェクト
ステートレスステートフル
※1万回処理を実施※単位はミリ秒
パフォーマンス測定(BlazeDS × JSON)
4倍速いっぽい
パフォーマンス測定(JDBC × Proxy)
• クライアント側からDBにselect/insert/update/deleteの処理を以下の方式で実装- JDBCドライバを読み込み直接接続- singleton形式で初期化したDataSourceを使用し接続
- Proxy経由でAMF形式で接続(Proxy内ではDataSourceをsingletonで保持)
0
1,000
2,000
3,000
4,000
JDBC直接 DataSource Proxy
insert updateselect delete
パフォーマンス測定(JDBC × Proxy)
※100回処理を実施※単位はミリ秒
パフォーマンス測定(JDBC × Proxy)
• 直接DataSouceで接続するのに比べ、BlazeDSのProxy経由の方が若干遅い(通信オーバーヘッド分)
• しかしProxyの利便性により相殺できるレベル• Hadoop上のMap/Reduceタスクのように
singletonモデルが扱い辛い場合にProxy接続は便利
パフォーマンス測定(Proxy × Tokyo Tyrant)
• Tokyo Cabinet / Tokyo Tyrant に対して先ほどの「JDBC×Proxy」と同様の処理を実施。※set(insert),get(select)のみ
0
250
500
750
1,000
DataSource Proxy Tokyo Tyrant
put/insertget/select
パフォーマンス測定(Proxy×Tokyo Tyrant)
※100回処理を実施※単位はミリ秒
パフォーマンス測定(Proxy × Tokyo Tyrant)
• Tokyo Tyrantの方が圧倒的に速い• 用途によっては、実体データをTokyo
Tyrantに保存する形で実装しても良さそう
Tomcat
springframework
今回はBlazeDSを用いて以下のような構成に
MySQL
BlazeDS
アプリ
アプリ
アプリproxyサービス
スキーマレスDB接続
接続プール
HTTPベースの接続(AMF)
MyISAM
MySQL
レプリケーション
現在取り扱っているデータ件数
データ更新 データ参照
データ量 350万/日 -
APIコール数 500万/日 1,550万/日
※4/19付けの結果※APIサーバ:1台※DBサーバ:1台
処理にはまだまだ余裕がある。
BlazeDS使用の際の注意点• ステートフル通信をエミュレートするために、接続時にセッションオブジェクトが作成される。デフォルト設定で大量のリクエストを発行するとOut Of Memoryが発生する可能性がある。→セッションの保持期間を短くするか、セッション保持をしないようパッチを当てる(今回は前者で対応)
• RPC接続時にBlazeDS上でExceptionがthrowされてもクライアント側には通知されない→エラーコード等をクライアント側に返却する必要あり
まとめ• MySQLでkey-value storeってありだよね• アプリ−MySQL間でProxy的な機能があると利便性が増す
• BlazeDSはTomcat上で動作、Java実装、AMFが扱えるということで便利。
• BlazeDS(AMF)のRPCはプレーンテキストのHTTPより4倍くらい速い
• 数千万ヒット/日:1台 程度の負荷は実用的に耐えられる実績も既にある。
ご清聴ありがとうございました
質疑応答
top related