메시지 순서 지정

메시지 순서 지정은 게시자 클라이언트가 게시한 순서대로 구독자 클라이언트에서 메시지를 수신할 수 있게 해주는 Pub/Sub의 기능입니다.

예를 들어 리전의 게시자 클라이언트가 메시지 1, 2, 3을 순서대로 게시한다고 가정해 보겠습니다. 메시지 순서 지정을 사용하면 구독자 클라이언트는 게시된 메시지를 동일한 순서로 수신합니다. 순서대로 전달하려면 게시자 클라이언트가 동일한 리전에 메시지를 게시해야 합니다.

메시지 순서 지정은 데이터베이스 변경 캡처, 사용자 세션 추적, 이벤트 시간 보존이 중요한 스트리밍 애플리케이션과 같은 시나리오에 유용한 기능입니다.

이 페이지에서는 메시지 순서 지정의 개념과 메시지를 순서대로 수신하도록 구독자 클라이언트를 설정하는 방법을 설명합니다. 메시지 순서 지정을 위해 게시자 클라이언트를 구성하려면 순서 키를 사용하여 메시지 게시를 참조하세요.

메시지 순서 지정 개요

Pub/Sub의 순서 지정은 다음에 따라 결정됩니다.

  • 순서 키: Pub/Sub 메시지 메타데이터에 사용되는 문자열이며, 메시지를 정렬해야 하는 항목을 나타냅니다. 순서 키의 최대 길이는 1KB입니다. 한 리전에서 순서가 지정된 메시지 집합을 받으려면 같은 리전에 동일한 순서 키로 모든 메시지를 게시해야 합니다. 순서 키의 예시로는 고객 ID와 데이터베이스에 있는 행의 기본 키가 있습니다.

    각 순서 키의 게시 처리량은 1Mbps로 제한됩니다. 한 주제의 모든 순서 키에 대한 처리량은 게시 리전에서 사용 가능한 할당량으로 제한됩니다. 이 한도는 많은 Gbps 단위로 늘릴 수 있습니다.

    순서 키는 파티션 기반 메시징 시스템의 파티션과 다릅니다. 순서 키는 파티션보다 카디널리티가 훨씬 높을 수 있습니다.

  • 메시지 순서 지정 사용 설정: 구독 설정입니다. 구독에 메시지 순서 지정이 사용 설정된 경우 구독자 클라이언트는 서비스에서 수신한 순서대로 동일한 순서 키를 사용하여 동일한 리전에 게시된 메시지를 수신합니다. 구독에서 이 설정을 사용 설정해야 합니다.

    동일한 주제 T에 두 개의 구독 A와 B가 연결되어 있다고 가정해 보겠습니다. 구독 A는 메시지 순서 지정이 사용 설정된 상태로 구성되고 구독 B는 메시지 순서 지정을 사용 설정하지 않고 구성됩니다. 이 아키텍처에서 구독 A와 B는 모두 주제 T로부터 동일한 메시지 집합을 수신합니다. 동일한 리전에 순서 키로 메시지를 게시하면 구독 A는 게시된 순서대로 메시지를 수신합니다. 반면에 구독 B는 예상 순서 없이 메시지를 수신합니다.

일반적으로 솔루션에서 게시자 클라이언트가 순서가 지정된 메시지와 순서가 지정되지 않은 메시지를 모두 전송해야 하는 경우 순서가 지정된 메시지와 순서가 지정되지 않은 메시지를 위한 주제를 별도로 만듭니다.

순서가 지정된 메시징 사용 시 고려사항

