Rails on GCP(GAE)の非同期処理の構成を考える
従来は、キューとしてRedisを利用するSidekiqなどのライブラリが主流だが、Redisはコストが高いしマネージドサービスだとしても管理コストは掛かるので使いたくない。クラウド環境においてはキューサービスがあるので、そちらを利用する構成を考える。
ActiveJobに統合されたライブラリとして、以下がある。
AWSならSQSを使うshoryuken: https://github.com/ruby-shoryuken/shoryuken
GCPならCloud Tasksを使う cloudtasker: https://github.com/keypup-io/cloudtasker
そもそもCloud TasksはPush型のサービスなので、Job worker(Pullして処理する人)を作る必要がない?
CloudTasks
処理時間
- 宛先がGAEではないHTTPターゲットの場合、10分以内
- 宛先がGAEスタンダート環境の場合、自動スケーリングで10分以内、手動スケーリングで24時間以内
- 宛先がGAEフレキシブル環境の場合、60分以内
https://cloud.google.com/tasks/docs/creating-appengine-handlers?hl=ja - レスポンスが2xx以外、またはレスポンスがない場合はリトライされる
元々はGAEのタスクキューとして開始されたものが、Cloud Tasksとして独立した形。
だから設定方法としてqueue.yamlとCloud Tasks APIをつかう2種類の方法が残っている。
Cloud Tasks または App Engine を初めて使用する場合は、Cloud Tasks API だけを使用してキューを管理し、queue.yaml と queue.xml の使用は避けてください。Cloud Tasks でキューを管理すると、キューの作成、更新、削除を行うときの選択肢が増えます。
今から使い始めるのならAPIで管理する方法が推奨される。
「default」という名前の App Engine キューに対しては、App Engine SDK でも Cloud Tasks API でも特殊な処理が行われます。
defaultというキューは特殊な扱いになるので、避けたほうが良さそう。
キューは、適切なワーカーを含むサービスの名前とバージョンを必要とします。これらの情報をターゲットといいます。ターゲットを設定するには、次の 3 つの方法があります。
GAEをターゲットとする場合、アプリのバージョン指定も必要。GAEのアプリを切り替えたときに、キューのターゲットのアプリバージョンをどう切り替えるか?
どちらの場合も、トークン バケット アルゴリズムを使用して、タスクの実行レートが制御されます。
ディスパッチのレート制御は、トークンバケット方式。それに加えて最大同時実行数も制御ができる。
ターゲットのインスタンスが複数ある場合、リクエストは均等に分散されるのだろうか?
処理時間が長い処理と短い処理が混在した場合に、うまく負荷分散できるのだろうか?
汎用HTTPターゲットでは、Cloud Functionsもターゲットにできる!
GAEでタスクをキューに入れる方法
SDKがあるので簡単。GAEターゲット側の実装
App Engine タスクでは、キューとタスクハンドラは同じ Cloud プロジェクト内で実行されます。
プロジェクトは同じである必要がある。
安全なタスクハンドラ、安全性の低いタスクハンドラ、サポートされるランタイムにおいては
login: admin
で制限された URI に、タスクをディスパッチできます。
これを指定しておけば、外から叩かれることはない。
login handler のサポートは終了していた。
Cloud Pub/SubとCloud Tasksの違い
負荷分散について
前提
- ターゲットはGAEのフレキシブル環境。
- ターゲットはマルチスレッドで複数のリクエストを並行して処理できるものとする。
- ここでは仮にスレッド数を10とする。
タスクの想定処理時間に応じて3つのキューを作り、ターゲット1台あたり以下の設定にする。
- 処理時間が小(~1s)
- maxDispatchesPerSecond: 20
- maxConcurrentDispatches: 10
- 処理時間が中(1s~30s)
- maxDispatchesPerSecond: 1
- maxConcurrentDispatches: 5
- 処理時間が大(30s~最大60,000s)
- maxDispatchesPerSecond: 1
- maxConcurrentDispatches: 1
ターゲットのインスタンスが複数ある場合、リクエストは均等に分散されるのだろうか?
処理時間が長い処理と短い処理が混在した場合に、うまく負荷分散できるのだろうか?
このあたりはGAEのPending Request Queueの動作による。
GAEをターゲットとする場合、アプリのバージョン指定も必要。GAEのアプリを切り替えたときに、キューのターゲットのアプリバージョンをどう切り替えるか?
Queueの設定では、バージョンを省略することもできる。
このとき、デフォルトのバージョンにルーティングされるとあるが、デフォルトのバージョンとはなにか?
バージョン名なしのURLのことと思われる。
注: ハンドラをべき等にするかどうか検討する必要があります。Cloud Tasks は「少なくとも 1 回」処理を行うように設計されています。つまり、タスクが正常に追加された場合、キューはそれを少なくとも 1 回配信します。まれに複数のタスクが実行されることもあるので、反復的な実行で有害な副作用が生じないように注意してください。
配信は「少なくとも1回」なので、ハンドラ側は冪等性を担保する必要がある。