1 回限りの配信

このページでは、Pub/Sub の 1 回限りの機能を使用してメッセージを受信して確認応答する方法について説明します。これにより、メッセージの重複処理を追跡して防止できます。この機能を有効にすると、Pub/Sub では次のセマンティックが提供されます。

  • サブスクライバーは、メッセージの確認応答が正常に完了したかどうかを確認できます。

  • メッセージの確認応答が正常に完了すると、再配信は行われません。

  • メッセージが未処理の間、再配信は発生しません。メッセージは、確認応答期限が切れるか、確認応答が行われるまで、未処理とみなされます。

  • 複数の有効な配信が存在する場合は、確認応答期限の経過またはクライアントが開始した否定確認応答により、メッセージの確認応答に最新の確認応答 ID のみを使用できます。以前の確認応答 ID を持つリクエストは失敗します。

1 回限りの機能が有効になっている場合、サブスクライバーは次のガイドラインに従って、メッセージが 1 回だけ処理されるようにすることができます。

  • 確認応答期限内にメッセージの確認応答を返します。

  • メッセージが正常に確認応答されるまで、メッセージの処理の進行状況に関する情報を保持します。

  • メッセージの処理の進行状況に関する情報を使用して、確認応答が失敗した場合の重複作業を防ぎます。

StreamingPull API を使用するサブスクライバーを含め、pull サブスクリプション タイプのみが 1 回限りの配信をサポートします。push とエクスポートのサブスクリプションでは 1 回限りの配信はサポートされていません。

Pub/Sub では、Pub/Sub で定義された一意のメッセージ ID に基づき、クラウド リージョン内で exactly-once(1 回限り)の配信がサポートされます。

再納品と重複

想定されている再配信と予期しない再配信の違いを理解することが重要です。

  • 再配信は、メッセージに対してクライアントによる否定確認応答が行われた場合、または確認応答期限が切れる前にクライアントが確認応答期限を延長しなかった場合のいずれかか原因で発生することがあります。再配信は有効であり、システムは意図したとおりに動作しているとみなされます。

    再配信のトラブルシューティングについては、重複の処理をご覧ください。

  • 重複とは、確認応答が成功した後、または確認応答期限が切れる前にメッセージが再送信された場合を指します。

  • 再配信されたメッセージには、再配信の試行までの同じメッセージ ID が保持されます。

1 回限りの配信が有効になっているサブスクリプションでは、配信が重複することはありません。

クライアント ライブラリでの 1 回限りの配信のサポート

  • サポートされているクライアント ライブラリには、レスポンス付きの確認応答のインターフェースがあります(例: Go)。このインターフェースを使用して、確認応答リクエストが成功したかどうかを確認できます。確認応答リクエストが成功した場合、クライアントは再配信を受信しないことが保証されます。確認応答リクエストが失敗した場合、クライアントは再配信を想定できます。

  • クライアントは、確認応答インターフェースなしで、サポートされているクライアント ライブラリを使用することもできます。ただし、このような確認応答の失敗によってメッセージのサイレント再配信が生じる可能性があります。

  • サポートされているクライアント ライブラリには、最小リース延長時間を設定するためのインターフェースがあります(例: Go)。ネットワーク関連の確認応答の有効期限切れを回避するには、最小リース延長の値を大きな値に設定する必要があります。最大値は 600 秒に設定されています。

  • Java クライアント ライブラリを使用しており、setChannelProvider() メソッドを使用してカスタム gRPC Channel でサブスクライバーを初期化する場合は、TransportChannelProvider をビルドするときに maxInboundMetadataSize を 1 MB 以上に設定することをおすすめします。この構成では、InstantiatingGrpcChannelProvider.Builder.setMaxInboundMetadataSize() メソッドまたは ManagedChannelBuilder.maxInboundMetadataSize() メソッドを使用できます。

1 回限りの配信に関連する変数のデフォルト値と範囲、および変数の名前は、クライアント ライブラリによって異なる場合があります。たとえば、Java クライアント ライブラリでは、次の変数が 1 回限りの配信を制御します。

変数 説明
setEnableExactlyOnceDelivery 1 回限りの配信を有効または無効にします。 true または false(デフォルト =false)
minDurationPerAckExtension 確認応答期限の変更に使用する最小時間(秒単位)。 範囲=0~600(デフォルト =none)
maxDurationPerAckExtension 確認応答期限の変更の延長に使用する最大時間(秒単位)。 範囲=0~600(デフォルト =none)

1 回限りの配信の場合、確認応答 ID がすでに期限切れの場合、PubASub への modifyAckDeadline または acknowledgment リクエストが失敗します。この場合、新しい配信がすでに処理中の可能性があるため、サービスは期限切れの確認応答 ID を無効と見なします。これは 1 回限りの配信の配信のための設計です。acknowledgment リクエストと ModifyAckDeadline リクエストが INVALID_ARGUMENT レスポンスを返すことがわかります。1 回限りの配信の配信が無効になっていると、確認応答 ID が期限切れになった場合にこれらのリクエストは OK を返します。

