transaction puzzlers
DESCRIPTION
Transaction Puzzlers. appengine ja night #4 あらかわ (@ashigeru). 講演者について. 名前 あらかわ (@ashigeru) 所属 株式会社グルージェント 開発部 普段の業務 研究開発 ( コンパイラ系 ) 教育 (Computer Aided Education) ブログ書き (Song of Cloud Blog). Song of Cloud Blog. 会社ブログ http://songofcloud.gluegent.com App Engine のポータルサイトをコンセプトに - PowerPoint PPT PresentationTRANSCRIPT
Transaction Puzzlers
appengine ja night #4あらかわ (@ashigeru)
appengine ja night #4 - @ashigeru
22010/01/22
講演者について 名前
あらかわ (@ashigeru) 所属
株式会社グルージェント 開発部 普段の業務
研究開発 ( コンパイラ系 )教育 (Computer Aided Education)ブログ書き (Song of Cloud Blog)
appengine ja night #4 - @ashigeru
32010/01/22
Song of Cloud Blog 会社ブログ
http://songofcloud.gluegent.com App Engine のポータルサイトをコンセプトに
By arakawa (App Engine 関連 ) Slim3 Datastore に乗り換える テキスト部分一致検索 送金のトランザクション処理パターン 分散トランザクション処理の最適化 SDK 1.2.8 Release Notes で語られなかったこと App Engine SDK 1.3.0 (overview) App Engine JDP Tips グローバルトランザクション処理のパターン
appengine ja night #4 - @ashigeru
42010/01/22
今日の内容 トランザクション処理の考え方 トランザクション処理のパターン
appengine ja night #4 - @ashigeru
52010/01/22
トランザクション処理の考え方 リソースを一時的に独占できる技術
同時に変更して不整合が起こる、などを回避
今回は悲観的 / 楽観的をあまり気にしないApp Engine は楽観的並行性制御いずれも一時的にリソースを独占できる設計 / 実装時には考慮する必要がある
appengine ja night #4 - @ashigeru
62010/01/22
App Engine のトランザクション トランザクションは Entity Group (EG) 単位
同一 EG 内のエンティティに対する操作は ACID複数 EG にまたがる操作は対応していない
appengine ja night #4 - @ashigeru
72010/01/22
Entity Group の構成 同じルートキーを持つエンティティ群
データストア上で近くに配置される 例
Foo(A)Foo(A)/Hoge(B)Foo(B)Bar(A)/Foo(A)Bar(A)/Foo(B)/Hoge(D)
EG: Foo(A)
EG: Foo(B)
EG: Bar(A)
appengine ja night #4 - @ashigeru
82010/01/22
Entity Group の特徴 ポイント
トランザクションの範囲はエンティティ作成時に決まり、変更できない
EG を大きくするとトランザクションで独占するエンティティが多くなる
EG の設計が非常に重要に間違えると並列性が極端に低下するうまくやればスケールアウトする
appengine ja night #4 - @ashigeru
92010/01/22
ここまでのまとめ (1)
App Engine のトランザクションは EG 単位EG 内では ACID トランザクションEG をまたぐトランザクションは未サポート
EG の設計によっては並列性が落ちるEG を大きくすると独占範囲が広がるEG を分断すると整合性を保つのが困難
appengine ja night #4 - @ashigeru
102010/01/22
トランザクション処理のパターン App Engine のトランザクションはやや特殊
パターンで対応したほうがよさそう 本日紹介するもの
Read-modify-write トランザクションの合成 ユニーク制約 冪 ( べき ) 等な処理 Exactly Once BASE Transaction
appengine ja night #4 - @ashigeru
112010/01/22
注意点 プログラムの説明に擬似コードを多用
言語は Javascript ライク API は Java の Low-Level API ライク
見慣れない言語要素 キーリテラル – KEY:…
KEY:Foo(A), KEY:Foo(A)/Bar(B), など データストア
get(tx, key), put(tx, entity), beginTransaction() タスクキュー
enqueue([tx,] statement)
appengine ja night #4 - @ashigeru
122010/01/22
パターン : read-modify-write
エンティティのプロパティを変更する 例 :
カウンタの増加ショッピングカートに商品を追加
現在の値をもとに次の値が決まる読む、変更、書き戻す、の 3 ステップが必要途中で割り込まれると不整合が起こる
appengine ja night #4 - @ashigeru
132010/01/22
read-modify-write (1)
考え方読んでから書き戻すまでエンティティを独占
100 101
100 + 1
appengine ja night #4 - @ashigeru
142010/01/22
read-modify-write (2)
var tx = beginTransaction()try { var counter = get(tx, KEY:Counter(C)) counter.value++ put(tx, counter) tx.commit()}finally { if (tx.isActive()) tx.rollback()}
appengine ja night #4 - @ashigeru
152010/01/22
read-modify-write (3)
var tx = beginTransaction()try { var counter = get(tx, KEY:Counter(C)) counter.value++ put(tx, counter) tx.commit()}finally { if (tx.isActive()) tx.rollback()}
読んでから書き戻すまでを ACID に行う
appengine ja night #4 - @ashigeru
162010/01/22
DSL: atomic (tx) { … } 以後は下記のように省略
トランザクションの開始と終了を簡略化
atomic(tx) { var counter = get(tx, KEY:Counter(C)) counter.value++ put(tx, counter)}
appengine ja night #4 - @ashigeru
172010/01/22
パターン : トランザクションの合成 同じ EG に対する複数のトランザクション
処理を合成 例 :
2 つのカウンタを同時に変更 ( 恣意的 )非正規化した 2 つの情報を同時に更新
注意点分断したトランザクションでは、途中で失敗
した際に修復が大変
appengine ja night #4 - @ashigeru
182010/01/22
トランザクションの合成 (1)
考え方同じ EG のトランザクションが 2 つあったら、
一度に処理してしまう
15 16
30 31
appengine ja night #4 - @ashigeru
192010/01/22
トランザクションの合成 (2)
atomic(tx) { var a = get(tx, KEY:Eg(C)/Counter(A)) a.value++ put(tx, a) var b = get(tx, KEY:Eg(C)/Counter(B)) b.value++ put(tx, b)}
appengine ja night #4 - @ashigeru
202010/01/22
トランザクションの合成 (3)
atomic(tx) { var a = get(tx, KEY:Eg(C)/Counter(A)) a.value++ put(tx, a) var b = get(tx, KEY:Eg(C)/Counter(B)) b.value++ put(tx, b)} 同じ EG のエンティテ
ィに対する操作
appengine ja night #4 - @ashigeru
212010/01/22
トランザクションの合成 (4)
atomic(tx) { var a = get(tx, KEY:Eg(C)/Counter(A)) a.value++ put(tx, a) var b = get(tx, KEY:Eg(C)/Counter(B)) b.value++ put(tx, b)} 複数のトランザクション
を合成 , 全体が ACIDに
appengine ja night #4 - @ashigeru
222010/01/22
パターン : ユニーク制約 重複するエンティティの登録を防止する 例 :
同じ ID を持つユーザの登録を防ぐダブルブッキングを防ぐ
注意点データストアは制約機能を組み込んでいないクエリはトランザクションに参加できない
appengine ja night #4 - @ashigeru
232010/01/22
ユニーク制約 (1)
考え方エンティティの入れ物ごと独占入れ物が空なら追加するエンティティは一意
@hoge @hoge @hoge
appengine ja night #4 - @ashigeru
242010/01/22
ユニーク制約 (2)
var key = KEY:User([email protected])atomic(tx) { var user = get(tx, key) if (user != null) { throw new NotUniqueException() } user = new User(key, ...) put(tx, user)}
appengine ja night #4 - @ashigeru
252010/01/22
ユニーク制約 (3)
var key = KEY:User([email protected])atomic(tx) { var user = get(tx, key) if (user != null) { throw new NotUniqueException() } user = new User(key, ...) put(tx, user)}
ユニーク制約をキーで表す ( メールアドレス )
appengine ja night #4 - @ashigeru
262010/01/22
ユニーク制約 (4)
var key = KEY:User([email protected])atomic(tx) { var user = get(tx, key) if (user != null) { throw new NotUniqueException() } user = new User(key, ...) put(tx, user)}
そのエンティティがすでにあれば制約違反
appengine ja night #4 - @ashigeru
272010/01/22
ユニーク制約 (5)
var key = KEY:User([email protected])atomic(tx) { var user = get(tx, key) if (user != null) { throw new NotUniqueException() } user = new User(key, ...) put(tx, user)}
存在しなければユニークなので追加
appengine ja night #4 - @ashigeru
282010/01/22
ユニーク制約 (6)
var key = KEY:User([email protected])atomic(tx) { var user = get(tx, key) if (user != null) { throw new NotUniqueException() } user = new User(key, ...) put(tx, user)} get から put までを独占
appengine ja night #4 - @ashigeru
292010/01/22
ここまでのまとめ (2)
read-modify-write最初に読んでから書き戻すまで独占
トランザクションの合成同一 EG に対する操作をまとめる
ユニーク制約入れ物を独占してからエンティティを作成すでにあったらユニークじゃないので失敗
appengine ja night #4 - @ashigeru
302010/01/22
パターン : 冪 ( べき ) 等な処理 1 回分しか効果を出さない処理
2 回以上成功しても、 1 回分しか反映しない 例 :
フォームの多重送信を防止お一人様一点限り。
注意点英語で idempotentだけど覚えにくい
appengine ja night #4 - @ashigeru
312010/01/22
冪等な処理 (1)
考え方「処理がユニークに成功する」ということまだ成功していなかったら成功させる一度成功していたら何もしない
成功 成功 成功
結果
appengine ja night #4 - @ashigeru
322010/01/22
冪等な処理 (2)var key = KEY:Counter(C)/Flag(unique)atomic(tx) { var flag = get(tx, key) if (flag != null) { return } put(tx, new Flag(key)) var counter = get(tx, KEY:Counter(C)) counter.value++ put(tx, counter)}
appengine ja night #4 - @ashigeru
332010/01/22
冪等な処理 (3)var key = KEY:Counter(C)/Flag(unique)atomic(tx) { var flag = get(tx, key) if (flag != null) { return } put(tx, new Flag(key)) var counter = get(tx, KEY:Counter(C)) counter.value++ put(tx, counter)}
「ユニークなキー」を表す→ db.allocate_ids()→ DatastoreService.allocateIds()
appengine ja night #4 - @ashigeru
342010/01/22
冪等な処理 (4)var key = KEY:Counter(C)/Flag(unique)atomic(tx) { var flag = get(tx, key) if (flag != null) { return } put(tx, new Flag(key)) var counter = get(tx, KEY:Counter(C)) counter.value++ put(tx, counter)}
ユニーク制約をユニークなキーで。
1 回目は確実に成功、キーを使いまわせば 2 回目は失敗
appengine ja night #4 - @ashigeru
352010/01/22
冪等な処理 (5)var key = KEY:Counter(C)/Flag(unique)atomic(tx) { var flag = get(tx, key) if (flag != null) { return } put(tx, new Flag(key)) var counter = get(tx, KEY:Counter(C)) counter.value++ put(tx, counter)}
それ以降の処理は一度だけしか行われない
appengine ja night #4 - @ashigeru
362010/01/22
冪等な処理 (6)var key = KEY:Counter(C)/Flag(unique)atomic(tx) { var flag = get(tx, key) if (flag != null) { return } put(tx, new Flag(key)) var counter = get(tx, KEY:Counter(C)) counter.value++ put(tx, counter)}
全体を合成して ACID に
appengine ja night #4 - @ashigeru
372010/01/22
冪等な処理 ( まとめ ) 冪等な処理
「 1 回分しか効果を出さない」パターン やりかた
「成功」済みかどうかについてユニーク制約トランザクションを合成
ユニーク制約で成功フラグを立てる OK なら続きの処理を行う
注意点ごみ (Flag) が残るが、これを消すのは一手間
appengine ja night #4 - @ashigeru
382010/01/22
パターン : Exactly Once
確実にぴったり 1 回成功する処理冪等な処理では 0 回の場合もある ( 最大 1
回 ) 例 :
カウンタの値を正確に更新する ( 恣意的 ) 注意点
「確実に失敗する」処理には適用できない
appengine ja night #4 - @ashigeru
392010/01/22
Exactly Once (1)
考え方1 度しか反映されない操作を執拗に繰り返すいつかは成功するはず間違えて 2 回以上成功しても効果は 1 回分
appengine ja night #4 - @ashigeru
402010/01/22
Exactly Once (2)var key = KEY:Counter(C)/Flag(unique)while (true) { try { atomic(tx) { var flag = get(tx, key) if (flag != null) { return } put(tx, new Flag(key)) var counter = get(tx, KEY:Counter(C)) counter.value++ put(tx, counter) } } catch (ignore) {}}
appengine ja night #4 - @ashigeru
412010/01/22
Exactly Once (3)var key = KEY:Counter(C)/Flag(unique)while (true) { try { atomic(tx) { var flag = get(tx, key) if (flag != null) { return } put(tx, new Flag(key)) var counter = get(tx, KEY:Counter(C)) counter.value++ put(tx, counter) } } catch (ignore) {}}
冪等な処理のパターン
appengine ja night #4 - @ashigeru
422010/01/22
Exactly Once (4)var key = KEY:Counter(C)/Flag(unique)while (true) { try { atomic(tx) { var flag = get(tx, key) if (flag != null) { return } put(tx, new Flag(key)) var counter = get(tx, KEY:Counter(C)) counter.value++ put(tx, counter) } } catch (ignore) {}}
冪等な処理を無限に繰り返す
30秒ルールがあるので確実とはいえない
appengine ja night #4 - @ashigeru
432010/01/22
Exactly Once (5)var key = KEY:Counter(C)/Flag(unique)enqueue(atomic(tx) { var flag = get(tx, key) if (flag != null) { return } put(tx, new Flag(key)) var counter = get(tx, KEY:Counter(C)) counter.value++ put(tx, counter)}) 代わりに Task Queue で
成功するまで繰り返し
appengine ja night #4 - @ashigeru
442010/01/22
Exactly Once ( まとめ ) Exactly Once
「確実にぴったり 1 回成功する」パターン ただし、いつ成功するかは不明
やりかた 冪等な処理を無限に繰り返す 一度成功したらあとは無駄なので打ち切る
App Engine の Task Queue を使える 成功するまで無限に繰り返す、という性質 30秒ルールがあるから while(true) は不適切
appengine ja night #4 - @ashigeru
452010/01/22
パターン : BASE Transaction
複数の EG にまたがるゆるいトランザクションACID ほど強い制約がない
例 :口座間の送金処理
注意点途中の状態が外側に見えるACID よりアプリケーションが複雑
appengine ja night #4 - @ashigeru
462010/01/22
BASE Transaction (1) 送金処理で本当にやりたいことは2つ
A の口座から X円引く B の口座に X円足す
「トランザクションの合成」は困難 A の口座と B の口座を同じ EG に配置? A から送金されうるすべての口座を同じ EG に?
トランザクションを分断すると危険 失敗例 :「 A の口座から X円引いたけど B に届かな
い」補償トランザクションすら失敗する可能性
appengine ja night #4 - @ashigeru
472010/01/22
BASE Transaction (2)
単純に考えてみるまず A の口座から 5000円引くそのあと「一度だけ」 B の口座に 5000円足
すatomic (tx1) { var a = get(tx1, KEY:Account(A)) a.amount -= 5000 put(tx1, a)} atomic (tx2) {
var b = get(tx2, KEY:Account(B)) b.amount += 5000 put(tx2, b)}
Exactly Once
appengine ja night #4 - @ashigeru
482010/01/22
BASE Transaction (3)var key = KEY:Account(B)/Flag(unique)atomic (tx1) { var a = get(tx1, KEY:Account(A)) a.amount -= 5000 put(tx1, a) enqueue(tx1, atomic(tx2) { var flag = get(tx2, key) if (flag != null) { return } put(tx2, new Flag(key)) var b = get(tx2, KEY:Account(B)) b.amount += 5000 put(tx2, b) })}
appengine ja night #4 - @ashigeru
492010/01/22
BASE Transaction (4)var key = KEY:Account(B)/Flag(unique)atomic (tx1) { var a = get(tx1, KEY:Account(A)) a.amount -= 5000 put(tx1, a) enqueue(tx1, atomic(tx2) { var flag = get(tx2, key) if (flag != null) { return } put(tx2, new Flag(key)) var b = get(tx2, KEY:Account(B)) b.amount += 5000 put(tx2, b) })}
Read-modify-write(A -= 5000)
appengine ja night #4 - @ashigeru
502010/01/22
BASE Transaction (5)var key = KEY:Account(B)/Flag(unique)atomic (tx1) { var a = get(tx1, KEY:Account(A)) a.amount -= 5000 put(tx1, a) enqueue(tx1, atomic(tx2) { var flag = get(tx2, key) if (flag != null) { return } put(tx2, new Flag(key)) var b = get(tx2, KEY:Account(B)) b.amount += 5000 put(tx2, b) })}
Read-modify-write(B += 5000)
appengine ja night #4 - @ashigeru
512010/01/22
BASE Transaction (6)var key = KEY:Account(B)/Flag(unique)atomic (tx1) { var a = get(tx1, KEY:Account(A)) a.amount -= 5000 put(tx1, a) enqueue(tx1, atomic(tx2) { var flag = get(tx2, key) if (flag != null) { return } put(tx2, new Flag(key)) var b = get(tx2, KEY:Account(B)) b.amount += 5000 put(tx2, b) })}
Exactly Once(B += 5000)
appengine ja night #4 - @ashigeru
522010/01/22
BASE Transaction (7)var key = KEY:Account(B)/Flag(unique)atomic (tx1) { var a = get(tx1, KEY:Account(A)) a.amount -= 5000 put(tx1, a) enqueue(tx1, atomic(tx2) { var flag = get(tx2, key) if (flag != null) { return } put(tx2, new Flag(key)) var b = get(tx2, KEY:Account(B)) b.amount += 5000 put(tx2, b) })}
全体を合成(A -= 5000, B += 5000)
appengine ja night #4 - @ashigeru
532010/01/22
BASE Transaction ( まとめ ) BASE Transaction
EG をまたいだゆるいトランザクション いつか確実に完了する、という性質
やりかた トランザクションを合成
一つ目の EG に対して操作を行う Exactly Once で二つ目の EG に対して操作を行う
注意点 操作が行われるまでタイムラグがある
Eventual Consistency: いずれ整合性が取れる二つ目の EG に対する操作は制約をかけられない
送金先に受け取り拒否されるとすごく困る
appengine ja night #4 - @ashigeru
542010/01/22
ここまでのまとめ (3) パターン : 冪等な処理
操作自体を最大一回だけ ( ユニーク ) にする = ユニーク制約 + トランザクションの合成
パターン : Exactly Once 最大一回だけ成功する処理を無限に繰り返す = 冪等な処理 + Task Queue
パターン : BASE Transaction 自分を変更後、相手の変更を確実に一度だけ適用 = read-modify-write + Exactly Once + 合成
appengine ja night #4 - @ashigeru
552010/01/22
おわりに App Engine のトランザクションは「パズ
ル」になりがち複雑な制約を考慮しつつ、時間内に解くルールも定石もあるので、積み重ねが大切
「仮説→検証」のサイクルが必要な段階みんなで情報共有パターンやアンチパターンを持ち寄ろう
appengine ja night #4 - @ashigeru
562010/01/22
参考文献 Programming Google App Engine
Oreilly & Associates Inc, 2009/11 リレーショナルデータベース入門
サイエンス社 , 2003/03 トランザクション処理
日経 BP 社 , 2001/10 BASE: An Acid Alternative
http://queue.acm.org/detail.cfm?id=1394128
appengine ja night #4 - @ashigeru
572010/01/22
Question + Discussion
実はあと数枚残ってる
Transaction Puzzlers Advanced Topics
appengine ja night #4@ashigeru
appengine ja night #4 - @ashigeru
592010/01/22
2種類の並行性制御 悲観的並行性制御
リソースを独占し、他人を待たせる
特徴他人に迷惑をかける 成功確率が高い 常にオーバーヘッド
楽観的並行性制御 リソースを独占できな
かったら、やり直し 特徴
他人に迷惑をかけない 失敗し続ける場合も 成功すれば速い
誰に迷惑が掛かるか、という点で異なる
appengine ja night #4 - @ashigeru
602010/01/22
その他のパターン Sharding
エンティティを複数の EG に分散 Long Running Transaction
リクエストをまたぐ長いトランザクション Software Global Transaction
ソフトウェアで 2PC プロトコルを実装 Software MVCC
ソフトウェアで MVCC を実装
appengine ja night #4 - @ashigeru
612010/01/22
トランザクション設計のポイント データの局所性を生かす 更新頻度を意識する 本当の制約を見極める
appengine ja night #4 - @ashigeru
622010/01/22
ポイント : データの局所性を生かす ユーザ情報はユーザごとに独立している
同一ユーザは同時にリクエストしないことが多い 多くのユーザを同時に捌いても並行稼動
「複数のユーザが共有するリソース」に注意 カウンタなどは典型例チケット予約なども該当する
ユースケース分析は重要 単一ユースケースの並列性から考える いざとなれば非正規化 +Eventual Consistency
appengine ja night #4 - @ashigeru
632010/01/22
ポイント : 更新頻度を意識する 更新頻度の観点でエンティティを分類
稀に更新 / 常に更新のプロパティを共存させない 更新しないプロパティはどこに混ぜてもいい
マスタ / トランザクションデータでは不十分 トランザクションデータも参照系と更新系に 参照系はトランザクション処理しなくてもよい場合
がある もちろん、更新に局所性があれば問題ない
ローカルトランザクションを衝突させない設計
appengine ja night #4 - @ashigeru
642010/01/22
ポイント : 本当の制約を見極める Strong Consistency はオーバースペック どの程度の不整合がどの順で許されるか考える
自動販売機の支払い / 商品受け取り ( 数秒 ) コンビニの支払い / 商品受け取り ( 数十秒 )ファーストフードの支払い /食事 ( 数分 ) レストランの支払い /食事 (▲ 数十分 )
同時にアプリケーション作成コストも考える 不整合の許容は制御することが増えすぎる提供先のビジネス特性を考慮し、合意を得る必要性 あらゆる観点で「必要十分」に