transaction puzzlers

64
Transaction Puzzlers appengine ja night #4 ああああ (@ashigeru)

Upload: courtney-bradshaw

Post on 30-Dec-2015

40 views

Category:

Documents


0 download

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 Presentation

TRANSCRIPT

Page 1: Transaction Puzzlers

Transaction Puzzlers

appengine ja night #4あらかわ (@ashigeru)

Page 2: Transaction Puzzlers

appengine ja night #4 - @ashigeru

22010/01/22

講演者について 名前

あらかわ (@ashigeru) 所属

株式会社グルージェント 開発部 普段の業務

研究開発 ( コンパイラ系 )教育 (Computer Aided Education)ブログ書き (Song of Cloud Blog)

Page 3: Transaction Puzzlers

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 グローバルトランザクション処理のパターン

Page 4: Transaction Puzzlers

appengine ja night #4 - @ashigeru

42010/01/22

今日の内容 トランザクション処理の考え方 トランザクション処理のパターン

Page 5: Transaction Puzzlers

appengine ja night #4 - @ashigeru

52010/01/22

トランザクション処理の考え方 リソースを一時的に独占できる技術

同時に変更して不整合が起こる、などを回避

今回は悲観的 / 楽観的をあまり気にしないApp Engine は楽観的並行性制御いずれも一時的にリソースを独占できる設計 / 実装時には考慮する必要がある

Page 6: Transaction Puzzlers

appengine ja night #4 - @ashigeru

62010/01/22

App Engine のトランザクション トランザクションは Entity Group (EG) 単位

同一 EG 内のエンティティに対する操作は ACID複数 EG にまたがる操作は対応していない

Page 7: Transaction Puzzlers

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)

Page 8: Transaction Puzzlers

appengine ja night #4 - @ashigeru

82010/01/22

Entity Group の特徴 ポイント

トランザクションの範囲はエンティティ作成時に決まり、変更できない

EG を大きくするとトランザクションで独占するエンティティが多くなる

EG の設計が非常に重要に間違えると並列性が極端に低下するうまくやればスケールアウトする

Page 9: Transaction Puzzlers

appengine ja night #4 - @ashigeru

92010/01/22

ここまでのまとめ (1)

App Engine のトランザクションは EG 単位EG 内では ACID トランザクションEG をまたぐトランザクションは未サポート

EG の設計によっては並列性が落ちるEG を大きくすると独占範囲が広がるEG を分断すると整合性を保つのが困難

Page 10: Transaction Puzzlers

appengine ja night #4 - @ashigeru

102010/01/22

トランザクション処理のパターン App Engine のトランザクションはやや特殊

パターンで対応したほうがよさそう 本日紹介するもの

Read-modify-write トランザクションの合成 ユニーク制約 冪 ( べき ) 等な処理 Exactly Once BASE Transaction

Page 11: Transaction Puzzlers

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)

Page 12: Transaction Puzzlers

appengine ja night #4 - @ashigeru

122010/01/22

パターン : read-modify-write

エンティティのプロパティを変更する 例 :

カウンタの増加ショッピングカートに商品を追加

現在の値をもとに次の値が決まる読む、変更、書き戻す、の 3 ステップが必要途中で割り込まれると不整合が起こる

Page 13: Transaction Puzzlers

appengine ja night #4 - @ashigeru

132010/01/22

read-modify-write (1)

考え方読んでから書き戻すまでエンティティを独占

100 101

100 + 1

Page 14: Transaction Puzzlers

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()}

Page 15: Transaction Puzzlers

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 に行う

Page 16: Transaction Puzzlers

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)}

Page 17: Transaction Puzzlers

appengine ja night #4 - @ashigeru

172010/01/22

パターン : トランザクションの合成 同じ EG に対する複数のトランザクション

処理を合成 例 :

2 つのカウンタを同時に変更 ( 恣意的 )非正規化した 2 つの情報を同時に更新

注意点分断したトランザクションでは、途中で失敗

した際に修復が大変

Page 18: Transaction Puzzlers

appengine ja night #4 - @ashigeru

182010/01/22

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

考え方同じ EG のトランザクションが 2 つあったら、

一度に処理してしまう

15 16

30 31

Page 19: Transaction Puzzlers

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)}

Page 20: Transaction Puzzlers

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 のエンティテ

ィに対する操作

Page 21: Transaction Puzzlers

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に

Page 22: Transaction Puzzlers

appengine ja night #4 - @ashigeru

222010/01/22

パターン : ユニーク制約 重複するエンティティの登録を防止する 例 :

同じ ID を持つユーザの登録を防ぐダブルブッキングを防ぐ

注意点データストアは制約機能を組み込んでいないクエリはトランザクションに参加できない

