Transaction Puzzlers

appengine ja night #4
あらかわ (@ashigeru)
今日の内容
  トランザクション処理の考え方
  トランザクション処理のパターン




2010/01/22   appengine ja night #4 - @ashigeru   4
トランザクション処理の考え方
  リソースを一時的に独占できる技術
     同時に変更して不整合が起こる、などを回避


  今回は悲観的/楽観的をあまり気にしない
     App Engineは楽観的並⾏性制御
     いずれも一時的に...
App Engineのトランザクション
  トランザクションはEntity Group (EG)単位
     同一EG内のエンティティに対する操作はACID
     複数EGにまたがる操作は対応していない




2010/01/22   ...
Entity Groupの構成
  同じルートキーを持つエンティティ群
     データストア上で近くに配置される
  例
     Foo(A)                                EG: Foo(A)
     F...
Entity Groupの特徴
  ポイント
     トランザクションの範囲はエンティティ作成
     時に決まり、変更できない
     EGを⼤きくするとトランザクションで独占す
     るエンティティが多くなる
  EGの設計が非常...
ここまでのまとめ (1)
  App EngineのトランザクションはEG単位
     EG内ではACIDトランザクション
     EGをまたぐトランザクションは未サポート
  EGの設計によっては並列性が落ちる
     EGを⼤きくする...
トランザクション処理のパターン
  App Engineのトランザクションはやや特殊
     パターンで対応したほうがよさそう
  本日紹介するもの
     Read-modify-write
     トランザクションの合成
     ユ...
注意点
  プログラムの説明に擬似コードを多⽤
     言語はJavascriptライク
     APIはJavaのLow-Level APIライク
  ⾒慣れない言語要素
     キーリテラル – KEY:…
        KEY:F...
パターン: read-modify-write
  エンティティのプロパティを変更する
  例:
     カウンタの増加
     ショッピングカートに商品を追加


  現在の値をもとに次の値が決まる
     読む、変更、書き戻す、の3ス...
read-modify-write (1)
  考え方
     読んでから書き戻すまでエンティティを独占




                        100 + 1

             100               ...
read-modify-write (2)

