トランザクションをSerializableにする4つの方法
-
Upload
kumazaki-hiroki -
Category
Technology
-
view
8.143 -
download
0
Transcript of トランザクションをSerializableにする4つの方法
トランザクションをSerializableにする 4 つの方法
2015 年 12 月 18 日熊崎 宏樹 @kumagi
Serializable とは?• ユーザの一連の操作(お金を口座 A から B へ
1000 円移動する、等)が「時系列上のどこかの瞬間に実行された」と解釈できる事が保証された一貫性モデル– 実際に一瞬で実行されたかはどうでもいい– どこかの瞬間であれば、 1 年前でも 1 億年後でも ( 定義上 ) 構わない
Serializable とは?• 複数のユーザの動作をどこかの瞬間に論理的にプロットした時に矛盾が起きない組み合わせが一つでも存在するなら OK– 順番が狂うなどもどうでも良い
時間
User3User2User1 A
BC
DE
上記トランザクション群の結果は B→A→C→E→D の順で実行された事にすれば何らかの「順」にはなったので Serializableである。
Serializable とは?• 自分で書いた値が読めなくても一応
Serializable– 下の図では User1 はトランザクション A で書いた値をトランザクション B で読めない
時間
User1 A B
上記トランザクション群の結果は B→A の順で実行された事にすれば何らかの「順」にはなったので Serializable である。
脱線 :Linearizable とは?• Serializable かつ、実行された瞬間が「操作の開始時」以後、「操作の終了時」以前という条件を満たす場合に Linearizable
– オレンジの線と緑の線が鈍角を描かなければ OK とも言える
時間
User3User2User1 A
BC
DE
B→A→C→D→E の順で実行されたとすれば、各トランザクションの実行時間中に実行が行われたと言えるのでLinearizable
実際の DB はどうなのか• 有名どころの DB のデフォルト設定は、パフォーマンス上の問題から基本的に Serializable より緩い一貫性モデルを採用している
MySQL「大抵のユーザはデフォルトの REPEATABLE READで充分やで」
Oracle「 READ COMMITEDが一番良く使われるだろうしデフォルトにしといたで」
PostgreSQL「 READ COMMITEDがデフォルトやで」 DB2「 CUSOR STABILITYがデフォルトやで」
データベース授業あるある ___ _ / \ / \ キリッ. / (ー) (ー)\ / ⌒( __ 人 __ )⌒ \ | |r┬-| | \ ` ー '´ / ノ \ / ´ ヽ | l \ ヽ - 一 ''''''"~~ ` `' ー -- 、 -一 ''''''' ー - 、 . ヽ ____ (⌒)(⌒)⌒) ) (⌒ _ (⌒)⌒)⌒))
トランザクションは ACID を守ります。A は AtomicityC は ConsistencyI は IsolationD は Durabilityを意味します。
データベース授業あるある ___ _ / _ ノ ヽ、 _ \ ミ ミ ミ o ゚ ( (●) ) ( (●) ) ゚ o ミ ミ ミ/⌒)⌒)⌒. ::::::⌒ ( __ 人 __ )⌒ ::: \ /⌒)⌒)⌒) | / / / |r┬-| | (⌒)/ / / / /| :::::::::::(⌒) | | | / ゝ :::::::::::/| ノ | | | \ / ) /ヽ / ` ー '´ ヽ / / | | l||l 从人 l||l l||l 从人 l||l バンバン ヽ - 一 ''''''"~~ ` `' ー -- 、 - 一 ''''''' ー - 、 ヽ ____ (⌒)(⌒)⌒) ) (⌒ _ (⌒)⌒)⌒))
だっておwwwwwwww守ってないおwwww遅くてやってられないおwwww
実用上大丈夫なのか• パフォーマンスは出るし SQL の書き方次第で何とかなる( SELECT FOR UPDATE うんぬん)• それで話が終わるとよくないので、ここでおもむろに Serializable 以外の分離レベルの落とし穴を紹介
アプリ例:病院予約アプリ• 医師が複数居る– 全ての医師は初めみんな「診察可能」
• 患者がやってきて医師の一覧を見て一人選んで治療の予約を入れる– 選ばれた医師の状態は「予約済み」になる
• 急患に備え、非常時以外では医師は最低 1 人は「診察可能」状態でなければならない– その条件を満たせないなら予約を断る
• どう実装するか?
ロジック例
SET @avail SELECT COUNT(*) FROM doctors WHERE state == “診察可能” ;IF @avail <= 1 BEGIN -- 医師が一人以下なら RAISEERROR(“診察可能な医師が居なくなります” );ELSE -- 医師が二人以上いるなら UPDATE doctors SET state == “予約済み” WHERE state ==“診察可能” AND name == “田中医者丸” ; COMMIT;END
• 以下のロジックで良さそうに見える– なお発表者はストアドプロシージャの文法に詳しくないので以下はフィーリングでお読みください
ロジック例• トランザクション内で行われる個々の動作を青い線で表記
時間
User医師何人 ?
この 3 ステップの完了をひとまとまりのトランザクションとして実行する。 All or Nothing で実行されるのできっと上手く行くように思える。なお、 MVCC の実装上、コミット前の write はローカルなwrite set に閉じる事が多いが、細かい事は考えないようにする。
2 人居るで
じゃあ佐藤さんヨロ
ええで
コミット頼む
OK
Write Skew
• 実際にはうまく行かない– 一覧の取得は命じたが書き換えを禁じてないから commit がそのまま通る
時間
User2User1
医師何人 ?
医師何人?
上記トランザクション群の結果として、診察可能な医師が 0人になる。これは User1 と User2 のどちらが先に実行した事にもできない。良さそうなロジックなのにバグってる有名な一例。
2 人居るで 2 人居るで
じゃあ佐藤さんヨロじゃあ田中さんヨロ
ええで ええで
コミット頼む コミット頼む
OK OK
Read Only• 詳細は A Read-Only Transaction Anomaly Under Snapshot
Isolation(Alan ら 2004) を参照• 以下の実行は Serialize できない
時間
YX
口座 A を確認
0 円 OK
Z
口座 B を確認
0 円
口座 A に 20万預ける 口座 B から 10万引き出す (A+B が負になるので金利込の 11万を減らす )口座 A を確認
0 円
口座 B を確認口座 A を確認コミット
20万円コミット {A: 20, B: 0}
20万円OK 0 円 -11万円X→Y と仮定すると、最終状態が {A:20, B:-10} にならないとおかしい つまり Y は必ず X の前に来ないとダメY→X→Z と仮定すると、 Z は {A: 20, B: -11} を読まないとおかしい つまり Z は Y より前で X より後じゃないとダメY→X の順序と、 X→Z→Y の順序は同時に実現できない
さあどうする?• 診察可能医師を SELECT する際に SELECT FOR UPDATE を利用する(アプリロジックへの侵入)• “急患待ち”という新たな状態を定義して医師を先にそこに割り当ててしまう(業務ロジックへの侵入)• “急患待ち”の医師が 4 人以上になるシステムにしておけば Write Skew が 3回起きる可能性はかなり低いでしょ(運用でカバーする例)• “急患待ち”状態の医師なんて別に要らんかったんや。緊急なら診察中の他の医師が何とかするでしょ(解決を諦める例)• 分離レベルに SERIALIZABLEを指定 DB の問題は
DB で解決
解決策• 2 Phase Lock を行う• Read-Tracking を行う (SSI)• Read-Validation を行う (SILO その他 )• SQL のレイヤで先に Serializable にしてしまう
解決策• 2 Phase Lock を行う• Read-Tracking を行う (SSI)• Read-Validation を行う (SILO その他 )• SQL のレイヤで先に Serializable にしてしまう
2 Phase Lock
• 手続き全体が成長相と減退相の 1 つずつからなるべし
1 2
獲得しているロックの数
時間
成長相 減退相
2 Phase Lock
• つまりこれはだめ
成長相・減退相が 2回以上ある
Strict 2 Phase Lock(S2PL)• 2 Phase Lock に加えて「コミットが済むまで一切ロック解放するべからず」という条件を加えた物
– カスケードロールバックを回避できるので実用上 2PL と言ったらこれの事を指す事が多い [要出典 ]
1 2
獲得しているロックの数
時間
成長相 減退相コミットのタイミングはこれより右に行ってはいけない
2 Phase Lock
• なんでこの方法なら Serializable になるのかはPhilip A. Bernstein, Vassos Hadzilacos, Nathan Goodman (1987): Concurrency Control and Recovery in Database Systems を参照
解決策• 2 Phase Lock を行う• Read-Tracking を行う (SSI)• Read-Validation を行う (SILO その他 )• SQL のレイヤで先に Serializable にしてしまう
Read Uncommited
Read Commited
Repeatable Read
Snapshot Isolation
トランザクション分離レベル• Serializable という安全地帯から出ると様々な魔物( Anomaly )が襲い掛かってくる
Serializalbe
Write Skew Anomaly
Phantom Read Anomaly
Read Only Anomaly
Non-Repeatable Read Anomaly
Dirty Read Anomaly
Snapshot Isolation• Write Skew Anomaly と Read Only Anomaly 以外の Anomalyが起きないとされているトランザクション分離レベル• 全てのトランザクションは、トランザクション開始時のデータベースのスナップショットのみを見ながら走る、という分離レベル
– Read Repeatable と違ってファントムリード等も起きない– 物理的に全体のスナップショットを作ったりはしない
• 実装上は MVCC(Multi-Version Concurrency Control) が用いられる事が多いが、厳密にはそれだけでは SI にできない– 理由を説明するにはこの余白は狭すぎる
基本アイデア• Snapshot Isolation で問題になるのは Write Skew Anomalyと Read Only Anomaly[Alan ら SIGMOD04] だから、それらが起きなくなる仕組みを用意すれば Serializable と呼んで良いんじゃね?
– 1999 年ごろからそのアイデアはあった– ユーザの SQL 文を検査して Anomaly が起きそうかどうか静的チェックする方法なども検討された
• その結果、 TPC-C の実行は Snapshot Isolation でも Anomaly が起きないパターンである事が証明されたりなどした• Snapshot Isolation で発生する Anomaly を検出する方法考えました! Dependency Cyclic Graph(DSG) を書けばいけます!
– paper: Making Snapshot Isolation Serializable [Alan ら 2005]
Dependency Serialization Graph
時間
BA
医師何人 ?
医師何人?
2 人居るで 2 人居るで
じゃあ佐藤さんヨロじゃあ田中さんヨロ
ええで あかんで
コミット頼む
OK
• 「読んだものを書いた」「書いた物を読んだ」「書いた後に書いた」のいずれかの順序関係がある場合に依存辺を追加していく
A A B A B
B が読んだ値に A が書き込むのでAnti-Dependency (問題ない)
A B
A が読んだ値に B が書き込むのでAnti-Dependency→ 円環できた
Dependency Serialization Graph• ついでに Read Only Anomaly も対処できる
時間
BA
口座 A を確認
0 円 OK
A A B A B
B が読んだ値に A が書き込むのでAnti-Dependency (問題ない)
A がコミットした値を C が読むのでDependency
C
口座 B を確認
0 円
口座 A に 20万預ける 口座 B から 10万引き出す (A+B が負になるので金利込の 11万を減らす口座 A を確認
0 円
口座 B を確認口座 A を確認コミット
20万円
コミット
20万円OK 0 円 あかんで
A BC
C が読んだ値にB が書き込んだので
Anti-Dependency→ 円環できた
A BC
パフォーマンス測定• SI と比べてほとんどパフォーマンス低下なし
出典 : Making Snapshot Isolation Serializable
DSG について• 円環ができたら abort という割とすっきりしたアルゴリズム• 速度もそれなりに出る• False Positive(必要のない abort) が起きる– 詳細は論文に
• マルチコアにスケールしようと思ったら複数の reader が DSG の更新のためにキャッシュラインを取り合うのでスケールしにくい
解決策• 2 Phase Lock を行う• Read-Tracking を行う (SSI)• Read-Validation を行う (SILO その他 )• SQL のレイヤで先に Serializable にしてしまう
Read Validation
• コミットのタイミングで自分が読んだものと同じ物を読んだと確認すれば良い– いわゆる Optimistic Concurrency Control
• 全てのデータに単調増加するバージョンをつける• Read Lock を取る代わりにバージョンを記録する• トランザクションをコミットする際に必要な
Write Lock を獲得してから Read Set のバージョンを比較する• すべて一致したらコミットを認める
Write Skew
• Read のバージョン比較で簡単に解決する
時間
User2User1
医師何人 ?
医師何人?
それぞれのトランザクションは医師 2 人のデータのバージョンを取得している。2 人居るで 2 人居るで
じゃあ佐藤さんヨロじゃあ田中さんヨロ
ええで ええで
コミット頼む コミット頼む
OK あかんで
ここでバージョンあがる
Read Only
• バージョン比較強い
時間
YX
口座 A を確認
0 円 OK
Z
口座 B を確認
0 円
口座 A に 20万預ける 口座 B から 10万引き出す ( 金利込の 11万を減らす )口座 A を確認
0 円
口座 B を確認口座 A を確認コミット
20万円コミット {A: 20, B: 0}
20万円OK 0 円 -11万円
ここで A のバージョンがあがる
コミット
A のバージョン不一致でabort
あかんで
永続性の話• Read-Lock を取らないのはややこしい問題をひき起こす–具体的には S2PL と同様にならない
X
Y
Z
T1: Y を読んで X に上書きする (Y=0 だったので X==0へ )T2: Y と Z に書きこむ (Y=Z=10 と書き込む )
WL
validation
Log T1
Log T2
WL
WL
トランザクション的には T1→T2 の順に滞りなくシリアライズされたといえるがログに書き込まれる順序はT2→T1
永続性の話• もし後に来たはずの T2 しか書き込めなかったら T1 リカバリはどうなるの?– もっとややこしいストーリーはまだまだ出てくる
X
Y
Z
WL
validation
Log T1
Log T2
WL
WL Crash!
バージョン比較の話• 単調に増えるバージョンをデータ毎に個々に管理するとまずい(特に ARIES 的な意味で LSN はリカバリに不可欠)– グローバルなカウンタで LSN を管理するのが普通
• LSN は全トランザクションが増やし続けるのでパフォーマンスボトルネックになる–キャッシュコヒーレントトラフィックがヤバい
Silo
• LSN の代わりに Epoch というグローバルカウンタを共有• 40msごとに Epoch の数字を増やす専用のスレッドが居る–他のスレッドは読むだけなのでロックは要らない
• 各トランザクションはコミットできる瞬間になったら自分の TransactionID(TID) を取得する– コミット前の Epoch確認の瞬間が Serialization
Point となる。
Silo
• TID(64bits) は以下の構成– 上位ビット (32bits?) は epoch– 中位ビット (余り ) は他と被らない値– 下位ビット (3bit) はフラグ (lock/latest-version/absent) を保持
• 決定する際は以下のプロトコルで決める– Read/Write-Set の全てのレコードより大きくて– 自分の過去のトランザクションより大きくて– 現在の Epoch を利用した– 最小の値
Silo のログ• ログは各ワーカーがローカルに Epochごとにディスクに吐き出す– 全部のワーカーの同一 Epoch のログをかき集めればその Epoch 内の全ての更新が出来上がる
• 同一 Epoch 内でのログの全順序性は問わない– 半順序性は TID決定プロトコルで保障される
–永続化のタイミングは全てのワーカーの同一Epoch が書き出された時に Group Commit される
– リカバリの際は同一 Epoch 内の全てのログが集まらないと永続化されているとみなさない
永続性の話• Epoch が全トランザクションの有効性を保障する
X
Y
Z
T1, T2 が同一 Epoch の場合
WL
validation
Log T1
Log T2
WL
WL
T2 のログは同一 Epoch である T1 のログが残っていない場合無効になる
Crash!
永続性の話• Epoch が全トランザクションの有効性を保障する
X
Y
Z
T1 と T2 の Serialization Point の間に Epoch の境界がある場合WL
validation
Log T1
Log T2
WL
WL Crash!
T2 の Epoch のほうが T1 の Epoch より進んでいるので無効化される
Silo まとめ• 他にもいくつも技術が詰め込まれてて話したいこといっぱいあるけど力不足で説明しきれない• 32 コアで TPC-C が 70万トランザクション /秒とかすごい– ただしクエリは C++ でローカル直書きなので TPC-C スコアそのものではない
• これをさらに改良したという「 FOEDUS 」がHPE から OSS で出てる– こっちもいろいろ語りたいけど時間がない
解決策• 2 Phase Lock を行う• Read-Tracking を行う (SSI)• Read-Validation を行う (SILO その他 )• 先に Serializable にしてしまう
いつ Serialize するか?• クライアントからのリクエストに One
Shot モデルを採用してよいなら届いた瞬間にキューに放り込んでその順に実行された事にしてしまえばいい• Rethinking serializable multiversion
concurrency control(Jose ら VLDB2015) などで説明されてる。• 発表資料作る時間なかったのでごめんなさい
Master
BOHM: 大まかな概観図
master
User3User2User1 A
BC
DE
DECAB
worker1 worker2 worker3 worker4
行ごとに分割統治するShared Nothing アーキテクチャ
受け付けた順に処理する
BOHM ベンチマーク (YCSB)
• パフォーマンスは出てるが圧倒的でもない• Hekaton と 2PL がいい勝負してる… !?
他になかったの?• 4 つしかないとは誰も言ってない• いくつ思いつくかでトランザクションの知識量を測れるかも