Page 23: Transaction Puzzlers

appengine ja night #4 - @ashigeru

232010/01/22

ユニーク制約 (1)

考え方エンティティの入れ物ごと独占入れ物が空なら追加するエンティティは一意

@hoge @hoge @hoge

Page 24: Transaction Puzzlers

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)}

Page 25: Transaction Puzzlers

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)}

ユニーク制約をキーで表す ( メールアドレス )

Page 26: Transaction Puzzlers

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)}

そのエンティティがすでにあれば制約違反

Page 27: Transaction Puzzlers

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)}

存在しなければユニークなので追加

Page 28: Transaction Puzzlers

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 までを独占

Page 29: Transaction Puzzlers

appengine ja night #4 - @ashigeru

292010/01/22

ここまでのまとめ (2)

read-modify-write最初に読んでから書き戻すまで独占

トランザクションの合成同一 EG に対する操作をまとめる

ユニーク制約入れ物を独占してからエンティティを作成すでにあったらユニークじゃないので失敗

Page 30: Transaction Puzzlers

appengine ja night #4 - @ashigeru

302010/01/22

パターン : 冪 ( べき ) 等な処理 1 回分しか効果を出さない処理

2 回以上成功しても、 1 回分しか反映しない 例 :

フォームの多重送信を防止お一人様一点限り。

注意点英語で idempotentだけど覚えにくい

Page 31: Transaction Puzzlers

appengine ja night #4 - @ashigeru

312010/01/22

冪等な処理 (1)

考え方「処理がユニークに成功する」ということまだ成功していなかったら成功させる一度成功していたら何もしない

成功 成功 成功

結果

Page 32: Transaction Puzzlers

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)}

Page 33: Transaction Puzzlers

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()

Page 34: Transaction Puzzlers

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 回目は失敗

Page 35: Transaction Puzzlers

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)}

それ以降の処理は一度だけしか行われない

Page 36: Transaction Puzzlers

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 に

Page 37: Transaction Puzzlers

appengine ja night #4 - @ashigeru

372010/01/22

冪等な処理 ( まとめ ) 冪等な処理

「 1 回分しか効果を出さない」パターン やりかた

「成功」済みかどうかについてユニーク制約トランザクションを合成

ユニーク制約で成功フラグを立てる OK なら続きの処理を行う

注意点ごみ (Flag) が残るが、これを消すのは一手間

Page 38: Transaction Puzzlers

appengine ja night #4 - @ashigeru

382010/01/22

パターン : Exactly Once

確実にぴったり 1 回成功する処理冪等な処理では 0 回の場合もある ( 最大 1

回 ) 例 :

カウンタの値を正確に更新する ( 恣意的 ) 注意点

「確実に失敗する」処理には適用できない

Page 39: Transaction Puzzlers

appengine ja night #4 - @ashigeru

392010/01/22

Exactly Once (1)

考え方1 度しか反映されない操作を執拗に繰り返すいつかは成功するはず間違えて 2 回以上成功しても効果は 1 回分

Page 40: Transaction Puzzlers

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) {}}

Page 41: Transaction Puzzlers

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) {}}

冪等な処理のパターン

Page 42: Transaction Puzzlers

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秒ルールがあるので確実とはいえない

Page 43: Transaction Puzzlers

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 で

成功するまで繰り返し

Page 44: Transaction Puzzlers

appengine ja night #4 - @ashigeru

442010/01/22

Exactly Once ( まとめ ) Exactly Once

「確実にぴったり 1 回成功する」パターン ただし、いつ成功するかは不明

やりかた 冪等な処理を無限に繰り返す 一度成功したらあとは無駄なので打ち切る

App Engine の Task Queue を使える 成功するまで無限に繰り返す、という性質 30秒ルールがあるから while(true) は不適切

Page 45: Transaction Puzzlers

appengine ja night #4 - @ashigeru

452010/01/22

パターン : BASE Transaction

複数の EG にまたがるゆるいトランザクションACID ほど強い制約がない

例 :口座間の送金処理

注意点途中の状態が外側に見えるACID よりアプリケーションが複雑

Page 46: Transaction Puzzlers

appengine ja night #4 - @ashigeru

462010/01/22

BASE Transaction (1) 送金処理で本当にやりたいことは2つ

A の口座から X円引く B の口座に X円足す

「トランザクションの合成」は困難 A の口座と B の口座を同じ EG に配置? A から送金されうるすべての口座を同じ EG に?

トランザクションを分断すると危険 失敗例 :「 A の口座から X円引いたけど B に届かな

い」補償トランザクションすら失敗する可能性

Page 47: Transaction Puzzlers

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

Page 48: Transaction Puzzlers

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) })}

