1 / 64

Transaction Puzzlers

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 のポータルサイトをコンセプトに

Download Presentation

Transaction Puzzlers

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Transaction Puzzlers appengine ja night #4 あらかわ (@ashigeru)

  2. 講演者について • 名前 • あらかわ (@ashigeru) • 所属 • 株式会社グルージェント 開発部 • 普段の業務 • 研究開発 (コンパイラ系) • 教育 (Computer Aided Education) • ブログ書き (Song of Cloud Blog) appengine ja night #4 - @ashigeru

  3. 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

  4. 今日の内容 • トランザクション処理の考え方 • トランザクション処理のパターン appengine ja night #4 - @ashigeru

  5. トランザクション処理の考え方 • リソースを一時的に独占できる技術 • 同時に変更して不整合が起こる、などを回避 • 今回は悲観的/楽観的をあまり気にしない • App Engineは楽観的並行性制御 • いずれも一時的にリソースを独占できる • 設計/実装時には考慮する必要がある appengine ja night #4 - @ashigeru

  6. App Engineのトランザクション • トランザクションはEntity Group (EG)単位 • 同一EG内のエンティティに対する操作はACID • 複数EGにまたがる操作は対応していない appengine ja night #4 - @ashigeru

  7. 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

  8. Entity Groupの特徴 • ポイント • トランザクションの範囲はエンティティ作成時に決まり、変更できない • EGを大きくするとトランザクションで独占するエンティティが多くなる • EGの設計が非常に重要に • 間違えると並列性が極端に低下する • うまくやればスケールアウトする appengine ja night #4 - @ashigeru

  9. ここまでのまとめ (1) • App EngineのトランザクションはEG単位 • EG内ではACIDトランザクション • EGをまたぐトランザクションは未サポート • EGの設計によっては並列性が落ちる • EGを大きくすると独占範囲が広がる • EGを分断すると整合性を保つのが困難 appengine ja night #4 - @ashigeru

  10. トランザクション処理のパターン • App Engineのトランザクションはやや特殊 • パターンで対応したほうがよさそう • 本日紹介するもの • Read-modify-write • トランザクションの合成 • ユニーク制約 • 冪(べき)等な処理 • Exactly Once • BASE Transaction appengine ja night #4 - @ashigeru

  11. 注意点 • プログラムの説明に擬似コードを多用 • 言語は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

  12. パターン: read-modify-write • エンティティのプロパティを変更する • 例: • カウンタの増加 • ショッピングカートに商品を追加 • 現在の値をもとに次の値が決まる • 読む、変更、書き戻す、の3ステップが必要 • 途中で割り込まれると不整合が起こる appengine ja night #4 - @ashigeru

  13. read-modify-write (1) • 考え方 • 読んでから書き戻すまでエンティティを独占 100 + 1 100 101 appengine ja night #4 - @ashigeru

  14. 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

  15. 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

  16. DSL: atomic (tx) { … } • 以後は下記のように省略 • トランザクションの開始と終了を簡略化 atomic(tx) { var counter = get(tx, KEY:Counter(C)) counter.value++ put(tx, counter)} appengine ja night #4 - @ashigeru

  17. パターン: トランザクションの合成 • 同じEGに対する複数のトランザクション処理を合成 • 例: • 2つのカウンタを同時に変更 (恣意的) • 非正規化した2つの情報を同時に更新 • 注意点 • 分断したトランザクションでは、途中で失敗した際に修復が大変 appengine ja night #4 - @ashigeru

  18. トランザクションの合成 (1) • 考え方 • 同じEGのトランザクションが2つあったら、一度に処理してしまう 15 16 30 31 appengine ja night #4 - @ashigeru

  19. トランザクションの合成 (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

  20. トランザクションの合成 (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

  21. トランザクションの合成 (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

  22. パターン: ユニーク制約 • 重複するエンティティの登録を防止する • 例: • 同じIDを持つユーザの登録を防ぐ • ダブルブッキングを防ぐ • 注意点 • データストアは制約機能を組み込んでいない • クエリはトランザクションに参加できない appengine ja night #4 - @ashigeru

  23. ユニーク制約 (1) • 考え方 • エンティティの入れ物ごと独占 • 入れ物が空なら追加するエンティティは一意 @hoge @hoge @hoge appengine ja night #4 - @ashigeru

  24. ユニーク制約 (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)} appengine ja night #4 - @ashigeru

  25. ユニーク制約 (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)} ユニーク制約をキーで表す(メールアドレス) appengine ja night #4 - @ashigeru

  26. ユニーク制約 (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)} そのエンティティがすでにあれば制約違反 appengine ja night #4 - @ashigeru

  27. ユニーク制約 (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)} 存在しなければユニークなので追加 appengine ja night #4 - @ashigeru

  28. ユニーク制約 (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までを独占 appengine ja night #4 - @ashigeru

  29. ここまでのまとめ (2) • read-modify-write • 最初に読んでから書き戻すまで独占 • トランザクションの合成 • 同一EGに対する操作をまとめる • ユニーク制約 • 入れ物を独占してからエンティティを作成 • すでにあったらユニークじゃないので失敗 appengine ja night #4 - @ashigeru

  30. パターン: 冪(べき)等な処理 • 1回分しか効果を出さない処理 • 2回以上成功しても、1回分しか反映しない • 例: • フォームの多重送信を防止 • お一人様一点限り。 • 注意点 • 英語でidempotentだけど覚えにくい appengine ja night #4 - @ashigeru

  31. 冪等な処理 (1) • 考え方 • 「処理がユニークに成功する」ということ • まだ成功していなかったら成功させる • 一度成功していたら何もしない 成功 成功 成功 結果 appengine ja night #4 - @ashigeru

  32. 冪等な処理 (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

  33. 冪等な処理 (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

  34. 冪等な処理 (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

  35. 冪等な処理 (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

  36. 冪等な処理 (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

  37. 冪等な処理 (まとめ) • 冪等な処理 • 「1回分しか効果を出さない」パターン • やりかた • 「成功」済みかどうかについてユニーク制約 • トランザクションを合成 • ユニーク制約で成功フラグを立てる • OKなら続きの処理を行う • 注意点 • ごみ(Flag)が残るが、これを消すのは一手間 appengine ja night #4 - @ashigeru

  38. パターン: Exactly Once • 確実にぴったり1回成功する処理 • 冪等な処理では0回の場合もある (最大1回) • 例: • カウンタの値を正確に更新する(恣意的) • 注意点 • 「確実に失敗する」処理には適用できない appengine ja night #4 - @ashigeru

  39. Exactly Once (1) • 考え方 • 1度しか反映されない操作を執拗に繰り返す • いつかは成功するはず • 間違えて2回以上成功しても効果は1回分 appengine ja night #4 - @ashigeru

  40. 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

  41. 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

  42. 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

  43. 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

  44. Exactly Once (まとめ) • Exactly Once • 「確実にぴったり1回成功する」パターン • ただし、いつ成功するかは不明 • やりかた • 冪等な処理を無限に繰り返す • 一度成功したらあとは無駄なので打ち切る • App EngineのTask Queueを使える • 成功するまで無限に繰り返す、という性質 • 30秒ルールがあるからwhile(true)は不適切 appengine ja night #4 - @ashigeru

  45. パターン: BASE Transaction • 複数のEGにまたがるゆるいトランザクション • ACIDほど強い制約がない • 例: • 口座間の送金処理 • 注意点 • 途中の状態が外側に見える • ACIDよりアプリケーションが複雑 appengine ja night #4 - @ashigeru

  46. BASE Transaction (1) • 送金処理で本当にやりたいことは2つ • Aの口座からX円引く • Bの口座にX円足す • 「トランザクションの合成」は困難 • Aの口座とBの口座を同じEGに配置? • Aから送金されうるすべての口座を同じEGに? • トランザクションを分断すると危険 • 失敗例:「Aの口座からX円引いたけどBに届かない」 • 補償トランザクションすら失敗する可能性 appengine ja night #4 - @ashigeru

  47. BASE Transaction (2) • 単純に考えてみる • まずAの口座から5000円引く • そのあと「一度だけ」Bの口座に5000円足す atomic (tx1) { var a = get(tx1, KEY:Account(A)) a.amount -= 5000 put(tx1, a)} Exactly Once atomic (tx2) { var b = get(tx2, KEY:Account(B)) b.amount += 5000 put(tx2, b)} appengine ja night #4 - @ashigeru

  48. 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

  49. 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

  50. 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

More Related