松尾津邑研究室 M2 熊崎宏樹
大規模サービスを支えるミドルウェアの一つとして
キーバリューストア(KVS)が注目されている
複数の計算機に分散する事で性能をスケールアウト可能
計算機故障への耐性も冗長化で実現
一貫性の問題からKVSを利用できないケース
A
B
1000円 1000円
T1:AからBへ100円送金
T2:BからAへ50円送金
1000 = get(A);
1000 = get(B);
set(A, 900);
set(B, 1100);
1000 = get(A);
1000 = get(B);
set(A, 950);
set(B, 1050);
数が合わない
900円 1050円
キーバリューペアが指定した値の場合のみ新しい
値が保存するCASコマンド
条件判定と保存をアトミックに行える
キーバリューペアが存在しない場合に限り保存に
成功するaddコマンド
KVP不在の確認と保存をアトミックに行える
アトミックな操作が利用可能
上手く動きそうに見える
キー
A
B
バリュー
1000
900
1000
1100
lock(A);
lock(B);
set(A, 900);
set(B, 1100);
unlock(A);
unlock(B);
クライアントが離脱すると破綻する
タイムアウトでアンロックしてもデータが矛盾する
キー
A
B
永久にロッ
バリュー永久にロック
ク
されたまま
1000
900 されたまま
1000
離脱
永久にロッ
データが
ク
矛盾する
されたまま
lock(A);
lock(B);
set(A, 900);
set(B, 1100);
unlock(A);
unlock(B);
通常のKVSの機能を用いてトランザクションを実行可能
なミドルウェアを提案
分散KVS上でも同様に利用可能
スケールアウト性能や対故障性の恩恵を受けられる
一貫性を保証できるため
より多くの場面でスケールアウト性能や
耐故障性を得られる
DynamicSTMを下敷きにKVS上で同様の機能を実現
DynamicSTMとは
CPU上での並行処理を補助するアルゴリズム
CAS命令を用いて実装可能
複数のオブジェクトを一括に更新可能
ロックを用いずデッドロックが発生しない
メモリの間接参照を用いて集約化を行う
GC(GarbageCollection)のある環境を想定している
多くのKVSには間接参照とGCが無い
KVS上でそれらを実現
Software transactional memory for dynamic-sized data structures
2003 Maurice Herlihy, Victor Luchangco, Mark Moir, William N. Scherer III
キーは文字列なのでバリューとして保持できる
A
キー
バリュー
A
B
B
C
B
C
通常のキーバリューペア(KVP)をトランザクション
のために拡張したもの
keyA
value
old
keyA
new
value value’ OwnerName
トランザクション
ステータス
active
バリューがcommited、abort、activeの3状態のど
れかを取るキーバリューペア
トランザクションの現在の状態を表す
commited:完了
abort:リトライ待ち
active:実行中
key OwnerName
トランザクション
ステータス
value
active
ownerの状態によって読み出し方を分岐
commited:Newの値を読み出す
abort:Oldの値を読み出す
active:競合を解決する(後述)
key
old new OwnerName
commited
Abort
active
KVSには2つのKVPが保存されるだけ
キー
keyA
Owner:T1
バリュー
Value
Value’
Owner:T1
active
keyA
value value’ owner
committed
committed
省略して表記
トランザクションを用いて口座Aから口座Bへ送金
Transaction {
a = get(A);
b = get(B);
set(A, a – 100);
set(B, b + 100);
}
を実現する
ステータスを初期化
初期化
必要なTKVPを更新
獲得・更新
ステータスをコミットへ
コミット
yes
成功したら後片付け
成功?
後処理
no
自身のステータスをactiveとしてキーバリューストア
に新たに保存する
キーはランダムな文字列を生成する
Transaction{
a = get(A);
b = get(B);
set(A, a – 100);
set(B, b + 100);
}
set(T1,active)
T1
active
ステータスを初期化
初期化
必要なTKVPを更新
獲得・更新
ステータスをコミットへ
コミット
yes
成功したら後片付け
成功?
後処理
no
KVPは通常通り読む
TKVPはStatusの値によって最新の値が変わる
A
最新の値
A
commited
commitedなら new を読む
A
abort
abortなら old を読む
TKVPならば通常のKVPに書き換えてから読む
書き換えにはCASを用いる
不要となったトランザクションステータスは消す
Commitedなのでnew
A
T1
commited
T1
abort
Abortなのでold
A
A, Bを通常のKVPへと書き換えながら読み出す
a
A
B
b
Transaction{
a = get(A);
commited
b = get(B);
set(A, a – 100);
set(B, b + 100);
}
abort
A, BをTKVPへと書き換える
所有者として自分のステータスを指させる
A
a a - 100 T1
active
B
b b + 100 T1
Transaction{
a = get(A);
b = get(B);
set(A, a – 100);
set(B, b + 100);
}
ステータスを初期化
初期化
必要なTKVPを更新
獲得・更新
ステータスをコミットへ
コミット
yes
成功したら後片付け
成功?
後処理
no
ステータスがactiveであることを確認して
commitedへ書き換える
Transaction{
a = get(A);
b = get(B);
set(A, a – 100);
set(B, b + 100);
}
CAS
cas(T1,committed)
T1
commited
active
ステータスをcommittedへCAS
newの値が一斉に外部から最新の値として観測される
A
B
Transaction{
CAS
a = get(A);
a a - 100 T1
b = get(B);
set(A, a – 100);
commited
active
set(B, b + 100);
}
b b + 100 T1
最新の値
ステータスを初期化
初期化
必要なTKVPを更新
獲得・更新
ステータスをコミットへ
コミット
yes
成功したら後片付け
成功?
後処理
no
ステータスを消すためKVPへ戻す
書き換えはCASを用いて行い、CAS失敗は無視
失敗したということは他のトランザクションが引き継いだ事
を意味するため問題ない
最終的に不要なKVPが残らない
A
a a - 100 T1
committed
B
b b + 100 T1
全体として送るコマンドの一例が以下
set(hoge,active)
gets(A)
cas(A, a)
gets(B)
cas(A,[a, a-100, T1])
cas(B,[b, b+100, T1])
cas(T1,committed)
cas(A, a-100);
cas(B, b+100);
delete(T1);
初期化
Aの獲得
Bの獲得
準備
Aの更新
Bの更新
コミット
Aの後処理
Bの後処理
ステータス削除
後処理
ステータスを初期化
初期化
必要なTKVPを更新
獲得・更新
ステータスをコミットへ
コミット
yes
成功したら後片付け
成功?
後処理
no
コミット時にステータスがabortに書き換えられていたら
コミット失敗。トランザクションを始めからやり直す
abortに書き変わっているというのはどういう状況か
T1
abort
statusの値によって読み出し方を分岐
commited:Newの値を読み出す
abort:Oldの値を読み出す
active:競合を解決する
競合を解決する際にAbortされる
key
old new status
active
トランザクションT1
Transaction{
a = get(A);
b = get(B);
set(A, a - 100);
set(B, b + 100);
トランザクションT2
Transaction{
set(B, 950);
所有者のstatusをabortへ書き換える
TKVPを奪う
トランザクションT2
後処理も行う事でゴミを残さない
A
a a - 100 T1
CAS
Bが欲しいけど
Bを獲得できた
使用中だ…
abort
active
最新の値
B
Transaction{
set(b, 950);
+ 100 T2
b b 950
T1
Abort
active
後発のトランザクションが所有権を奪う
デッドロックも矛盾も発生しない
ステータスも削除するのでゴミが残らない
クライアント離脱への耐性がある
A
a a - 100 T1
ロールバックされる active
abort
B
b b + 100 T1
離脱
Abort
active
後発のトランザクションが所有権を奪う
デッドロックも矛盾も発生しない
ステータスも削除するのでゴミが残らない
クライアント離脱への耐性がある
A
a a - 100 T1
離脱
後処理が引き継がれる
committed
B
b b + 100 T1
active
離脱
GET/CASn回
active
成功・失敗の境目→
CAS1回
CASn回
commited
Pythonでクライアントライブラリを実装
KVSとしてmemcachedをConsistentHashingで利用
1000000件の口座からランダムに2つを選びトランザクショ
ンで残高の一部を移動させる
計算機はAmazonEC2のc1.xlargeインスタンスを50台使用
CPU: 8コア
MEMORY:7GB
Network:1GbE
クライアントとして50台使用
そのうち1台~15台をKVSサーバとしても使用
性能が飽和していないため台数を増やして検証したい
EC2内での物理ネットワーク構成が不定なためか性能が安定しない
トランザクションなしでのアクセスと比較したい
トランザクションなしでは12万qpsほどが上限だったので
1サーバでの性能である約600qpsはおよそ1/200ほどの速度低下
これは想定値より遥かに低いので原因の究明が必要
既存のキーバリューストアの上でトランザクショ
ン処理を行う方法を提案し実装・評価した
更なる評価を行う必要がある
トランザクションを利用した範囲検索の実装
書き換えの遅延化でスループットの向上
© Copyright 2026 ExpyDoc