トランザクションをSerializableにする4つの方法

47
トトトトトトトトト Serializable トトト 4トトトト 2015 ト 12 ト 18 ト トト トト @kumagi

Transcript of トランザクションをSerializableにする4つの方法

Page 1: トランザクションをSerializableにする4つの方法

トランザクションをSerializableにする 4 つの方法

2015 年 12 月 18 日熊崎 宏樹 @kumagi

Page 2: トランザクションをSerializableにする4つの方法

Serializable とは?• ユーザの一連の操作(お金を口座 A から B へ

1000 円移動する、等)が「時系列上のどこかの瞬間に実行された」と解釈できる事が保証された一貫性モデル– 実際に一瞬で実行されたかはどうでもいい– どこかの瞬間であれば、 1 年前でも 1 億年後でも ( 定義上 ) 構わない

Page 3: トランザクションをSerializableにする4つの方法

Serializable とは?• 複数のユーザの動作をどこかの瞬間に論理的にプロットした時に矛盾が起きない組み合わせが一つでも存在するなら OK– 順番が狂うなどもどうでも良い

時間

User3User2User1 A

BC

DE

上記トランザクション群の結果は B→A→C→E→D の順で実行された事にすれば何らかの「順」にはなったので Serializableである。

Page 4: トランザクションをSerializableにする4つの方法

Serializable とは?• 自分で書いた値が読めなくても一応

Serializable– 下の図では User1 はトランザクション A で書いた値をトランザクション B で読めない

時間

User1 A B

上記トランザクション群の結果は B→A の順で実行された事にすれば何らかの「順」にはなったので Serializable である。

Page 5: トランザクションをSerializableにする4つの方法

脱線 :Linearizable とは?• Serializable かつ、実行された瞬間が「操作の開始時」以後、「操作の終了時」以前という条件を満たす場合に Linearizable

– オレンジの線と緑の線が鈍角を描かなければ OK とも言える

時間

User3User2User1 A

BC

DE

B→A→C→D→E の順で実行されたとすれば、各トランザクションの実行時間中に実行が行われたと言えるのでLinearizable

Page 6: トランザクションをSerializableにする4つの方法

実際の DB はどうなのか• 有名どころの DB のデフォルト設定は、パフォーマンス上の問題から基本的に Serializable より緩い一貫性モデルを採用している

MySQL「大抵のユーザはデフォルトの REPEATABLE READで充分やで」

Oracle「 READ COMMITEDが一番良く使われるだろうしデフォルトにしといたで」

PostgreSQL「 READ COMMITEDがデフォルトやで」 DB2「 CUSOR STABILITYがデフォルトやで」

Page 7: トランザクションをSerializableにする4つの方法