다음 목록에는 Pub/Sub의 순서가 지정된 메시징 동작과 관련된 중요한 정보가 포함되어 있습니다.

  • 키 내 순서 지정: 동일한 순서 키로 게시된 메시지는 순서대로 수신될 것으로 예상됩니다. 순서 키 A에 대해 메시지 1, 2, 3을 게시한다고 가정합니다. 순서 지정이 사용 설정되면 1이 2보다 먼저 전송되고 2가 3보다 먼저 전송되어야 합니다.

  • 교차 키 순서 지정: 서로 다른 순서 키로 게시된 메시지는 순서대로 수신되지 않습니다. 순서 키 A와 B가 있다고 가정해 보겠습니다. 순서 키 A의 경우 메시지 1과 2가 순서대로 게시됩니다. 순서 키 B의 경우 메시지 3과 4가 순서대로 게시됩니다. 하지만 메시지 1은 메시지 4 전이나 후에 도착할 수 있습니다.

  • 메시지 재전송: Pub/Sub는 각 메시지를 최소 한 번은 전송하므로 Pub/Sub 서비스에서 메시지를 다시 전송할 수 있습니다. 메시지가 다시 전송되면 확인된 메시지를 포함하여 해당 키의 모든 후속 메시지의 재전송이 트리거됩니다. 구독자 클라이언트가 특정 순서 키에 대해 메시지 1, 2, 3을 수신한다고 가정합니다. 메시지 2가 다시 전송된 경우(확인 기한이 만료되었거나 Pub/Sub에 최선의 확인이 유지되지 않음) 메시지 3도 다시 전송됩니다. 구독에서 메시지 순서 지정 및 데드 레터 주제가 모두 사용 설정된 경우, 가능하면 Pub/Sub가 메시지를 데드 레터 주제로 전달하기 때문에 이 동작이 참이 아닐 수 있습니다.

  • 확인 지연 및 데드 레터 주제: 지정된 순서 키에 대해 확인되지 않은 메시지는 특히 서버 재시작 또는 트래픽 변경 시 다른 순서 키의 메시지 전송을 지연시킬 수 있습니다. 이러한 이벤트에서 순서를 유지하려면 모든 메시지를 적시에 확인해야 합니다. 적시에 확인할 수 없는 경우 무기한 메시지 보류를 방지하기 위해 데드 레터 주제를 사용하는 것이 좋습니다. 메시지를 데드 레터 주제에 쓸 경우 순서가 유지되지 않을 수 있습니다.

  • 메시지 어피니티(streamingPull 클라이언트): 동일한 키의 메시지는 일반적으로 동일한 streamingPull 구독자 클라이언트로 전달됩니다. 어피니티는 특정 구독자 클라이언트에 대한 순서 키의 메시지가 대기 중일 때 발생합니다. 대기 중인 메시지가 없으면 어피니티가 부하 분산을 위해 이동되거나 클라이언트 연결이 끊어질 수 있습니다.

    잠재적인 어피니티 변경이 있더라도 원활한 처리를 보장하기 위해서는 지정된 순서 키에 대해 모든 클라이언트에서 메시지를 처리할 수 있도록 streamingPull 애플리케이션을 설계하는 것이 중요합니다.

  • Dataflow와 통합: Pub/Sub로 Dataflow를 구성할 때 구독에 메시지 순서 지정을 사용 설정하지 마세요. Dataflow에는 전체 메시지 순서 지정을 위한 자체 메커니즘이 있으며 기간 설정 작업의 일부로 모든 메시지를 시간순으로 보장합니다. 이 순서 지정 방법은 Pub/Sub의 순서 키 기반 방식과 다릅니다. Dataflow에서 순서 키를 사용하면 파이프라인 성능이 저하될 수 있습니다.

  • 자동 확장: Pub/Sub의 순서가 지정된 전송은 수십억 개의 순서 키로 확장됩니다. 동일한 순서 키가 있는 모든 메시지에 순서 지정이 적용되기 때문에 순서 키 수가 많으면 구독자에게 더 많은 동시 전송이 가능합니다.

순서가 지정된 전송은 몇 가지 영향을 미칩니다. 순서가 지정된 전송은 순서가 지정되지 않은 전송과 비교하여 게시 가용성을 약간 낮추고 엔드 투 엔드 메시지 전송 지연 시간을 늘릴 수 있습니다. 순서가 지정된 전송 사례의 경우 메시지가 올바른 순서로 기록되고 읽히도록 장애 조치에 조정이 필요합니다.

