appengine ja night #4 Transaction Puzzlers
Upcoming SlideShare
Loading in...5
×
 

Like this? Share it with your network

Share

appengine ja night #4 Transaction Puzzlers

on

  • 4,197 views

#ajn4 で発表したスライド。

#ajn4 で発表したスライド。
app engineでのトランザクションでビルディングブロックとなることを期待したパターン集。

Statistics

Views

Total Views
4,197
Views on SlideShare
4,140
Embed Views
57

Actions

Likes
9
Downloads
54
Comments
0

3 Embeds 57

http://d.hatena.ne.jp 51
http://www.slideshare.net 5
http://twitter.com 1

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

CC Attribution License

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

appengine ja night #4 Transaction Puzzlers Presentation Transcript

  • 1. Transaction Puzzlers appengine ja night #4 あらかわ (@ashigeru)
  • 2. 今日の内容 トランザクション処理の考え方 トランザクション処理のパターン 2010/01/22 appengine ja night #4 - @ashigeru 4
  • 3. トランザクション処理の考え方 リソースを一時的に独占できる技術 同時に変更して不整合が起こる、などを回避 今回は悲観的/楽観的をあまり気にしない App Engineは楽観的並⾏性制御 いずれも一時的にリソースを独占できる 設計/実装時には考慮する必要がある 2010/01/22 appengine ja night #4 - @ashigeru 5
  • 4. App Engineのトランザクション トランザクションはEntity Group (EG)単位 同一EG内のエンティティに対する操作はACID 複数EGにまたがる操作は対応していない 2010/01/22 appengine ja night #4 - @ashigeru 6
  • 5. Entity Groupの構成 同じルートキーを持つエンティティ群 データストア上で近くに配置される 例 Foo(A) EG: Foo(A) Foo(A)/Hoge(B) Foo(B) EG: Foo(B) Bar(A)/Foo(A) EG: Bar(A) Bar(A)/Foo(B)/Hoge(D) 2010/01/22 appengine ja night #4 - @ashigeru 7
  • 6. Entity Groupの特徴 ポイント トランザクションの範囲はエンティティ作成 時に決まり、変更できない EGを⼤きくするとトランザクションで独占す るエンティティが多くなる EGの設計が非常に重要に 間違えると並列性が極端に低下する うまくやればスケールアウトする 2010/01/22 appengine ja night #4 - @ashigeru 8
  • 7. ここまでのまとめ (1) App EngineのトランザクションはEG単位 EG内ではACIDトランザクション EGをまたぐトランザクションは未サポート EGの設計によっては並列性が落ちる EGを⼤きくすると独占範囲が広がる EGを分断すると整合性を保つのが困難 2010/01/22 appengine ja night #4 - @ashigeru 9
  • 8. トランザクション処理のパターン App Engineのトランザクションはやや特殊 パターンで対応したほうがよさそう 本日紹介するもの Read-modify-write トランザクションの合成 ユニーク制約 冪(べき)等な処理 Exactly Once BASE Transaction 2010/01/22 appengine ja night #4 - @ashigeru 10
  • 9. 注意点 プログラムの説明に擬似コードを多⽤ 言語は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) 2010/01/22 appengine ja night #4 - @ashigeru 11
  • 10. パターン: read-modify-write エンティティのプロパティを変更する 例: カウンタの増加 ショッピングカートに商品を追加 現在の値をもとに次の値が決まる 読む、変更、書き戻す、の3ステップが必要 途中で割り込まれると不整合が起こる 2010/01/22 appengine ja night #4 - @ashigeru 12
  • 11. read-modify-write (1) 考え方 読んでから書き戻すまでエンティティを独占 100 + 1 100 101 2010/01/22 appengine ja night #4 - @ashigeru 13
  • 12. 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() } 2010/01/22 appengine ja night #4 - @ashigeru 14
  • 13. read-modify-write (3) var tx = beginTransaction() try { var counter = get(tx, KEY:Counter(C)) counter.value++ put(tx, counter) tx.commit() 読んでから書き戻す } までをACIDに⾏う finally { if (tx.isActive()) tx.rollback() } 2010/01/22 appengine ja night #4 - @ashigeru 15
  • 14. DSL: atomic (tx) { … } 以後は下記のように省略 トランザクションの開始と終了を簡略化 atomic(tx) { var counter = get(tx, KEY:Counter(C)) counter.value++ put(tx, counter) } 2010/01/22 appengine ja night #4 - @ashigeru 16
  • 15. パターン: トランザクションの合成 同じEGに対する複数のトランザクション 処理を合成 例: 2つのカウンタを同時に変更 (恣意的) 非正規化した2つの情報を同時に更新 注意点 分断したトランザクションでは、途中で失敗 した際に修復が⼤変 2010/01/22 appengine ja night #4 - @ashigeru 17
  • 16. トランザクションの合成 (1) 考え方 同じEGのトランザクションが2つあったら、 一度に処理してしまう 15 16 30 31 2010/01/22 appengine ja night #4 - @ashigeru 18
  • 17. トランザクションの合成 (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) } 2010/01/22 appengine ja night #4 - @ashigeru 19
  • 18. トランザクションの合成 (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のエンティティ に対する操作 2010/01/22 appengine ja night #4 - @ashigeru 20
  • 19. トランザクションの合成 (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に 2010/01/22 appengine ja night #4 - @ashigeru 21
  • 20. パターン: ユニーク制約 重複するエンティティの登録を防止する 例: 同じIDを持つユーザの登録を防ぐ ダブルブッキングを防ぐ 注意点 データストアは制約機能を組み込んでいない クエリはトランザクションに参加できない 2010/01/22 appengine ja night #4 - @ashigeru 22
  • 21. ユニーク制約 (1) 考え方 エンティティの入れ物ごと独占 入れ物が空なら追加するエンティティは一意 @hoge @hoge @hoge 2010/01/22 appengine ja night #4 - @ashigeru 23
  • 22. ユニーク制約 (2) var key = KEY:User(hoge@example.com) atomic(tx) { var user = get(tx, key) if (user != null) { throw new NotUniqueException() } user = new User(key, ...) put(tx, user) } 2010/01/22 appengine ja night #4 - @ashigeru 24
  • 23. ユニーク制約 (3) var key = KEY:User(hoge@example.com) atomic(tx) { var user = get(tx, key) ユニーク制約をキーで if (user != null) { 表す(メールアドレス) throw new NotUniqueException() } user = new User(key, ...) put(tx, user) } 2010/01/22 appengine ja night #4 - @ashigeru 25
  • 24. ユニーク制約 (4) var key = KEY:User(hoge@example.com) atomic(tx) { var user = get(tx, key) if (user != null) { throw new NotUniqueException() } user = new User(key, ...) put(tx, user) そのエンティティが } すでにあれば制約違反 2010/01/22 appengine ja night #4 - @ashigeru 26
  • 25. ユニーク制約 (5) var key = KEY:User(hoge@example.com) atomic(tx) { var user = get(tx, key) if (user != null) { 存在しなければ ユニークなので追加 throw new NotUniqueException() } user = new User(key, ...) put(tx, user) } 2010/01/22 appengine ja night #4 - @ashigeru 27
  • 26. ユニーク制約 (6) var key = KEY:User(hoge@example.com) atomic(tx) { var user = get(tx, key) if (user != null) { throw new NotUniqueException() } user = new User(key, ...) put(tx, user) } getからputまでを独占 2010/01/22 appengine ja night #4 - @ashigeru 28
  • 27. ここまでのまとめ (2) read-modify-write 最初に読んでから書き戻すまで独占 トランザクションの合成 同一EGに対する操作をまとめる ユニーク制約 入れ物を独占してからエンティティを作成 すでにあったらユニークじゃないので失敗 2010/01/22 appengine ja night #4 - @ashigeru 29
  • 28. パターン: 冪(べき)等な処理 1回分しか効果を出さない処理 2回以上成功しても、1回分しか反映しない 例: フォームの多重送信を防止 お一人様一点限り。 注意点 英語でidempotentだけど覚えにくい 2010/01/22 appengine ja night #4 - @ashigeru 30
  • 29. 冪等な処理 (1) 考え方 「処理がユニークに成功する」ということ まだ成功していなかったら成功させる 一度成功していたら何もしない 成功 成功 成功 結果 2010/01/22 appengine ja night #4 - @ashigeru 31
  • 30. 冪等な処理 (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) } 2010/01/22 appengine ja night #4 - @ashigeru 32
  • 31. 冪等な処理 (3) var key = KEY:Counter(C)/Flag(unique) atomic(tx) { var flag = get(tx, key) 「ユニークなキー」を表す if (flag != null) { → db.allocate_ids() return } → DatastoreService.allocateIds() put(tx, new Flag(key)) var counter = get(tx, KEY:Counter(C)) counter.value++ put(tx, counter) } 2010/01/22 appengine ja night #4 - @ashigeru 33
  • 32. 冪等な処理 (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回目は失敗 2010/01/22 appengine ja night #4 - @ashigeru 34
  • 33. 冪等な処理 (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) } 2010/01/22 appengine ja night #4 - @ashigeru 35
  • 34. 冪等な処理 (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に } 2010/01/22 appengine ja night #4 - @ashigeru 36
  • 35. 冪等な処理 (まとめ) 冪等な処理 「1回分しか効果を出さない」パターン やりかた 「成功」済みかどうかについてユニーク制約 トランザクションを合成 そのキーでユニーク制約を確認 OKなら続きの処理を⾏う 注意点 ごみ(Flag)が残るが、これを消すのは一⼿間 2010/01/22 appengine ja night #4 - @ashigeru 37
  • 36. パターン: Exactly Once 確実にぴったり1回成功する処理 冪等な処理では0回の場合もある (最⼤1回) 例: カウンタの値を正確に更新する(恣意的) 注意点 「確実に失敗する」処理には適⽤できない 2010/01/22 appengine ja night #4 - @ashigeru 38
  • 37. Exactly Once (1) 考え方 1度しか反映されない操作を執拗に繰り返す いつかは成功するはず 間違えて2回以上成功しても効果は1回分 2010/01/22 appengine ja night #4 - @ashigeru 39
  • 38. 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) {} } 2010/01/22 appengine ja night #4 - @ashigeru 40
  • 39. 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) {} } 2010/01/22 appengine ja night #4 - @ashigeru 41
  • 40. 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) } 30秒ルールがあるので } catch (ignore) {} 確実とはいえない } 2010/01/22 appengine ja night #4 - @ashigeru 42
  • 41. 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で 成功するまで繰り返し 2010/01/22 appengine ja night #4 - @ashigeru 43
  • 42. Exactly Once (まとめ) Exactly Once 「確実にぴったり1回成功する」パターン ただし、いつ成功するかは不明 やりかた 冪等な処理を無限に繰り返す 一度成功したらあとは無駄なので打ち切る App EngineのTask Queueを使える 成功するまで無限に繰り返す、という性質 30秒ルールがあるからwhile(true)は無理 2010/01/22 appengine ja night #4 - @ashigeru 44
  • 43. パターン: BASE Transaction 複数のEGにまたがるゆるいトランザク ション ACIDほど強い制約がない 例: 口座間の送⾦処理 注意点 途中の状態が外側に⾒える ACIDよりアプリケーションが複雑 2010/01/22 appengine ja night #4 - @ashigeru 45
  • 44. BASE Transaction (1) 送⾦処理で本当にやりたいことは2つ Aの口座からX円引く Bの口座にX円足す 「トランザクションの合成」は困難 Aの口座とBの口座を同じEGに配置? Aから送⾦されうるすべての口座を同じEGに? トランザクションを分断すると危険 失敗例:「Aの口座からX円引いたけどBに届かない」 補償トランザクションすら失敗する可能性 2010/01/22 appengine ja night #4 - @ashigeru 46
  • 45. BASE Transaction (2) 単純に考えてみる まずAの口座から5000円引く そのあと「一度だけ」Bの口座に5000円足す atomic (tx1) { var a = get(tx1, KEY:Account(A)) Exactly Once a.amount -= 5000 put(tx1, a) } atomic (tx2) { var b = get(tx2, KEY:Account(B)) b.amount += 5000 put(tx2, b) } 2010/01/22 appengine ja night #4 - @ashigeru 47
  • 46. 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) }) } 2010/01/22 appengine ja night #4 - @ashigeru 48
  • 47. 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) Read-modify-write if (flag != null) { (A -= 5000) return } put(tx2, new Flag(key)) var b = get(tx2, KEY:Account(B)) b.amount += 5000 put(tx2, b) }) } 2010/01/22 appengine ja night #4 - @ashigeru 49
  • 48. 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) Read-modify-write if (flag != null) { (B += 5000) return } put(tx2, new Flag(key)) var b = get(tx2, KEY:Account(B)) b.amount += 5000 put(tx2, b) }) } 2010/01/22 appengine ja night #4 - @ashigeru 50
  • 49. 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) Exactly Once if (flag != null) { (B += 5000) return } put(tx2, new Flag(key)) var b = get(tx2, KEY:Account(B)) b.amount += 5000 put(tx2, b) }) } 2010/01/22 appengine ja night #4 - @ashigeru 51
  • 50. 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) { (A -= 5000, B += 5000) return } put(tx2, new Flag(key)) var b = get(tx2, KEY:Account(B)) b.amount += 5000 put(tx2, b) }) } 2010/01/22 appengine ja night #4 - @ashigeru 52
  • 51. BASE Transaction (まとめ) BASE Transaction EGをまたいだゆるいトランザクション いつか確実に完了する、という性質 やりかた トランザクションを合成 一つ目のEGに対して操作を⾏う Exactly Onceで二つ目のEGに対して操作を⾏う 注意点 操作が⾏われるまでタイムラグがある Eventual Consistency: いずれ整合性が取れる 二つ目のEGに対する操作は制約をかけられない 送⾦先に受け取り拒否されるとすごく困る 2010/01/22 appengine ja night #4 - @ashigeru 53
  • 52. ここまでのまとめ (3) パターン: 冪等な処理 操作自体を最⼤一回だけ(ユニーク)にする = ユニーク制約 + トランザクションの合成 パターン: Exactly Once 最⼤一回だけ成功する処理を無限に繰り返す = 冪等な処理 + Task Queue パターン: BASE Transaction 自分を変更後、相⼿の変更を確実に一度だけ適⽤ = read-modify-write + 合成 + Exactly Once 2010/01/22 appengine ja night #4 - @ashigeru 54
  • 53. おわりに App Engineのトランザクションは「パズ ル」になりがち 複雑な制約を考慮しつつ、時間内に解く ルールも定⽯もあるので、積み重ねが⼤切 「仮説→検証」のサイクルが必要な段階 みんなで情報共有 パターンやアンチパターンを持ち寄ろう 2010/01/22 appengine ja night #4 - @ashigeru 55
  • 54. 参考文献 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 2010/01/22 appengine ja night #4 - @ashigeru 56