Page 49: Transaction Puzzlers

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)

Page 50: Transaction Puzzlers

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)

Page 51: Transaction Puzzlers

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)

Page 52: Transaction Puzzlers

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)

Page 53: Transaction Puzzlers

appengine ja night #4 - @ashigeru

532010/01/22

BASE Transaction ( まとめ ) BASE Transaction

EG をまたいだゆるいトランザクション いつか確実に完了する、という性質

やりかた トランザクションを合成

一つ目の EG に対して操作を行う Exactly Once で二つ目の EG に対して操作を行う

注意点 操作が行われるまでタイムラグがある

Eventual Consistency: いずれ整合性が取れる二つ目の EG に対する操作は制約をかけられない

送金先に受け取り拒否されるとすごく困る

Page 54: Transaction Puzzlers

appengine ja night #4 - @ashigeru

542010/01/22

ここまでのまとめ (3) パターン : 冪等な処理

操作自体を最大一回だけ ( ユニーク ) にする = ユニーク制約 + トランザクションの合成

パターン : Exactly Once 最大一回だけ成功する処理を無限に繰り返す = 冪等な処理 + Task Queue

パターン : BASE Transaction 自分を変更後、相手の変更を確実に一度だけ適用 = read-modify-write + Exactly Once + 合成

Page 55: Transaction Puzzlers

appengine ja night #4 - @ashigeru

552010/01/22

おわりに App Engine のトランザクションは「パズ

ル」になりがち複雑な制約を考慮しつつ、時間内に解くルールも定石もあるので、積み重ねが大切

「仮説→検証」のサイクルが必要な段階みんなで情報共有パターンやアンチパターンを持ち寄ろう

Page 56: Transaction Puzzlers

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

Page 57: Transaction Puzzlers

appengine ja night #4 - @ashigeru

572010/01/22

Question + Discussion

実はあと数枚残ってる

Page 58: Transaction Puzzlers

Transaction Puzzlers Advanced Topics

appengine ja night #4@ashigeru

Page 59: Transaction Puzzlers

appengine ja night #4 - @ashigeru

592010/01/22

2種類の並行性制御 悲観的並行性制御

リソースを独占し、他人を待たせる

特徴他人に迷惑をかける 成功確率が高い 常にオーバーヘッド

楽観的並行性制御 リソースを独占できな

かったら、やり直し 特徴

他人に迷惑をかけない 失敗し続ける場合も 成功すれば速い

誰に迷惑が掛かるか、という点で異なる

Page 60: Transaction Puzzlers

appengine ja night #4 - @ashigeru

602010/01/22

その他のパターン Sharding

エンティティを複数の EG に分散 Long Running Transaction

リクエストをまたぐ長いトランザクション Software Global Transaction

ソフトウェアで 2PC プロトコルを実装 Software MVCC

ソフトウェアで MVCC を実装

Page 61: Transaction Puzzlers

appengine ja night #4 - @ashigeru

612010/01/22

トランザクション設計のポイント データの局所性を生かす 更新頻度を意識する 本当の制約を見極める

Page 62: Transaction Puzzlers

appengine ja night #4 - @ashigeru

622010/01/22

ポイント : データの局所性を生かす ユーザ情報はユーザごとに独立している

同一ユーザは同時にリクエストしないことが多い 多くのユーザを同時に捌いても並行稼動

「複数のユーザが共有するリソース」に注意 カウンタなどは典型例チケット予約なども該当する

ユースケース分析は重要 単一ユースケースの並列性から考える いざとなれば非正規化 +Eventual Consistency

Page 63: Transaction Puzzlers

appengine ja night #4 - @ashigeru

632010/01/22

ポイント : 更新頻度を意識する 更新頻度の観点でエンティティを分類

稀に更新 / 常に更新のプロパティを共存させない 更新しないプロパティはどこに混ぜてもいい

マスタ / トランザクションデータでは不十分 トランザクションデータも参照系と更新系に 参照系はトランザクション処理しなくてもよい場合

がある もちろん、更新に局所性があれば問題ない

ローカルトランザクションを衝突させない設計

Page 64: Transaction Puzzlers

appengine ja night #4 - @ashigeru

642010/01/22

ポイント : 本当の制約を見極める Strong Consistency はオーバースペック どの程度の不整合がどの順で許されるか考える

自動販売機の支払い / 商品受け取り ( 数秒 ) コンビニの支払い / 商品受け取り ( 数十秒 )ファーストフードの支払い /食事 ( 数分 ) レストランの支払い /食事 (▲ 数十分 )

同時にアプリケーション作成コストも考える 不整合の許容は制御することが増えすぎる提供先のビジネス特性を考慮し、合意を得る必要性 あらゆる観点で「必要十分」に