메시지 순서 지정을 사용하는 방법에 대한 자세한 내용은 다음 권장사항 주제를 참조하세요.

메시지 순서 지정의 구독자 클라이언트 동작

구독자 클라이언트는 특정 리전에 게시된 순서대로 메시지를 수신합니다. Pub/Sub는 구독 가져오기 및 내보내기에 연결된 구독자 클라이언트와 같이 메시지를 수신하는 다양한 방법을 지원합니다. 클라이언트 라이브러리는 streamingPull을 사용합니다(PHP 제외).

이러한 구독 유형에 대한 자세한 내용은 구독 유형 선택을 참조하세요.

다음 섹션에서는 각 구독자 클라이언트 유형별로 순서대로 메시지 수신이 의미하는 바를 설명합니다.

StreamingPull 구독자 클라이언트

streamingPull과 함께 클라이언트 라이브러리를 사용하는 경우 구독자 클라이언트가 메시지를 수신할 때마다 실행되는 사용자 콜백을 지정해야 합니다. 클라이언트 라이브러리를 사용하면 지정된 순서 키에 대해 콜백이 올바른 순서로 메시지에 완료될 때까지 실행됩니다. 해당 콜백 내에서 메시지가 확인되면 메시지에 대한 모든 계산이 순서대로 수행됩니다. 그러나 사용자 콜백이 메시지에 다른 비동기 작업을 예약하는 경우 구독자 클라이언트는 비동기 작업이 순서대로 수행되도록 해야 합니다. 한 가지 옵션은 순서대로 처리되는 로컬 작업 큐에 메시지를 추가하는 것입니다.

풀 구독자 클라이언트

풀 구독에 연결된 구독자 클라이언트의 경우 Pub/Sub 메시지 순서 지정은 다음을 지원합니다.

  • PullResponse의 순서 키에 대한 모든 메시지는 목록에서 올바른 순서로 되어 있습니다.

  • 순서 키에 대해 한 번에 하나의 메시지 배치만 대기 상태일 수 있습니다.

Pub/Sub 서비스가 구독자의 pull 요청에 대해 전송하는 응답의 성공 또는 지연 시간을 보장할 수 없으므로 순서가 지정된 전송을 유지하려면 한 번에 하나의 메시지 배치만 대기 상태일 수 있어야 합니다.

푸시 구독자 클라이언트

푸시에 대한 제한은 풀에 비해 훨씬 더 엄격합니다. 푸시 구독의 경우 Pub/Sub는 각 순서 키에 대해 한 번에 하나의 대기 메시지만 지원합니다. 각 메시지는 별도의 요청으로 푸시 엔드포인트에 전송됩니다. 따라서 동시에 요청을 전송하는 것은 동일한 순서 키에 대해 여러 개의 메시지 배치를 동시에 보내 구독자를 동시에 가져오는 것과 동일한 문제가 발생합니다. 푸시 구독은 같은 순서 키로 메시지가 자주 게시되거나 지연 시간이 매우 중요한 주제에는 적합하지 않을 수 있습니다.

내보내기 구독자 클라이언트

내보내기 구독은 순서가 지정된 메시지를 지원합니다. BigQuery 구독의 경우 순서 키가 동일한 메시지가 BigQuery 테이블에 순서대로 기록됩니다. Cloud Storage 구독의 경우 순서 키가 동일한 메시지가 모두 동일한 파일에 기록되지 않을 수 있습니다. 같은 파일 내에 있으면 순서 키의 메시지가 순서대로 표시됩니다. 여러 파일에 분산되면 순서 키의 이후 메시지는 이전 메시지가 포함된 파일 이름의 타임스탬프보다 이전 타임스탬프 이름을 가진 파일에 나타날 수 있습니다.

메시지 순서 지정 사용 설정

메시지를 순서대로 받으려면 메시지를 수신하는 구독에서 메시지 순서 속성을 설정하세요. 메시지를 순서대로 수신하면 지연 시간이 늘어날 수 있습니다. 구독을 만든 후에는 메시지 순서 속성을 변경할 수 없습니다.

