やっぱり AppEngine ja night #2 で聞いた発表のメモです。
「Design of TaskQueue」 shin1ogawa さん
Taskとはhttpのリクエストハンドラ。パラメータはリクエストパラメータとして受け取れる。
通常のHTTPリクエストは60秒以内(昔は10秒以内だった)に返す必要があるが、タスクの場合はもっと長い。
Queueの種類
- Push Queue
- GAEが自動的に処理をドンドン起動する
- 2xx以外のパラメータが返ると自動リトライしつづける
- Pull Queue
- GAE側が自分でタスクを拾いに行く
- 動きはアプリケーション次第
- キューにタスクを詰めるという部分は同じ
- GAE側が自分でタスクを拾いに行く
- Scheduled Queue
- いわゆる Cron
Queue の設計
大事なことは タスクを実行させるインスタンス(serviceとかmoduleという単位)を固定する ということ。
Service(Module)の設定でインスタンスのクラスを指定できる(どのserviceでタスクを実行するかをqueueで設定できる)
タスクの設計
- チェインパターン
- タスク自身が次のタスクを投入する方式
- 例えば100個ずつ処理 → タスクの終了時に次のタスクをキューに詰める → 新タスクが100個ずつまた処理 → 繰り返す...
- 捜査対象を拾うタスクと、処理を行うタスクで2つを走らせるパターン(単純なチェインパターンより早い)
- データストアのカーソルを取得してキューに詰めるタスク
- カーソルからデータストアのデータを拾い処理するタスク
タスクを分割すると待ち合わせが必要になる。
複雑なタスクのパターンは、タスクの状態はDSに保存する。
そのDSの状態を読みに行って、完了かどうかを監視する
少しでも複雑な状態になるならDSのエンティティによる管理を推奨する
Task は Datastore のためにある
AppEngineでは、ほぼデータストアのためにTQを使う。
それがほとんど。
多くの場合は整合性の保証、非正規化したデータをなんとかする場合など。
あとはキャッシュの更新や、データのマイグレーション、壊れたデータの復旧など。
Datastoreの補助としてのタスク(Pullキュー)
- 1エンティティの上書き頻度を下げる
- 同一エンティティの書き込みは1秒に1回までの制限を何とかする
- ページのビュー数や頻度の高い位置情報の更新とか
- 更新頻度をまとめたり、間引いたりする
- 同一エンティティの書き込みは1秒に1回までの制限を何とかする
Pullキューを使うシナリオ例
- ページのビュー数をエンティティに記録したい
- F5連鎖された時に、Pullキューに詰める
- GAEアプリケーション側からタスクを取りに行って、タスク数をカウントして一気にページのビュー数を更新する
- アクセス毎にDatastoreにアクセスしなくてもよくなる
- ユーザーの位置情報をエンティティに記録したい
- ユーザーのモバイル端末から位置情報が頻繁に送られてくる
- 位置情報の記録精度はどこまで保証しなくていい場合
- ユーザーの位置情報をタスクに詰める
- Pullキューで必要ないタスクを間引く
- エンティティの書き込み数を減らせる
- ユーザーのモバイル端末から位置情報が頻繁に送られてくる
その他のバッチ処理の場合
- 外部APIを叩く場合は、速度やリトライの時間が大事かも
- queueの設定でなんとかできるなら、queueでやるほうがよいかも
- Datastoreの併用で状態管理が大事になる
- データストア外の大量データを扱う場合は、メモリの上限が問題になる場合が
- 処理を小さくする分割するしか無い
- appengine の GCS の 32MB 上限にぶちあたることがある (/gs/...)
- 巨大なテキストファイルとか
まとめ
- Datastoreの設計にはタスクキューの設計が大きく影響する
- 整合性を保たせるために, どれくらい時間がかかるか
- それをどれくらい許容できるか
- インスタンスの性能も大事
- モジュールという機能をちゃんと把握しておくよい (Queue と紐付けられるので)
質疑応答
Q. タスクキューと Cloud Pub/Sub の使い分けは?
A. 明確な指針は無い。他のプロダクトをどれだけ使うかによる。GAEがメインじゃない場合はPub/Subを使うかもしれない。プロダクトの中心がGAEに寄った構成ならタスクキューを使うだろう。
「Datastore backup and restore/Python to Go on App Engine」 drillbits さん
発表資料: https://docs.google.com/presentation/d/17uf24u2ORhII0sfZGRwevxAtI1T9-8MkyZcXm3f5Pec
Datastore Adminをつかって Datastore のスナップショットを別プロジェクトへ移行することができる。
ただ、移行する前にオリジナルのGCPプロジェクトを削除してしまうと、GCSの名前がハードコーディングされているため、インポート時に失敗するとのこと。
そこでオリジナルのプロジェクトが削除された後でも移行できる drillbits/tirtds というツール作った
GAE/Go の Go1.8化 vvkame さん
発表資料: https://speakerdeck.com/vvakame/yappari-appengine-ja-night-number-2
yaml で go1
と指定することで Goの1.8 でビルドされるようになった。
Go1.6 -> 1.8 へのアップデートでアプリが死ぬ現象が起きるようになった。
gcpug/nouhauリポジトリのissueに情報が集まっているので見ましょう。
よく起きるのは Context
の部分
yaml で go1.6
を明示すると Go1.6 でビルドされるので、とりあえずはこれで回避できる。
なので焦らず net/context
から標準の context
へゆっくり移動していきましょう。
「wordpress on GAEをやってみた」 ExistMikanさん
発表資料: https://www.slideshare.net/ExistMikan/wordpress-on-gae-se
WordPress のプラグインをいじると、デプロイの度に手作業が必要になるとのこと。
「GAE/GoでのSearch API」 桑原国仁さん
発表資料: URL探せず...
SearchAPI とは全文検索可能用の検索に特化したストレージ。DatastoreやGCSとは別の独立したもの。
GAE/Goでは使えない or
や not
を使ったクエリが使える
日本語の場合は、形態素解析もしてくれて細かい検索もできる。
いくつか用語が異なる。
index -> Datastore でいう kind
document -> Datastore でいう entity
その他に,
- 15,000/m の書き込み制限がある。
- index は後から消せないらしい。
- ウェブコンソールからのデータ操作ができないので、ソフトウェア側で実装する必要あり。
search api のベストプラクティス
- or, not を節約する
- 書き込みの多いデータには向かない
- 検索項目もカテゴリで絞ってから検索したほうが高速
- 例: 値段で検索したい場合
- 値段範囲(price_range)をつくって分けておく
- 範囲を絞ってから、その範囲内のドキュメントを検索する
- Datastoreの連携による一貫性に注意
- 大きいindex1つではなく、うまく分散して動かす
- ローカル環境と挙動が違うので注意
- 実際にあげて動作確認しましょう
感想
最近仕事で GAE/Go を触る機会があるがドキュメントを読みながら探り探りで作業をしているので、このようなエキスパートや実務でゴリゴリ扱っている方々がノウハウを共有する会は非常に参考になった。
3ヶ月〜半年くらいのスパンで定期的に開催することを計画されているようなので、次回も是非参加したい。
また、GCPUGのSlack や gcpug/nouhauリポジトリに色々と知見が共有されているらしいので、そのようなコミュニティについても知れたのが良かったです。
※ nouhau
リポジトリはGCPに関する知見をPRすると、レビュー後にマージして貰えるそうです。
最後に、今回発表して知見を共有していただいた方々に感謝いたします。