push タスクの作成

このページでは、タスクを作成して push キューに配置する方法を説明します。タスクを処理する場合は、新しいタスク オブジェクトを作成してキューに配置する必要があります。タスクを処理するサービスとハンドラを明示的に指定し、必要に応じてタスク固有のデータをハンドラに渡すこともできます。また、タスクを実行するタイミングのスケジューリング、タスクが失敗した場合の再試行回数の制限など、タスクの構成を微調整することもできます。

新しいタスクを作成する

新しいタスクを作成してキューに追加するには、QueueFactory を使用して Queue を取得し、その add() メソッドを呼び出します。queue.xml ファイルで指定されている名前付きキューを、ファクトリの getQueue() メソッドを使用して取得することも、デフォルトのキューを getDefaultQueue() を使用して取得することもできます。Queueadd() メソッドを呼び出す際には、TaskOptions インスタンス(TaskOptions.Builder で生成されます)を指定できます。引数を指定せずに呼び出すと、そのキューのデフォルトのオプションを使用してタスクを作成できます。

import com.google.appengine.api.taskqueue.Queue;
import com.google.appengine.api.taskqueue.QueueFactory;
import com.google.appengine.api.taskqueue.TaskOptions;
Queue queue = QueueFactory.getDefaultQueue();
queue.add(TaskOptions.Builder.withUrl("/worker").param("key", key));

ワーカー サービスを指定する

タスクがキューから取り出される(ポップ)と、タスクはタスクキュー サービスによってワーカー サービスに送信されます。いずれのタスクにもターゲットと url があり、どのサービスとハンドラがそのタスクを実行するかがこれらによって決まります。

target

ターゲットは、どのサービスがタスク実行の HTTP リクエストを受け取るかを指定します。これは文字列であり、サービス / バージョン / インスタンスを正規形式のいずれかで指定します。よく使用される形式は次のとおりです。

    service
    version.service
    instance.version.service

ターゲット文字列の前には、アプリのドメイン名が付加されます。タスクのターゲットを設定するには、次の 3 つの方法があります。

  • タスクを作成するときにターゲットを宣言します。タスクの作成時にターゲットを明示的に設定するには、TaskOptions を使用して Host ヘッダーを設定します。

    taskOptions.header("Host", versionHostname)
    

  • queue.xml でキューを定義するときに、target ディレクティブを指定します(queue-blue定義をご覧ください)。target が指定されているキューに追加されるタスクはすべて、そのターゲットを使用します。タスクの作成時に別のターゲットが割り当てられていても無視されます。

  • 上記の 2 つの方法のいずれかでターゲットが指定されていない場合、タスクをキューに追加したサービスのバージョンが、そのタスクのターゲットになります。この方法でタスクをデフォルトのサービスとバージョンからキューに追加した場合、タスクの実行前にデフォルトのバージョンが変更されると、タスクは変更後のデフォルトのバージョンで実行されます。

url

url に基づいて、ターゲット サービスのハンドラのうち 1 つが選択され、そのハンドラによってタスクが実行されます。

url は、ターゲット サービス内のハンドラ URL パターンのうち 1 つに一致する必要があります。タスクに指定されたメソッドが GET または PULL である場合は、url にクエリ パラメータを含めることができます。url が指定されていない場合は、デフォルトの URL /_ah/queue/[QUEUE_NAME] が使用されます。ここで [QUEUE_NAME] はタスクのキューの名前です。

データをハンドラに渡す

データをハンドラに渡す場合、タスクの URL 内のクエリ パラメータとして渡す方法がありますが、この方法が可能なのはタスクで指定されているメソッドが GETPULL の場合のみです。

TaskOptions.Builder コンストラクタには、データを HTTP リクエストのペイロードとして、およびパラメータとして追加するためのメソッドがあります。パラメータはクエリ パラメータとして URL に追加されます。

params
POST メソッドをペイロードとともに使用する場合や、GET メソッドを使用するときに URL とクエリ パラメータを付加する場合は、params を指定しないでください。

タスクに名前を付ける

新しいタスクを作成すると、デフォルトではタスクに一意の名前が割り当てられます。ただし、name パラメータを使用すると、タスクに独自の名前を割り当てることができます。独自のタスク名を割り当てることの利点は、名前付きのタスクでは重複が除外されることです。つまり、タスク名を使用すると、タスクが 1 回のみ追加されることを保証できます。重複の除外は、タスクが完了するか、削除されてから 9 日間続きます。