Google Cloud 콘솔, Google Cloud CLI, Pub/Sub API를 사용하여 구독을 만들 때 메시지 순서 속성을 설정할 수 있습니다.

콘솔

메시지 순서 속성을 설정한 구독을 만들려면 다음 단계를 따르세요.

  1. Google Cloud 콘솔에서 구독 페이지로 이동합니다.

구독 페이지로 이동

  1. 구독 만들기를 클릭합니다.

  2. 구독 ID를 입력합니다.

  3. 메시지를 수신할 주제를 선택합니다.

  4. 메시지 순서 섹션에서 순서 키로 메시지 순서 지정을 선택합니다.

  5. 만들기를 클릭합니다.

gcloud

메시지 순서 속성을 설정한 구독을 만들려면 gcloud pubsub subscriptions create 명령어와 --enable-message-ordering 플래그를 사용합니다.

gcloud pubsub subscriptions create SUBSCRIPTION_ID \
  --enable-message-ordering

SUBSCRIPTION_ID를 구독 ID로 바꿉니다.

요청이 성공하면 명령줄에 확인 메시지가 표시됩니다.

Created subscription [SUBSCRIPTION_ID].

REST

메시지 순서 속성을 설정하여 구독을 만들려면 다음과 같이 PUT 요청을 보냅니다.

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

다음을 바꿉니다.

  • PROJECT_ID: 주제가 있는 프로젝트의 프로젝트 ID
  • SUBSCRIPTION_ID: 구독의 ID

요청 본문에서 다음을 지정합니다.

{
  "topic": TOPIC_ID,
  "enableMessageOrdering": true,
}

TOPIC_ID를 구독에 연결할 주제의 ID로 바꿉니다.

요청이 성공하는 경우 응답은 JSON 형식의 구독입니다.

{
  "name": projects/PROJECT_ID/subscriptions/SUBSCRIPTION_ID,
  "topic": projects/PROJECT_ID/topics/TOPIC_ID,
  "enableMessageOrdering": true,
}

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_message_ordering(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 CreateSubscriptionWithOrderingSample
{
    public Subscription CreateSubscriptionWithOrdering(string projectId, string topicId, string subscriptionId)
    {
        SubscriberServiceApiClient subscriber = SubscriberServiceApiClient.Create();
        var topicName = TopicName.FromProjectTopic(projectId, topicId);
        var subscriptionName = SubscriptionName.FromProjectSubscription(projectId, subscriptionId);

        var subscriptionRequest = new Subscription
        {
            SubscriptionName = subscriptionName,
            TopicAsTopicName = topicName,
            EnableMessageOrdering = 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 Go API 참고 문서를 참조하세요.

import (
	"context"
	"fmt"
	"io"
	"time"

	"cloud.google.com/go/pubsub"
)

func createWithOrdering(w io.Writer, projectID, subID string, topic *pubsub.Topic) error {
	// projectID := "my-project-id"
	// subID := "my-sub"
	// topic of type https://godoc.org/cloud.google.com/go/pubsub#Topic
	ctx := context.Background()
	client, err := pubsub.NewClient(ctx, projectID)
	if err != nil {
		return fmt.Errorf("pubsub.NewClient: %w", err)
	}
	defer client.Close()

	// Message ordering can only be set when creating a subscription.
	sub, err := client.CreateSubscription(ctx, subID, pubsub.SubscriptionConfig{
		Topic:                 topic,
		AckDeadline:           20 * time.Second,
		EnableMessageOrdering: true,
	})
	if err != nil {
		return fmt.Errorf("CreateSubscription: %w", err)
	}
	fmt.Fprintf(w, "Created subscription: %v\n", sub)
	return nil
}

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 CreateSubscriptionWithOrdering {
  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";

    createSubscriptionWithOrderingExample(projectId, topicId, subscriptionId);
  }

  public static void createSubscriptionWithOrderingExample(
      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())
                  // Set message ordering to true for ordered messages in the subscription.
                  .setEnableMessageOrdering(true)
                  .build());

      System.out.println("Created a subscription with ordering: " + subscription.getAllFields());
    }
  }
}