acknowledgment リクエストと ModifyAckDeadline リクエストに有効な確認応答 ID が含まれるようにするには、minDurationPerAckExtension の値を大きな数値に設定することを検討してください。

リージョンに関する考慮事項

1 回限りの配信の保証は、サブスクライバーが同じリージョンでサービスに接続する場合にのみ適用されます。サブスクライバー アプリケーションが複数のリージョンに分散されていると、1 回限りの配信が有効になっている場合でも、メッセージの重複配信が発生する可能性があります。パブリッシャーは任意のリージョンにメッセージを送信でき、1 回限りの保証は維持されます。

Google Cloud内でアプリケーションを実行すると、デフォルトで同じリージョンの Pub/Sub エンドポイントに接続されます。したがって、 Google Cloud内の単一リージョンでアプリケーションを実行すると、通常は単一リージョンとやり取りすることになります。

Google Cloudの外部または複数のリージョンでサブスクライバー アプリケーションを実行している場合は、Pub/Sub クライアントの構成時にロケーション エンドポイントを使用することで、単一リージョンに接続していることを保証できます。Pub/Sub のすべてのロケーション エンドポイントは、単一のリージョンを指します。ロケーション エンドポイントの詳細については、Pub/Sub エンドポイントをご覧ください。Pub/Sub のすべてのロケーション エンドポイントのリストについては、ロケーション エンドポイントのリストをご覧ください。

1 回限りの配信のサブスクリプションを作成する

Google Cloud コンソール、Google Cloud CLI、クライアント ライブラリ、または Pub/Sub API を使用して、1 回限りの配信を行うサブスクリプションを作成できます。

pull サブスクリプション

Console

1 回限りの配信を行う pull サブスクリプションを作成するには、次の手順を行います。

  1. Google Cloud コンソールで、[サブスクリプション] ページに移動します。

    [サブスクリプション] に移動

  2. [サブスクリプションを作成] をクリックします。

  3. [サブスクリプション ID] を入力します。

  4. プルダウン メニューからトピックを選択するか、作成します。

    サブスクリプションがトピックからメッセージを受信します。

  5. [1 回限りの配信] セクションで、[1 回限りの配信を有効にする] を選択します。

  6. [作成] をクリックします。

gcloud

1 回限りの配信を行う pull サブスクリプションを作成するには、--enable-exactly-once-delivery フラグを指定して gcloud pubsub subscriptions create コマンドを使用します。

gcloud pubsub subscriptions create SUBSCRIPTION_ID \
  --topic=TOPIC_ID \
  --enable-exactly-once-delivery

以下を置き換えます。

  • SUBSCRIPTION_ID: 作成するサブスクリプションの ID
  • TOPIC_ID: サブスクリプションに関連付けるトピックの ID

REST

1 回限りの配信を行うサブスクリプションを作成するには、projects.subscriptions.create メソッドを使用します。

PUT https://pubsub.googleapis.com/v1/projects/PROJECT_ID/subscriptions/SUBSCRIPTION_ID
Authorization: Bearer $(gcloud auth print-access-token)

以下を置き換えます。

  • PROJECT_ID: サブスクリプションを作成するプロジェクトのプロジェクト ID
  • SUBSCRIPTION_ID: 作成するサブスクリプションの ID

1 回限りの配信を行う pull サブスクリプションを作成するには、リクエスト本文に次のように指定します。

{
  "topic": "projects/PROJECT_ID/topics/TOPIC_ID",
  "enableExactlyOnceDelivery": true,
}

以下を置き換えます。

  • PROJECT_ID: トピックが含まれるプロジェクトのプロジェクト ID
  • TOPIC_ID: サブスクリプションに関連付けるトピックの ID

C++

このサンプルを試す前に、クイックスタート: クライアント ライブラリの使用の C++ の設定手順を実施してください。詳細については、Pub/Sub C++ API リファレンス ドキュメントをご覧ください。

namespace pubsub = ::google::cloud::pubsub;
namespace pubsub_admin = ::google::cloud::pubsub_admin;
[](pubsub_admin::SubscriptionAdminClient client,
   std::string const& project_id, std::string const& topic_id,
   std::string const& subscription_id) {
  google::pubsub::v1::Subscription request;
  request.set_name(
      pubsub::Subscription(project_id, subscription_id).FullName());
  request.set_topic(pubsub::Topic(project_id, topic_id).FullName());
  request.set_enable_exactly_once_delivery(true);
  auto sub = client.CreateSubscription(request);
  if (sub.status().code() == google::cloud::StatusCode::kAlreadyExists) {
    std::cout << "The subscription already exists\n";
    return;
  }
  if (!sub) throw std::move(sub).status();

  std::cout << "The subscription was successfully created: "
            << sub->DebugString() << "\n";
}