重複除外ロジックはパフォーマンスのオーバーヘッドを大幅に増加させるため、レイテンシが増加し、場合によっては名前付きタスクに伴うエラー率が高まるので注意してください。このようなコストは、タイムスタンプのように連続したタスク名が付けられている場合、大幅に増大する可能性があります。そのため、独自の名前を割り当てる場合は、コンテンツのハッシュなどの適度に分散された接頭辞をタスク名に使用することをおすすめします。

独自の名前をタスクに割り当てる場合、名前の最大長が 500 文字で、名前には英字の大文字と小文字、数字、アンダースコア、ハイフンを使用できることに注意してください。

非同期でのタスクの追加

デフォルトでは、タスクをキューに追加する呼び出しは同期的です。ほとんどのシナリオでは、同期呼び出しで問題ありません。タスク 1 個のキューへの追加は、通常は短時間で終わるオペレーションです。かなり長い時間を要するタスク追加オペレーションもごくわずかにありますが、タスク 1 個の追加に要する時間の中央値は 5 ミリ秒未満です。

異なるキューに対するタスク追加オペレーションはバッチ処理できないため、Task Queue API にはそのようなタスクを並行して追加できる非同期呼び出しが用意されており、このレイテンシをさらに短縮できます。これは、レイテンシの影響を受けやすく、異なるキューに対するタスク追加オペレーションを同時に複数個実行する必要があるアプリケーションを作成する場合に便利です。

タスクキューに対して非同期呼び出しを行う場合は、Queue クラスの非同期メソッドを使用します。返された Future に対して get を呼び出すと、リクエストを強制的に完了させることができます。トランザクションの中で非同期でタスクを追加するときは、リクエストを確実に完了させるために、get()Future に対して呼び出してからトランザクションを commit してください。

Cloud Datastore トランザクションの中でタスクをキューに追加する

タスクを Datastore トランザクションの一部としてキューに登録できます。これにより、トランザクションが正常に commit された場合にのみ、タスクがキューに登録されるようになります(キューへの登録が保証されます)。トランザクションの中で追加されたタスクは、そのトランザクションの一部とみなされ、同じレベルの分離と整合性を持つことになります。

1 つのトランザクションの中でタスクキューに挿入できるトランザクション タスクは 5 個までです。トランザクション タスクの名前をユーザーが指定することはできません。

次のコードサンプルでは、Datastore トランザクションの一部としてトランザクション タスクを push キューに挿入する方法を示します。

DatastoreService ds = DatastoreServiceFactory.getDatastoreService();
Queue queue = QueueFactory.getDefaultQueue();
try {
    Transaction txn = ds.beginTransaction();

    // ...

    queue.add(TaskOptions.Builder.withUrl("/path/to/my/worker"));

    // ...
    txn.commit();
} catch (DatastoreFailureException e) {
}

ワーカー サービスの代わりに DeferredTask を使用する

これまでのセクションで説明したような、タスクごとに別のハンドラを設定する処理は煩雑になりがちであり、タスクの複雑な引数のシリアル化とシリアル化解除も同様です。特に、キューで実行するタスクが小さく、多種多様で数も多い場合です。Java SDK には DeferredTask というインターフェースが含まれています。このインターフェースを使用すると、タスクを 1 つのメソッドとして定義できます。このインターフェースは、Java のシリアル化を使用してひとまとまりの作業をパッケージ化し、タスクキューに追加します。そのメソッドから正常に制御が戻ると成功とみなされ、例外がスローされた場合は失敗とみなされます。


/** A hypothetical expensive operation we want to defer on a background task. */
public static class ExpensiveOperation implements DeferredTask {

  @Override
  public void run() {
    System.out.println("Doing an expensive operation...");
    // expensive operation to be backgrounded goes here
  }
}

/**
 * Basic demonstration of adding a deferred task.
 *
 * @param request servlet request
 * @param resp servlet response
 */
@Override
public void doGet(final HttpServletRequest request, final HttpServletResponse resp)
    throws IOException {
  // Add the task to the default queue.
  Queue queue = QueueFactory.getDefaultQueue();

  // Wait 5 seconds to run for demonstration purposes
  queue.add(
      TaskOptions.Builder.withPayload(new ExpensiveOperation())
          .etaMillis(System.currentTimeMillis() + DELAY_MS));

  resp.setContentType("text/plain");
  resp.getWriter().println("Task is backgrounded on queue!");
}

マルチテナント アプリケーションでタスクを扱う

デフォルトでは、push キューについては、タスクの作成時に名前空間マネージャーで設定されていた現在の名前空間が使用されます。アプリケーションでマルチテナントを使用する場合は、Namespaces Java 8 API をご覧ください。

次のステップ