Node.js

이 샘플을 시도하기 전에 빠른 시작: 클라이언트 라이브러리 사용의 Node.js 설정 안내를 따르세요. 자세한 내용은 Pub/Sub Node.js API 참고 문서를 참조하세요.

/**
 * TODO(developer): Uncomment these variables before running the sample.
 */
// const topicNameOrId = 'YOUR_TOPIC_NAME_OR_ID';
// const subscriptionNameOrId = 'YOUR_SUBSCRIPTION_NAME_OR_ID';

// Imports the Google Cloud client library
const {PubSub} = require('@google-cloud/pubsub');

// Creates a client; cache this for further use
const pubSubClient = new PubSub();

async function createSubscriptionWithOrdering(
  topicNameOrId,
  subscriptionNameOrId
) {
  // Creates a new subscription
  await pubSubClient
    .topic(topicNameOrId)
    .createSubscription(subscriptionNameOrId, {
      enableMessageOrdering: true,
    });
  console.log(
    `Created subscription ${subscriptionNameOrId} with ordering enabled.`
  );
  console.log(
    'To process messages in order, remember to add an ordering key to your messages.'
  );
}

Node.js

이 샘플을 시도하기 전에 빠른 시작: 클라이언트 라이브러리 사용의 Node.js 설정 안내를 따르세요. 자세한 내용은 Pub/Sub Node.js API 참고 문서를 참조하세요.

/**
 * TODO(developer): Uncomment these variables before running the sample.
 */
// const topicNameOrId = 'YOUR_TOPIC_NAME_OR_ID';
// const subscriptionNameOrId = 'YOUR_SUBSCRIPTION_NAME_OR_ID';

// Imports the Google Cloud client library
import {PubSub} from '@google-cloud/pubsub';

// Creates a client; cache this for further use
const pubSubClient = new PubSub();

async function createSubscriptionWithOrdering(
  topicNameOrId: string,
  subscriptionNameOrId: string
) {
  // Creates a new subscription
  await pubSubClient
    .topic(topicNameOrId)
    .createSubscription(subscriptionNameOrId, {
      enableMessageOrdering: true,
    });
  console.log(
    `Created subscription ${subscriptionNameOrId} with ordering enabled.`
  );
  console.log(
    'To process messages in order, remember to add an ordering key to your messages.'
  );
}

Python

이 샘플을 시도하기 전에 빠른 시작: 클라이언트 라이브러리 사용의 Python 설정 안내를 따르세요. 자세한 내용은 Pub/Sub Python API 참고 문서를 참조하세요.

from google.cloud import pubsub_v1

# TODO(developer): Choose an existing topic.
# project_id = "your-project-id"
# topic_id = "your-topic-id"
# subscription_id = "your-subscription-id"

publisher = pubsub_v1.PublisherClient()
subscriber = pubsub_v1.SubscriberClient()
topic_path = publisher.topic_path(project_id, topic_id)
subscription_path = subscriber.subscription_path(project_id, subscription_id)

with subscriber:
    subscription = subscriber.create_subscription(
        request={
            "name": subscription_path,
            "topic": topic_path,
            "enable_message_ordering": True,
        }
    )
    print(f"Created subscription with ordering: {subscription}")

Ruby

이 샘플을 시도하기 전에 빠른 시작: 클라이언트 라이브러리 사용의 Ruby 설정 안내를 따르세요. 자세한 내용은 Pub/Sub Ruby API 참고 문서를 참조하세요.

# topic_id        = "your-topic-id"
# subscription_id = "your-subscription-id"

pubsub = Google::Cloud::Pubsub.new

topic        = pubsub.topic topic_id
subscription = topic.subscribe subscription_id,
                               message_ordering: true

puts "Pull subscription #{subscription_id} created with message ordering."

다음 단계