var tx = beginTransaction()
try {
  var counter = get(tx, KEY:Counter(C))
  counter.value++
  put(t...
read-modify-write (3)

var tx = beginTransaction()
try {
  var counter = get(tx, KEY:Counter(C))
  counter.value++
  put(t...
DSL: atomic (tx) { … }
   以後は下記のように省略
      トランザクションの開始と終了を簡略化

atomic(tx) {
  var counter = get(tx, KEY:Counter(C))
  cou...
パターン: トランザクションの合成
  同じEGに対する複数のトランザクション
  処理を合成
  例:
     2つのカウンタを同時に変更 (恣意的)
     非正規化した2つの情報を同時に更新
  注意点
     分断したトランザクシ...
トランザクションの合成 (1)
  考え方
     同じEGのトランザクションが2つあったら、
     一度に処理してしまう



                              15                  16

...
トランザクションの合成 (2)

atomic(tx) {
  var a = get(tx, KEY:Eg(C)/Counter(A))
  a.value++
  put(tx, a)
  var b = get(tx, KEY:Eg(C)...
トランザクションの合成 (3)

atomic(tx) {
  var a = get(tx, KEY:Eg(C)/Counter(A))
  a.value++
  put(tx, a)
  var b = get(tx, KEY:Eg(C)...
トランザクションの合成 (4)

atomic(tx) {
  var a = get(tx, KEY:Eg(C)/Counter(A))
  a.value++
  put(tx, a)
  var b = get(tx, KEY:Eg(C)...
パターン: ユニーク制約
  重複するエンティティの登録を防止する
  例:
     同じIDを持つユーザの登録を防ぐ
     ダブルブッキングを防ぐ
  注意点
     データストアは制約機能を組み込んでいない
     クエリはトラン...
ユニーク制約 (1)
  考え方
     エンティティの入れ物ごと独占
     入れ物が空なら追加するエンティティは一意




         @hoge   @hoge              @hoge



2010/01/22...
ユニーク制約 (2)

var key = KEY:User(hoge@example.com)
atomic(tx) {
  var user = get(tx, key)
  if (user != null) {
    throw ne...
ユニーク制約 (3)

var key = KEY:User(hoge@example.com)
atomic(tx) {
  var user = get(tx, key)
                 ユニーク制約をキーで
  if (...
ユニーク制約 (4)

var key = KEY:User(hoge@example.com)
atomic(tx) {
  var user = get(tx, key)
  if (user != null) {
    throw ne...
ユニーク制約 (5)

var key = KEY:User(hoge@example.com)
atomic(tx) {
  var user = get(tx, key)
  if (user != null) { 存在しなければ
    ...
ユニーク制約 (6)

var key = KEY:User(hoge@example.com)
atomic(tx) {
  var user = get(tx, key)
  if (user != null) {
    throw ne...
ここまでのまとめ (2)
  read-modify-write
     最初に読んでから書き戻すまで独占
  トランザクションの合成
     同一EGに対する操作をまとめる
  ユニーク制約
     入れ物を独占してからエンティティを作...
パターン: 冪(べき)等な処理
  1回分しか効果を出さない処理
     2回以上成功しても、1回分しか反映しない
  例:
     フォームの多重送信を防止
     お一人様一点限り。
  注意点
     英語でidempotentだ...
冪等な処理 (1)
  考え方
     「処理がユニークに成功する」ということ
     まだ成功していなかったら成功させる
     一度成功していたら何もしない


     成功      成功                     ...
冪等な処理 (2)
var key = KEY:Counter(C)/Flag(unique)
atomic(tx) {
  var flag = get(tx, key)
  if (flag != null) {
    return
  ...
冪等な処理 (3)
var key = KEY:Counter(C)/Flag(unique)
atomic(tx) {
  var flag = get(tx, key)
      「ユニークなキー」を表す
  if (flag != nu...
冪等な処理 (4)
var key = KEY:Counter(C)/Flag(unique)
atomic(tx) {
  var flag = get(tx, key)
  if (flag != null) {
    return
  ...
冪等な処理 (5)
var key = KEY:Counter(C)/Flag(unique)
atomic(tx) {
  var flag = get(tx, key)
  if (flag != null) {
             ...
冪等な処理 (6)
var key = KEY:Counter(C)/Flag(unique)
atomic(tx) {
  var flag = get(tx, key)
  if (flag != null) {
    return
  ...
冪等な処理 (まとめ)
  冪等な処理
     「1回分しか効果を出さない」パターン
  やりかた
     「成功」済みかどうかについてユニーク制約
     トランザクションを合成
        そのキーでユニーク制約を確認
     ...
パターン: Exactly Once
  確実にぴったり1回成功する処理
     冪等な処理では0回の場合もある (最⼤1回)
  例:
     カウンタの値を正確に更新する(恣意的)
  注意点
     「確実に失敗する」処理には適⽤で...
Exactly Once (1)
  考え方
     1度しか反映されない操作を執拗に繰り返す
     いつかは成功するはず
     間違えて2回以上成功しても効果は1回分




2010/01/22   appengine ja ni...
Exactly Once (2)
var key = KEY:Counter(C)/Flag(unique)
while (true) {
  try {
    atomic(tx) {
      var flag = get(tx, ke...
Exactly Once (3)
var key = KEY:Counter(C)/Flag(unique)
while (true) {
  try {
    atomic(tx) {
      var flag = get(tx, ke...
Exactly Once (4)
var key = KEY:Counter(C)/Flag(unique)
while (true) {
  try {
    atomic(tx) {
                           ...
Exactly Once (5)
var key = KEY:Counter(C)/Flag(unique)
enqueue(atomic(tx) {
   var flag = get(tx, key)
   if (flag != null...
Exactly Once (まとめ)
  Exactly Once
     「確実にぴったり1回成功する」パターン
     ただし、いつ成功するかは不明
  やりかた
     冪等な処理を無限に繰り返す
     一度成功したらあとは無駄...
パターン: BASE Transaction
  複数のEGにまたがるゆるいトランザク
  ション
     ACIDほど強い制約がない
  例:
     口座間の送⾦処理
  注意点
     途中の状態が外側に⾒える
     ACIDよ...
BASE Transaction (1)
  送⾦処理で本当にやりたいことは2つ
     Aの口座からX円引く
     Bの口座にX円足す
  「トランザクションの合成」は困難
     Aの口座とBの口座を同じEGに配置?
     Aか...
BASE Transaction (2)
   単純に考えてみる
     まずAの口座から5000円引く
     そのあと「一度だけ」Bの口座に5000円足す
atomic (tx1) {
  var a = get(tx1, KEY:Ac...
BASE Transaction (3)
var key = KEY:Account(B)/Flag(unique)
atomic (tx1) {
  var a = get(tx1, KEY:Account(A))
  a.amount -=...
BASE Transaction (4)
var key = KEY:Account(B)/Flag(unique)
atomic (tx1) {
  var a = get(tx1, KEY:Account(A))
  a.amount -=...
BASE Transaction (5)
var key = KEY:Account(B)/Flag(unique)
atomic (tx1) {
  var a = get(tx1, KEY:Account(A))
  a.amount -=...
BASE Transaction (6)
var key = KEY:Account(B)/Flag(unique)
atomic (tx1) {
  var a = get(tx1, KEY:Account(A))
  a.amount -=...
BASE Transaction (7)
var key = KEY:Account(B)/Flag(unique)
atomic (tx1) {
  var a = get(tx1, KEY:Account(A))
  a.amount -=...
BASE Transaction (まとめ)
  BASE Transaction
     EGをまたいだゆるいトランザクション
     いつか確実に完了する、という性質
  やりかた
     トランザクションを合成
        一つ...
ここまでのまとめ (3)
  パターン: 冪等な処理
     操作自体を最⼤一回だけ(ユニーク)にする
     = ユニーク制約 + トランザクションの合成
  パターン: Exactly Once
     最⼤一回だけ成功する処理を無限...
おわりに
  App Engineのトランザクションは「パズ
  ル」になりがち
     複雑な制約を考慮しつつ、時間内に解く
     ルールも定⽯もあるので、積み重ねが⼤切
  「仮説→検証」のサイクルが必要な段階
     みんなで情報...
参考文献
  Programming Google App Engine
     Oreilly & Associates Inc, 2009/11
  リレーショナルデータベース入門
     サイエンス社, 2003/03
  トランザク...
Upcoming SlideShare
Loading in …5
×

appengine ja night #4 Transaction Puzzlers

3,465 views
3,371 views

Published on

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

Published in: Technology
0 Comments
9 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
3,465
On SlideShare
0
From Embeds
0
Number of Embeds
80
Actions
Shares
0
Downloads
55
Comments
0
Likes
9
Embeds 0
No embeds

No notes for slide

appengine ja night #4 Transaction Puzzlers

  1. 1. Transaction Puzzlers appengine ja night #4 あらかわ (@ashigeru)
  2. 2. 今日の内容 トランザクション処理の考え方 トランザクション処理のパターン 2010/01/22 appengine ja night #4 - @ashigeru 4
  3. 3. トランザクション処理の考え方 リソースを一時的に独占できる技術 同時に変更して不整合が起こる、などを回避 今回は悲観的/楽観的をあまり気にしない App Engineは楽観的並⾏性制御 いずれも一時的にリソースを独占できる 設計/実装時には考慮する必要がある 2010/01/22 appengine ja night #4 - @ashigeru 5
  4. 4. App Engineのトランザクション トランザクションはEntity Group (EG)単位 同一EG内のエンティティに対する操作はACID 複数EGにまたがる操作は対応していない 2010/01/22 appengine ja night #4 - @ashigeru 6
  5. 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. 6. Entity Groupの特徴 ポイント トランザクションの範囲はエンティティ作成 時に決まり、変更できない EGを⼤きくするとトランザクションで独占す るエンティティが多くなる EGの設計が非常に重要に 間違えると並列性が極端に低下する うまくやればスケールアウトする 2010/01/22 appengine ja night #4 - @ashigeru 8
  7. 7. ここまでのまとめ (1) App EngineのトランザクションはEG単位 EG内ではACIDトランザクション EGをまたぐトランザクションは未サポート EGの設計によっては並列性が落ちる EGを⼤きくすると独占範囲が広がる EGを分断すると整合性を保つのが困難 2010/01/22 appengine ja night #4 - @ashigeru 9
  8. 8. トランザクション処理のパターン App Engineのトランザクションはやや特殊 パターンで対応したほうがよさそう 本日紹介するもの Read-modify-write トランザクションの合成 ユニーク制約 冪(べき)等な処理 Exactly Once BASE Transaction 2010/01/22 appengine ja night #4 - @ashigeru 10
  9. 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. 10. パターン: read-modify-write エンティティのプロパティを変更する 例: カウンタの増加 ショッピングカートに商品を追加 現在の値をもとに次の値が決まる 読む、変更、書き戻す、の3ステップが必要 途中で割り込まれると不整合が起こる 2010/01/22 appengine ja night #4 - @ashigeru 12
  11. 11. read-modify-write (1) 考え方 読んでから書き戻すまでエンティティを独占 100 + 1 100 101 2010/01/22 appengine ja night #4 - @ashigeru 13
  12. 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. 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. 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. 15. パターン: トランザクションの合成 同じEGに対する複数のトランザクション 処理を合成 例: 2つのカウンタを同時に変更 (恣意的) 非正規化した2つの情報を同時に更新 注意点 分断したトランザクションでは、途中で失敗 した際に修復が⼤変 2010/01/22 appengine ja night #4 - @ashigeru 17
  16. 16. トランザクションの合成 (1) 考え方 同じEGのトランザクションが2つあったら、 一度に処理してしまう 15 16 30 31 2010/01/22 appengine ja night #4 - @ashigeru 18
  17. 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. 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. 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. 20. パターン: ユニーク制約 重複するエンティティの登録を防止する 例: 同じIDを持つユーザの登録を防ぐ ダブルブッキングを防ぐ 注意点 データストアは制約機能を組み込んでいない クエリはトランザクションに参加できない 2010/01/22 appengine ja night #4 - @ashigeru 22
  21. 21. ユニーク制約 (1) 考え方 エンティティの入れ物ごと独占 入れ物が空なら追加するエンティティは一意 @hoge @hoge @hoge 2010/01/22 appengine ja night #4 - @ashigeru 23
  22. 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. 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. 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. 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. 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. 27. ここまでのまとめ (2) read-modify-write 最初に読んでから書き戻すまで独占 トランザクションの合成 同一EGに対する操作をまとめる ユニーク制約 入れ物を独占してからエンティティを作成 すでにあったらユニークじゃないので失敗 2010/01/22 appengine ja night #4 - @ashigeru 29
  28. 28. パターン: 冪(べき)等な処理 1回分しか効果を出さない処理 2回以上成功しても、1回分しか反映しない 例: フォームの多重送信を防止 お一人様一点限り。 注意点 英語でidempotentだけど覚えにくい 2010/01/22 appengine ja night #4 - @ashigeru 30
  29. 29. 冪等な処理 (1) 考え方 「処理がユニークに成功する」ということ まだ成功していなかったら成功させる 一度成功していたら何もしない 成功 成功 成功 結果 2010/01/22 appengine ja night #4 - @ashigeru 31
  30. 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. 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. 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. 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. 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. 35. 冪等な処理 (まとめ) 冪等な処理 「1回分しか効果を出さない」パターン やりかた 「成功」済みかどうかについてユニーク制約 トランザクションを合成 そのキーでユニーク制約を確認 OKなら続きの処理を⾏う 注意点 ごみ(Flag)が残るが、これを消すのは一⼿間 2010/01/22 appengine ja night #4 - @ashigeru 37
  36. 36. パターン: Exactly Once 確実にぴったり1回成功する処理 冪等な処理では0回の場合もある (最⼤1回) 例: カウンタの値を正確に更新する(恣意的) 注意点 「確実に失敗する」処理には適⽤できない 2010/01/22 appengine ja night #4 - @ashigeru 38
  37. 37. Exactly Once (1) 考え方 1度しか反映されない操作を執拗に繰り返す いつかは成功するはず 間違えて2回以上成功しても効果は1回分 2010/01/22 appengine ja night #4 - @ashigeru 39
  38. 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. 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. 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. 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. 42. Exactly Once (まとめ) Exactly Once 「確実にぴったり1回成功する」パターン ただし、いつ成功するかは不明 やりかた 冪等な処理を無限に繰り返す 一度成功したらあとは無駄なので打ち切る App EngineのTask Queueを使える 成功するまで無限に繰り返す、という性質 30秒ルールがあるからwhile(true)は無理 2010/01/22 appengine ja night #4 - @ashigeru 44
  43. 43. パターン: BASE Transaction 複数のEGにまたがるゆるいトランザク ション ACIDほど強い制約がない 例: 口座間の送⾦処理 注意点 途中の状態が外側に⾒える ACIDよりアプリケーションが複雑 2010/01/22 appengine ja night #4 - @ashigeru 45
  44. 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. 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. 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. 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. 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. 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. 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. 51. BASE Transaction (まとめ) BASE Transaction EGをまたいだゆるいトランザクション いつか確実に完了する、という性質 やりかた トランザクションを合成 一つ目のEGに対して操作を⾏う Exactly Onceで二つ目のEGに対して操作を⾏う 注意点 操作が⾏われるまでタイムラグがある Eventual Consistency: いずれ整合性が取れる 二つ目のEGに対する操作は制約をかけられない 送⾦先に受け取り拒否されるとすごく困る 2010/01/22 appengine ja night #4 - @ashigeru 53
  52. 52. ここまでのまとめ (3) パターン: 冪等な処理 操作自体を最⼤一回だけ(ユニーク)にする = ユニーク制約 + トランザクションの合成 パターン: Exactly Once 最⼤一回だけ成功する処理を無限に繰り返す = 冪等な処理 + Task Queue パターン: BASE Transaction 自分を変更後、相⼿の変更を確実に一度だけ適⽤ = read-modify-write + 合成 + Exactly Once 2010/01/22 appengine ja night #4 - @ashigeru 54
  53. 53. おわりに App Engineのトランザクションは「パズ ル」になりがち 複雑な制約を考慮しつつ、時間内に解く ルールも定⽯もあるので、積み重ねが⼤切 「仮説→検証」のサイクルが必要な段階 みんなで情報共有 パターンやアンチパターンを持ち寄ろう 2010/01/22 appengine ja night #4 - @ashigeru 55
  54. 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

×