The Round

合同会社ナイツオの開発ブログ

静岡でGo言語やりたい人!!→こちら

GAE/Go TaskQueueについて その3~Delayパッケージ~

どうもこんにちわ!マツウラです。

Push,Pullに続き今回はdelayパッケージについてです。
タスク毎に異なるハンドラを定義する手間を省くことが出来るのでちょっとしたタスク処理に便利そうです。
公式ドキュメントを参考に見てゆきます。

参考:The delay package - Go — Google Developers

キューで実行したい小さなタスクが大量にある場合、タスクそれぞれに異なるハンドラを設定するのは非常に厄介です。
そこで Go SDKには専用のタスクハンドラを設定、パラメータをシリアライズ&デシリアライズする作業を全てバイパスするシンプルなAPIappengine/delayパッケージとして含まれています。

delayパッケージはタスクキューAPIを用いてユーザーリクエスト外部でコードを実行する方法を提供しています。

後で実行する関数を宣言する方法は、コンテキストのトップレベルでFuncメソッドを呼び、任意の文字列キー、および関数を渡します(第一引数がappengine.Context型)。

宣言した関数はCallメソッドにより呼びだされます。

// var laterFunc = delay.Func("put", myFunc) でも可。
var expensiveFunc = delay.Func("put", func(c appengine.Context, k *datastore.Key, e *Person) {
    _, err := datastore.Put(c, k, e)
    if err != nil {
        c.Errorf("%v", err)
        return
    }
})

func Handler(w http.ResponseWriter, r *http.Request) {
    c := appengine.NewContext(r)

    e := &Person{}
    e.FirstName = "Abraham"
    e.LastName = "Barth"
    k := datastore.NewKey(c, "Person", e.FirstName+":"+e.LastName, 0, nil)
    expensiveFunc.Call(c, k, e)
}

delayパッケージは関数を呼び出し、引数をシリアライズ、その後タスクキューに追加されます。
タスクが実行されるとdelayパッケージは関数を実行します。

関数は通常のTaskqueueと同様、べき等となるように注意してください。

関数では任意の値を返すことができます。
また、引数の最後でerror型を返す場合、nil以外のエラーを返すことでリトライさせることができます。

関数の引数はgobパッケージによりエンコード可能な任意の型でもかまいません。
もし引数がinterface型ならば、引数に渡す実際の型が何であれgobパッケージで登録することはクライアントの責任です(ここの詳細はこちら:http://golang.org/pkg/gob/#Register)。

初期化や関数の実行中のどんなエラーもアプリケーションログに記録されます。
初期化中に起きたエラーログは、Callメソッドを呼び出しリクエストに関連付けされます。

まだ正常に実行されていない関数の呼び出しは、Func関数に渡された文字列キーで宣言されたファイル名を組み合わせることで保護されます。

<パッケージ名>/<ファイル名>:<文字列キー>
// 例えば、hello/delay.go:put

関数呼び出しが保留中にアプリケーションが更新されても、関連する機能はファイル名とキーの組み合わせで保護される限り安全です。

delayパッケージはタスクの作成にTask Queue APIを使用します。
この時、予約済みの"/_ah/queue/go/delay"パスで呼び出されます。
このパスはapp.yamlで"login: required"と記述してはいけません。
ただ、"login: admin"の記述は必要です。でないとアクセス制限が行われません。

delay func

func Func(key string, i interface{}) *Function

新たな関数を宣言します。
第二引数はappengine.Context型を第一引数にもつ関数である必要があります。

使用時の注意

この関数はプログラムの初期化時に呼ぶ必要があります。
つまりグローバル変数として宣言する、またはinit関数から呼ぶ必要があるということです。

仮に上記の例で言えばHandler関数内でexpensiveFuncを作成したとします。
コンパイルでもエラーは出ませんが次の問題があります。 関数を遅延実行するサーバーインスタンスが異なると、当の遅延実行される関数が作成、初期化されていないことになります。
そのため遅延関数が実行されません。
プログラム初期化時に実行されるコードは、リクエストを受信する前にインスタンスによって実行されることが保証されているのでFuncは初期化時に呼ぶように注意して下さい。