C#

このサンプルを試す前に、クイックスタート: クライアント ライブラリの使用の C# の設定手順を実施してください。詳細については、Pub/Sub C# API リファレンス ドキュメントをご覧ください。


using Google.Cloud.PubSub.V1;
using Grpc.Core;

public class CreateSubscriptionWithExactlyOnceDeliverySample
{
    public Subscription CreateSubscriptionWithExactlyOnceDelivery(string projectId, string topicId, string subscriptionId)
    {
        SubscriberServiceApiClient subscriber = SubscriberServiceApiClient.Create();
        TopicName topicName = TopicName.FromProjectTopic(projectId, topicId);
        SubscriptionName subscriptionName = SubscriptionName.FromProjectSubscription(projectId, subscriptionId);

        var subscriptionRequest = new Subscription
        {
            SubscriptionName = subscriptionName,
            TopicAsTopicName = topicName,
            EnableExactlyOnceDelivery = true
        };

        Subscription subscription = null;

        try
        {
            subscription = subscriber.CreateSubscription(subscriptionRequest);
        }
        catch (RpcException e) when (e.Status.StatusCode == StatusCode.AlreadyExists)
        {
            // Already exists.  That's fine.
        }
        return subscription;
    }
}

Go

次のサンプルでは、Go Pub/Sub クライアント ライブラリのメジャー バージョン(v2)を使用しています。v1 ライブラリをまだ使用している場合は、v2 への移行ガイドをご覧ください。v1 コードサンプルの一覧については、 非推奨のコードサンプルをご覧ください。

このサンプルを試す前に、クイックスタート: クライアント ライブラリの使用の Go の設定手順を実施してください。詳細については、Pub/Sub Go API のリファレンス ドキュメントをご覧ください。

import (
	"context"
	"fmt"
	"io"

	"cloud.google.com/go/pubsub/v2"
	"cloud.google.com/go/pubsub/v2/apiv1/pubsubpb"
)

func createSubscriptionWithExactlyOnceDelivery(w io.Writer, projectID, topic, subscription string) error {
	// projectID := "my-project-id"
	// topic := "projects/my-project-id/topics/my-topic"
	// subscription := "projects/my-project/subscriptions/my-sub"
	ctx := context.Background()
	client, err := pubsub.NewClient(ctx, projectID)
	if err != nil {
		return fmt.Errorf("pubsub.NewClient: %w", err)
	}
	defer client.Close()

	pbSub := &pubsubpb.Subscription{
		Name:                      subscription,
		Topic:                     topic,
		EnableExactlyOnceDelivery: true,
	}
	sub, err := client.SubscriptionAdminClient.CreateSubscription(ctx, pbSub)
	if err != nil {
		return fmt.Errorf("failed to create exactly once sub: %w", err)
	}
	fmt.Fprintf(w, "Created a subscription with exactly once delivery enabled: %v\n", sub)
	return nil
}

Java

このサンプルを試す前に、クイックスタート: クライアント ライブラリの使用の Java の設定手順を実施してください。詳細については、Pub/Sub Java API リファレンス ドキュメントをご覧ください。

import com.google.cloud.pubsub.v1.SubscriptionAdminClient;
import com.google.pubsub.v1.ProjectSubscriptionName;
import com.google.pubsub.v1.ProjectTopicName;
import com.google.pubsub.v1.Subscription;
import java.io.IOException;

public class CreateSubscriptionWithExactlyOnceDelivery {
  public static void main(String... args) throws Exception {
    // TODO(developer): Replace these variables before running the sample.
    String projectId = "your-project-id";
    String topicId = "your-topic-id";
    String subscriptionId = "your-subscription-id";

    createSubscriptionWithExactlyOnceDeliveryExample(projectId, topicId, subscriptionId);
  }

  public static void createSubscriptionWithExactlyOnceDeliveryExample(
      String projectId, String topicId, String subscriptionId) throws IOException {
    try (SubscriptionAdminClient subscriptionAdminClient = SubscriptionAdminClient.create()) {

      ProjectTopicName topicName = ProjectTopicName.of(projectId, topicId);
      ProjectSubscriptionName subscriptionName =
          ProjectSubscriptionName.of(projectId, subscriptionId);

      Subscription subscription =
          subscriptionAdminClient.createSubscription(
              Subscription.newBuilder()
                  .setName(subscriptionName.toString())
                  .setTopic(topicName.toString())
                  // Enable exactly once delivery in the subscription.
                  .setEnableExactlyOnceDelivery(true)
                  .build());

      System.out.println(
          "Created a subscription with exactly once delivery enabled: "
              + subscription.getAllFields());
    }
  }
}

Python