appengine java night #3
Upcoming SlideShare
Loading in...5
×
 

Like this? Share it with your network

Share

appengine java night #3

on

  • 3,656 views

実際に作ってわかったApp Engineの困ったところ

実際に作ってわかったApp Engineの困ったところ

Statistics

Views

Total Views
3,656
Views on SlideShare
3,161
Embed Views
495

Actions

Likes
4
Downloads
19
Comments
0

6 Embeds 495

http://d.hatena.ne.jp 475
http://www.slideshare.net 14
http://74.125.153.132 2
http://webcache.googleusercontent.com 2
http://k.hatena.ne.jp 1
http://bluerabbit.hatenablog.com 1

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

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 java night #3 Presentation Transcript

  • 1. appengine java night #3 実際に作ってわかった App Engine の困ったところ source: http://www.flickr.com/photos/katemonkey/122489910/
  • 2. 自己紹介 はてなID:bluerabbit twitterID:bluerabbit777jp
  • 3. 内容 「雨の日め〜る」というサービスを作りま した。 実際にApp Engineで作るにあたって困っ たことをどのように回避したかをお話しま す。
  • 4. 雨の日め〜るとは 会社帰りに・・・ 「あれっ。今日って雨だったの?」と 傘を忘れた経験がある。 そんなあなたのためのサービスです。   傘忘れを防止する為に作りました。
  • 5. 仕組み 「雨の日め〜る」は天気が雨の場合に 天気予報メールを送信する。 実現するための機能は下記3つ。 天気予報を取得する 天気予報をメールする ユーザ登録
  • 6. 天気予報を取得する 天気予報をUrlFetch APIを用いて取得 天気予報は朝(6:00)に取得 利用者のお住まいの地域は142用意 142の地域の天気予報をDatastoreに保存 ユーザの指定した時間にメールする これらをバッチ処理で行う。
  • 7. しかし
  • 8. 立ちはだかる App Eengineの制約
  • 9. 制約その1 1 リクエストは30秒以内に処理すべし HardDeadlineExceededError http://code.google.com/intl/ja/appengine/docs/whatisgoogleappengine.html
  • 10. App Engineではバッチ 処理も30秒以内 天気予報を取得するために次のようにした。 Cronで1分毎に実行 各地域の天気予報を取得 取得済みの地域はスキップ 天気予報をDatastoreへ保存 ※当時はTaskQueueがリリースされていなかった。
  • 11. 処理イメージ
  • 12. 処理時間30分orz...
  • 13. TaskQueueで高速化 TaskQueueを使って並列処理 1地域毎に1タスク、142のTaskで実行する Cronは指定時間にQueueを追加するだけ for (Location location : Location.getAll()) {    QueueFactory.getDefaultQueue(). add(TaskOptions.Builder .url("/crawler") .param("locationID",location.getId())); }
  • 14. 処理イメージ
  • 15. 処理時間3分
  • 16. App Engineでの バッチ処理 バッチ処理でも30秒以内に処理 結果的にTaskQueueを使う必要あり キューを使うことで非同期、並列処理となる 非同期、並列処理の知識と経験が必要 既存プログラムをApp Engineに移行する場合 にバッチ処理は処理方式を変更する必要に迫 られる
  • 17. こんなバッチの場合は どうする? 1Taskが30秒以内に終わらない バッチが終わったことを知りたい
  • 18. 1Taskが30秒以内に 終わらない 機能を分割する。1機能を30秒以内に分割 当該アプリの例だと1Taskの機能は下記 Fetch Parse Insert 機能別にTaskを実行するようにする
  • 19. TaskQueueを チェインする Fetch処理の最後にParseのキューを追加 Parse処理の最後にInsertのキューを追加 Insert処理を実行してDatastoreに登録する
  • 20. 処理イメージ
  • 21. バッチが終わったことを 知りたい 処理件数で把握する 複数リクエスト(TaskQueue)間で連番を作 成する 連番の処理件数がキューを追加した件数 と同じだったらバッチ終了と判断する
  • 22. カウンター Sharding Counter 書き込みが集中しないように複数のエン ティティに分散して書き込みし集計する Memcache Counter Memcacheを用いた簡易カウンター  Memcache Counterを紹介
  • 23. Memcache Counter MemcacheのLow Level API MemcacheService#incrementはアトミックに 実行される TaskQueueなどで複数のスレッドが同時にア クセスしても連番が補償される MemcacheServiceのjava doc
  • 24. APIの使用例 MemcacheService s = MemcacheServiceFactory.getMemcacheService(); if (!s.contains("MemcacheCounter")) { s.put("MemcacheCounter", 1); // 初期化は1 } else { // 2回目以降は値に+1する s.increment("MemcacheCounter", 1); } // 実行のたびに1,2,3,4,5になる System.out.println(s.get("MemcacheCounter")); http://d.hatena.ne.jp/bluerabbit/20091008/1255007854
  • 25. 天気予報をメールする 天気予報が雨かを判断する。 特定の時間になったらメールする。 これらをバッチ処理で行う。 しかし、ここにも制約が存在する。
  • 26. 制約その2 MailAPI の呼出回数は24時間あたり 7000件(1分間に32件)までにすべし http://code.google.com/intl/ja/appengine/docs/quotas.html#Mail
  • 27. Mailの回数を制御する メール送信はDatastoreを用いた自作Queue を使用する。 メール送信する場合はMailQueueのKind (テーブル)にEntity(データ)を保存する。 MailQueueの送信はCronにて1分毎に MailQueueに未送信があればメールするよう にする。
  • 28. 処理イメージ
  • 29. この処理には2つの 誤りがある 1.ユーザ数が増加した場合に   _____しない。 スケール 2.エラーが発生した場合に   ________の危険性がある メール二重送信
  • 30. 制約その3 Datastore は定期的にエラーが出る ことを許容すべし DatastoreTimeoutException ApiProxy$UnknownException CapabilityDisabledException
  • 31. ユーザ登録 下記のユーザ情報を登録する 受信するメールアドレス 受信する時間 受信する曜日 お住まいの地域 メールでユーザ登録の確認をする MailQueueを作成する 上記の2つのEntityを登録する
  • 32. 制約その4 トランザクションは設計する必要がある RDB のように使えないことを許容すべし
  • 33. (案1) EntityGroup ユーザとメールキューをEntityGroup関係 にする ※説明のため、意図的にJDOのイメージで記載しています。 App Engine のEntityGroupを理解しよう
  • 34. (案2) 非正規化 1リクエストで複数のEntityを登録しない。 1つのEntityですべて処理する
  • 35. (案3) TaskQueue 1リクエストで複数のEntityを登録しない。 1リクエストは1Entityのみ登録する。 MailQueueはTaskQueueで登録する。
  • 36. (案4) 考慮しない エラーがたまに出ることを前提とする 一時的に不整合になることを許容する 偶発的に起こる事象に対して柔軟に対応で きるように備える エラー、不整合を早急に発見する方法を 作りこむ
  • 37. (案5) 補償トランザクション トランザクションをプログラムで補償する Insert時 Userの登録は正常終了 MailQueueが異常終了 異常を検知してUserをロールバックする (Userを削除する) Update時 Userを更新する前にバックアップを作成 する(Userをシリアライズして保存) 失敗した場合はバックアップから戻す ※30秒制限があるため実装は困難です。しかし、タスクキューを使えば出来なくもありません。
  • 38. どれが最適な案? 決め手はなに? 案1)EntityGroup 案2)非正規化 案3)TaskQueue 案4)考慮しない 案5)補償トランザクション
  • 39. Entity Groupって何? 全てのEntityはEntity Groupに所属 Entity Group内ではトランザクションをサポート 全ての操作が成功か失敗かになる Entityを作成するときに、別のEntityを新しいEntity の「親」に指定することができる 新しいEntityに対して親を指定することで、その新し いEntityは親Entityと同じEntity Groupに入る 親を持たないEntityはルートエンティティとなる Entityの親はEntityの作成時に定義され、後で変更 することはできない Entity Group全体に対してトランザクションの排他処 理は実行される
  • 40. ルートエンティティ String kind = "User"; Key userKey = KeyFactory.createKey(kind, 1); Entity user = new Entity(userKey); DatastoreService ds = DatastoreServiceFactory. getDatastoreService(); ds.put(user); KEY Kind User(1) User
  • 41. UserにMailQueueを追加 String kind = "MailQueue"; Key mailKey = KeyFactory.createKey(userKey, kind, 1); Entity mail = new Entity(mailKey); DatastoreService.put(mail); KEY Kind User(1) User User(1)/MailQueue(1) MailQueue
  • 42. EntityGroupはKeyで構成 KEY Kind User(1) User User(1)/MailQueue(1) MailQueue User(1)/MailQueue(2) MailQueue User(1)/Book(8) Book ※ルートエンティティが子エンティティ を保持している訳ではない
  • 43. 同一Kindでも構成可能 KEY Kind Bank(1) Bank Bank(1)/Bank(2) Bank Bank(1)/Bank(3) Bank Bank(1)/Bank(4) Bank ※注意:排他はEntityGroup全体
  • 44. EntityGroupの排他 tx = ds.beginTransaction() ;  口座A -1000円  口座B +1000円 tx = ds.beginTransaction() ; 口座C = ds.get(tx, keyC); tx.commit();  口座C -2000  口座D +2000 // ConcurrentModificationException tx.commit(); ※口座A、B、C、DはEntityGroupです。
  • 45. トランザクション内の分離レベルは SERIALIZABLE tx = ds.beginTransaction() ; 口座A = ds.get(tx, keyA);  口座A 残高照会 1000円 tx = ds.beginTransaction() ;  口座A -1000円  口座B +1000円 tx.commit(); 口座B = ds.get(tx, keyB);  口座B 残高照会 0円 ※リクエスト前は口座Bの残高は0円です。
  • 46. その他 困ったこと
  • 47. 制約その5 App Engine のDatastoreには ユニークキー制約がつけられない
  • 48. ユニークキー制約が ないのでこんなミス TaskQueueで以下の登録処理を実行した 1. パラメータでEntityの登録値を取得 2. Datastoreに新規登録  3. 終了処理   2.の処理後にエラーが出たら リトライされて2重登録された
  • 49. 対応策 TaskQueueで登録処理する場合は事前にキー を作成する  TaskQueueの追加処理    1. キューで登録するキーを作成する 2. キーをTaskのパラメータに設定する 登録処理 1. キーのパラメータを取得してキーが登録 されているかを確認する 2. 存在しない場合は登録処理をする
  • 50. 処理イメージ(1) // キーを作成する。 DatastoreService service = DatastoreServiceFactory.getDatastoreService(); KeyRange keys = service.allocateIds("Kind", 1); String key = KeyFactory.keyToString(keys.getStart()); ); // キューのパラメータにキーを設定する QueueFactory.getDefaultQueue(). add(TaskOptions.Builder.url("/insert"). param("key", key)); DatastoreServiceのjava doc
  • 51. 処理イメージ(2) // DatastoreService#get(Key)で登録有無をチェック String keyString = (String) request.getAttribute("key"); Key key = KeyFactory.stringToKey(keyString); try { DatastoreService service = DatastoreServiceFactory.getDatastoreService(); Entity e = service.get(key); // 登録済み } catch (EntityNotFoundException e) { // 未登録 // → 登録処理を行う }
  • 52. 対応策(2) Keyにユニークな名前をつける TaskQueueの追加処理 特に処理なし 登録処理 1. ユニークになるようにcreateKeyする 例えば、当アプリはLocationIdと日付 2. キーが既に登録されているかを確認する 3. 存在しない場合は登録処理をする
  • 53. 処理イメージ // Keyを作成 String keyName = "001" + "20091204"; Key key = KeyFactory.createKey("Kind", keyName); DatastoreService ds = DatastoreServiceFactory.getDatastoreService(); try { ds.get(key); } catch (EntityNotFoundException e) { Entity entity = new Entity(key); // 作成キーで登録 ds.put(entity); // 存在しないときにのみ登録 } KeyFactoryのjava doc
  • 54. まとめ 制約,エラーを寛大な心で受け入れる 制約ではなくルール ルールを守りながらプログラムするゲーム このゲームは必ず開発者を成長させる
  • 55. Let's Enjoy Cloud Programming!!
  • 56. ご清聴ありがとうございました
  • 57. Questions?