データベース授業あるある           ___ _         / \ / \ キリッ.      / (ー) (ー)\     /   ⌒( __ 人 __ )⌒ \     |        |r┬-|      |     \     ` ー '´    /    ノ            \  / ´                ヽ  |     l              \ ヽ    - 一 ''''''"~~ ` `' ー --    、 -一 ''''''' ー - 、 .  ヽ ____ (⌒)(⌒)⌒)   )    (⌒ _ (⌒)⌒)⌒))

トランザクションは ACID を守ります。A は AtomicityC は ConsistencyI は IsolationD は Durabilityを意味します。

Page 8: トランザクションをSerializableにする4つの方法

データベース授業あるある          ___ _        / _ ノ  ヽ、 _ \        ミ ミ ミ o ゚ ( (●) ) ( (●) ) ゚ o           ミ ミ ミ/⌒)⌒)⌒. ::::::⌒ ( __ 人 __ )⌒ ::: \    /⌒)⌒)⌒) |   /   /   /       |r┬-|      |   (⌒)/   / / / /|   :::::::::::(⌒)   |   | |    /  ゝ :::::::::::/|      ノ     |   | |    \   /   )   /ヽ     /      ` ー '´    ヽ /     /  |      |    l||l  从人 l||l      l||l 从人 l||l   バンバン ヽ    - 一 ''''''"~~ ` `' ー --  、 - 一 ''''''' ー - 、  ヽ ____ (⌒)(⌒)⌒)   )    (⌒ _ (⌒)⌒)⌒))

だっておwwwwwwww守ってないおwwww遅くてやってられないおwwww

Page 9: トランザクションをSerializableにする4つの方法

実用上大丈夫なのか• パフォーマンスは出るし SQL の書き方次第で何とかなる( SELECT FOR UPDATE うんぬん)• それで話が終わるとよくないので、ここでおもむろに Serializable 以外の分離レベルの落とし穴を紹介

Page 10: トランザクションをSerializableにする4つの方法

アプリ例:病院予約アプリ• 医師が複数居る– 全ての医師は初めみんな「診察可能」

• 患者がやってきて医師の一覧を見て一人選んで治療の予約を入れる– 選ばれた医師の状態は「予約済み」になる

• 急患に備え、非常時以外では医師は最低 1 人は「診察可能」状態でなければならない– その条件を満たせないなら予約を断る

• どう実装するか?

Page 11: トランザクションをSerializableにする4つの方法

ロジック例

SET @avail SELECT COUNT(*) FROM doctors WHERE state == “診察可能” ;IF @avail <= 1 BEGIN -- 医師が一人以下なら RAISEERROR(“診察可能な医師が居なくなります” );ELSE -- 医師が二人以上いるなら UPDATE doctors SET state == “予約済み” WHERE state ==“診察可能” AND name == “田中医者丸” ; COMMIT;END

• 以下のロジックで良さそうに見える– なお発表者はストアドプロシージャの文法に詳しくないので以下はフィーリングでお読みください

Page 12: トランザクションをSerializableにする4つの方法

ロジック例• トランザクション内で行われる個々の動作を青い線で表記

時間

User医師何人 ?

この 3 ステップの完了をひとまとまりのトランザクションとして実行する。 All or Nothing で実行されるのできっと上手く行くように思える。なお、 MVCC の実装上、コミット前の write はローカルなwrite set に閉じる事が多いが、細かい事は考えないようにする。

2 人居るで

じゃあ佐藤さんヨロ

ええで

コミット頼む

OK

Page 13: トランザクションをSerializableにする4つの方法

Write Skew

• 実際にはうまく行かない– 一覧の取得は命じたが書き換えを禁じてないから commit がそのまま通る

時間

User2User1

医師何人 ?

医師何人?

上記トランザクション群の結果として、診察可能な医師が 0人になる。これは User1 と User2 のどちらが先に実行した事にもできない。良さそうなロジックなのにバグってる有名な一例。

2 人居るで 2 人居るで

じゃあ佐藤さんヨロじゃあ田中さんヨロ

ええで ええで

コミット頼む コミット頼む

OK OK

Page 14: トランザクションをSerializableにする4つの方法

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 の順序は同時に実現できない

Page 15: トランザクションをSerializableにする4つの方法

さあどうする?• 診察可能医師を SELECT する際に SELECT FOR UPDATE を利用する(アプリロジックへの侵入)• “急患待ち”という新たな状態を定義して医師を先にそこに割り当ててしまう(業務ロジックへの侵入)• “急患待ち”の医師が 4 人以上になるシステムにしておけば Write Skew が 3回起きる可能性はかなり低いでしょ(運用でカバーする例)• “急患待ち”状態の医師なんて別に要らんかったんや。緊急なら診察中の他の医師が何とかするでしょ(解決を諦める例)• 分離レベルに SERIALIZABLEを指定 DB の問題は

DB で解決

Page 16: トランザクションをSerializableにする4つの方法

解決策• 2 Phase Lock を行う• Read-Tracking を行う (SSI)• Read-Validation を行う (SILO その他 )• SQL のレイヤで先に Serializable にしてしまう

Page 17: トランザクションをSerializableにする4つの方法

解決策• 2 Phase Lock を行う• Read-Tracking を行う (SSI)• Read-Validation を行う (SILO その他 )• SQL のレイヤで先に Serializable にしてしまう

Page 18: トランザクションをSerializableにする4つの方法

2 Phase Lock

• 手続き全体が成長相と減退相の 1 つずつからなるべし

1 2

獲得しているロックの数

時間

成長相 減退相

Page 19: トランザクションをSerializableにする4つの方法

2 Phase Lock

• つまりこれはだめ

成長相・減退相が 2回以上ある

Page 20: トランザクションをSerializableにする4つの方法

Strict 2 Phase Lock(S2PL)• 2 Phase Lock に加えて「コミットが済むまで一切ロック解放するべからず」という条件を加えた物

– カスケードロールバックを回避できるので実用上 2PL と言ったらこれの事を指す事が多い [要出典 ]

1 2

獲得しているロックの数

時間

成長相 減退相コミットのタイミングはこれより右に行ってはいけない

Page 21: トランザクションをSerializableにする4つの方法

2 Phase Lock

• なんでこの方法なら Serializable になるのかはPhilip A. Bernstein, Vassos Hadzilacos, Nathan Goodman (1987): Concurrency Control and Recovery in Database Systems を参照

Page 22: トランザクションをSerializableにする4つの方法

解決策• 2 Phase Lock を行う• Read-Tracking を行う (SSI)• Read-Validation を行う (SILO その他 )• SQL のレイヤで先に Serializable にしてしまう

Page 23: トランザクションをSerializableにする4つの方法

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

Page 24: トランザクションをSerializableにする4つの方法

Snapshot Isolation• Write Skew Anomaly と Read Only Anomaly 以外の Anomalyが起きないとされているトランザクション分離レベル• 全てのトランザクションは、トランザクション開始時のデータベースのスナップショットのみを見ながら走る、という分離レベル

– Read Repeatable と違ってファントムリード等も起きない– 物理的に全体のスナップショットを作ったりはしない

• 実装上は MVCC(Multi-Version Concurrency Control) が用いられる事が多いが、厳密にはそれだけでは SI にできない– 理由を説明するにはこの余白は狭すぎる

Page 25: トランザクションをSerializableにする4つの方法

基本アイデア• 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]

Page 26: トランザクションをSerializableにする4つの方法

Dependency Serialization Graph

時間

BA

医師何人 ?

医師何人?

2 人居るで 2 人居るで

じゃあ佐藤さんヨロじゃあ田中さんヨロ

ええで あかんで

コミット頼む

OK

• 「読んだものを書いた」「書いた物を読んだ」「書いた後に書いた」のいずれかの順序関係がある場合に依存辺を追加していく

A A B A B

B が読んだ値に A が書き込むのでAnti-Dependency (問題ない)

A B

A が読んだ値に B が書き込むのでAnti-Dependency→ 円環できた

Page 27: トランザクションをSerializableにする4つの方法

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

Page 28: トランザクションをSerializableにする4つの方法

パフォーマンス測定• SI と比べてほとんどパフォーマンス低下なし

出典 : Making Snapshot Isolation Serializable

Page 29: トランザクションをSerializableにする4つの方法

DSG について• 円環ができたら abort という割とすっきりしたアルゴリズム• 速度もそれなりに出る• False Positive(必要のない abort) が起きる– 詳細は論文に

• マルチコアにスケールしようと思ったら複数の reader が DSG の更新のためにキャッシュラインを取り合うのでスケールしにくい

Page 30: トランザクションをSerializableにする4つの方法

解決策• 2 Phase Lock を行う• Read-Tracking を行う (SSI)• Read-Validation を行う (SILO その他 )• SQL のレイヤで先に Serializable にしてしまう

Page 31: トランザクションをSerializableにする4つの方法

Read Validation

• コミットのタイミングで自分が読んだものと同じ物を読んだと確認すれば良い– いわゆる Optimistic Concurrency Control

• 全てのデータに単調増加するバージョンをつける• Read Lock を取る代わりにバージョンを記録する• トランザクションをコミットする際に必要な

Write Lock を獲得してから Read Set のバージョンを比較する• すべて一致したらコミットを認める

Page 32: トランザクションをSerializableにする4つの方法

Write Skew

• Read のバージョン比較で簡単に解決する

時間

User2User1

医師何人 ?

医師何人?

それぞれのトランザクションは医師 2 人のデータのバージョンを取得している。2 人居るで 2 人居るで

じゃあ佐藤さんヨロじゃあ田中さんヨロ

ええで ええで

コミット頼む コミット頼む

OK あかんで

ここでバージョンあがる

Page 33: トランザクションをSerializableにする4つの方法

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

あかんで

Page 34: トランザクションをSerializableにする4つの方法

永続性の話• 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

Page 35: トランザクションをSerializableにする4つの方法

永続性の話• もし後に来たはずの T2 しか書き込めなかったら T1 リカバリはどうなるの?– もっとややこしいストーリーはまだまだ出てくる

X

Y

Z

WL

validation

Log T1

Log T2

WL

WL Crash!

Page 36: トランザクションをSerializableにする4つの方法

バージョン比較の話• 単調に増えるバージョンをデータ毎に個々に管理するとまずい(特に ARIES 的な意味で LSN はリカバリに不可欠)– グローバルなカウンタで LSN を管理するのが普通

• LSN は全トランザクションが増やし続けるのでパフォーマンスボトルネックになる–キャッシュコヒーレントトラフィックがヤバい

Page 37: トランザクションをSerializableにする4つの方法

Silo

• LSN の代わりに Epoch というグローバルカウンタを共有• 40msごとに Epoch の数字を増やす専用のスレッドが居る–他のスレッドは読むだけなのでロックは要らない

• 各トランザクションはコミットできる瞬間になったら自分の TransactionID(TID) を取得する– コミット前の Epoch確認の瞬間が Serialization

Point となる。

Page 38: トランザクションをSerializableにする4つの方法

Silo

• TID(64bits) は以下の構成– 上位ビット (32bits?) は epoch– 中位ビット (余り ) は他と被らない値– 下位ビット (3bit) はフラグ (lock/latest-version/absent) を保持

• 決定する際は以下のプロトコルで決める– Read/Write-Set の全てのレコードより大きくて– 自分の過去のトランザクションより大きくて– 現在の Epoch を利用した– 最小の値

Page 39: トランザクションをSerializableにする4つの方法

Silo のログ• ログは各ワーカーがローカルに Epochごとにディスクに吐き出す– 全部のワーカーの同一 Epoch のログをかき集めればその Epoch 内の全ての更新が出来上がる

• 同一 Epoch 内でのログの全順序性は問わない– 半順序性は TID決定プロトコルで保障される

–永続化のタイミングは全てのワーカーの同一Epoch が書き出された時に Group Commit される

– リカバリの際は同一 Epoch 内の全てのログが集まらないと永続化されているとみなさない

Page 40: トランザクションをSerializableにする4つの方法

永続性の話• Epoch が全トランザクションの有効性を保障する

X

Y

Z

T1, T2 が同一 Epoch の場合

WL

validation

Log T1

Log T2

WL

WL

T2 のログは同一 Epoch である T1 のログが残っていない場合無効になる

Crash!

Page 41: トランザクションをSerializableにする4つの方法

永続性の話• Epoch が全トランザクションの有効性を保障する

X

Y

Z

T1 と T2 の Serialization Point の間に Epoch の境界がある場合WL

validation

Log T1

Log T2

WL

WL Crash!

T2 の Epoch のほうが T1 の Epoch より進んでいるので無効化される

Page 42: トランザクションをSerializableにする4つの方法

Silo まとめ• 他にもいくつも技術が詰め込まれてて話したいこといっぱいあるけど力不足で説明しきれない• 32 コアで TPC-C が 70万トランザクション /秒とかすごい– ただしクエリは C++ でローカル直書きなので TPC-C スコアそのものではない

• これをさらに改良したという「 FOEDUS 」がHPE から OSS で出てる– こっちもいろいろ語りたいけど時間がない

Page 43: トランザクションをSerializableにする4つの方法

解決策• 2 Phase Lock を行う• Read-Tracking を行う (SSI)• Read-Validation を行う (SILO その他 )• 先に Serializable にしてしまう

Page 44: トランザクションをSerializableにする4つの方法

いつ Serialize するか?• クライアントからのリクエストに One

Shot モデルを採用してよいなら届いた瞬間にキューに放り込んでその順に実行された事にしてしまえばいい• Rethinking serializable multiversion

concurrency control(Jose ら VLDB2015) などで説明されてる。• 発表資料作る時間なかったのでごめんなさい

Page 45: トランザクションをSerializableにする4つの方法

Master

BOHM: 大まかな概観図

master

User3User2User1 A

BC

DE

DECAB

worker1 worker2 worker3 worker4

行ごとに分割統治するShared Nothing アーキテクチャ

受け付けた順に処理する

Page 46: トランザクションをSerializableにする4つの方法

BOHM ベンチマーク (YCSB)

• パフォーマンスは出てるが圧倒的でもない• Hekaton と 2PL がいい勝負してる… !?

Page 47: トランザクションをSerializableにする4つの方法

他になかったの?• 4 つしかないとは誰も言ってない• いくつ思いつくかでトランザクションの知識量を測れるかも