Ordenar mensajes

La ordenación de mensajes es una función de Pub/Sub que te permite recibir mensajes en tus clientes suscriptores en el orden en el que los publicaron los clientes editores.

Por ejemplo, supongamos que un cliente editor de una región publica los mensajes 1, 2 y 3 en ese orden. Con la ordenación de mensajes, el cliente suscriptor recibe los mensajes publicados en el mismo orden. Para que se entreguen en orden, el cliente del editor debe publicar los mensajes en la misma región. Sin embargo, los suscriptores pueden conectarse a cualquier región y la garantía de pedido se mantiene.

El orden de los mensajes es una función útil en situaciones como la captura de cambios en bases de datos, el seguimiento de sesiones de usuarios y las aplicaciones de streaming en las que es importante conservar la cronología de los eventos.

En esta página se explica el concepto de orden de los mensajes y cómo configurar los clientes de suscriptor para que reciban los mensajes en orden. Para configurar tus clientes editor para ordenar los mensajes, consulta Usar claves de ordenación para publicar un mensaje.

Descripción general de la ordenación de mensajes

El orden en Pub/Sub se determina de la siguiente manera:

  • Clave de orden: es una cadena que se usa en los metadatos de los mensajes de Pub/Sub y representa la entidad para la que se deben ordenar los mensajes. La clave de ordenación puede tener una longitud de hasta 1 KB. Para recibir un conjunto de mensajes ordenados en una región, debes publicar todos los mensajes con la misma clave de ordenación en la misma región. Algunos ejemplos de claves de ordenación son los IDs de cliente y la clave principal de una fila de una base de datos.

    El rendimiento de publicación de cada clave de ordenación está limitado a 1 MBps. El rendimiento de todas las claves de ordenación de un tema está limitado a la cuota disponible en una región de publicación. Este límite se puede aumentar hasta muchas unidades de GBps.

    Una clave de ordenación no es lo mismo que una partición en un sistema de mensajería basado en particiones, ya que se espera que las claves de ordenación tengan una cardinalidad mucho mayor que las particiones.

  • Habilitar la ordenación de mensajes: se trata de un ajuste de la suscripción. Cuando se habilita la ordenación de mensajes en una suscripción, los clientes suscriptores reciben los mensajes publicados en la misma región con la misma clave de ordenación en el orden en el que los haya recibido el servicio. Debes habilitar este ajuste en la suscripción.

    Supongamos que tienes dos suscripciones, A y B, asociadas al mismo tema T. La suscripción A se ha configurado con el orden de los mensajes habilitado y la suscripción B se ha configurado sin el orden de los mensajes habilitado. En esta arquitectura, las suscripciones A y B reciben el mismo conjunto de mensajes del tema T. Si publicas mensajes con claves de ordenación en la misma región, la suscripción A recibe los mensajes en el orden en el que se publicaron. En cambio, la suscripción B recibe los mensajes sin ningún orden concreto.

Por lo general, si tu solución requiere que los clientes editores envíen mensajes ordenados y no ordenados, crea temas independientes: uno para los mensajes ordenados y otro para los no ordenados.

Consideraciones al usar la mensajería ordenada

La siguiente lista contiene información importante sobre el comportamiento de los mensajes ordenados en Pub/Sub:

  • Ordenación con la misma clave: los mensajes publicados con la misma clave de ordenación se reciben en el orden en el que se publicaron. Supongamos que, para la clave de ordenación A, publicas los mensajes 1, 2 y 3. Si el orden está habilitado, se espera que el pedido 1 se entregue antes que el 2 y que el 2 se entregue antes que el 3.

  • Ordenación entre claves: no se espera que los mensajes publicados con diferentes claves de ordenación se reciban en orden. Supongamos que tienes las teclas de ordenación A y B. En el caso de la clave de ordenación A, los mensajes 1 y 2 se publican en orden. En el caso de la clave de ordenación B, los mensajes 3 y 4 se publican en orden. Sin embargo, el mensaje 1 podría llegar antes o después del mensaje 4.

  • Reenvío de mensajes: Pub/Sub envía cada mensaje al menos una vez, por lo que el servicio Pub/Sub puede volver a enviar mensajes. Si se vuelve a enviar un mensaje, se volverán a enviar todos los mensajes posteriores de esa clave, incluso los que se hayan confirmado. Supongamos que un cliente suscriptor recibe los mensajes 1, 2 y 3 de una clave de ordenación específica. Si el mensaje 2 se vuelve a enviar (porque ha caducado el plazo de confirmación o porque la confirmación de mejor esfuerzo no se ha conservado en Pub/Sub), el mensaje 3 también se vuelve a enviar. Si tanto el orden de los mensajes como un tema de mensajes fallidos están habilitados en una suscripción, es posible que este comportamiento no se cumpla, ya que Pub/Sub reenvía los mensajes a los temas de mensajes fallidos en la medida de lo posible.

  • Retrasos en las confirmaciones y temas de mensajes fallidos: los mensajes no confirmados de una clave de ordenación determinada pueden retrasar el envío de mensajes de otras claves de ordenación, sobre todo durante los reinicios del servidor o los cambios en el tráfico. Para mantener el orden en estos eventos, asegúrate de confirmar la recepción de todos los mensajes a tiempo. Si no es posible enviar una confirmación a tiempo, considera la posibilidad de usar un tema de mensajes fallidos para evitar que los mensajes se retengan indefinidamente. Ten en cuenta que es posible que no se conserve el orden cuando se escriban mensajes en un tema de mensajes fallidos.

  • Afinidad de mensajes (clientes de streamingPull): los mensajes con la misma clave se suelen enviar al mismo cliente suscriptor de streamingPull. Se espera la afinidad cuando hay mensajes pendientes de una clave de ordenación para un cliente suscriptor específico. Si no hay mensajes pendientes, la afinidad puede cambiar para equilibrar la carga o si se desconecta el cliente.

    Para que el procesamiento se realice sin problemas incluso si se producen cambios en la afinidad, es fundamental diseñar la aplicación streamingPull de forma que pueda gestionar mensajes en cualquier cliente para una clave de ordenación determinada.

  • Integración con Dataflow: no habilites el orden de los mensajes en las suscripciones al configurar Dataflow con Pub/Sub. Dataflow tiene su propio mecanismo para ordenar los mensajes por completo, lo que garantiza el orden cronológico de todos los mensajes como parte de las operaciones de ventanas. Este método de ordenación es diferente del enfoque basado en claves de ordenación de Pub/Sub. Usar claves de ordenación con Dataflow puede reducir el rendimiento de la canalización.

  • Escalado automático: la entrega ordenada de Pub/Sub se adapta a miles de millones de claves de ordenación. Un mayor número de claves de ordenación permite una entrega paralela a los suscriptores, ya que la ordenación se aplica a todos los mensajes que tienen la misma clave de ordenación.

  • Compensaciones de rendimiento: la entrega ordenada conlleva algunas compensaciones. En comparación con la entrega sin orden, la entrega ordenada reduce la disponibilidad de publicación y aumenta la latencia de entrega de mensajes de extremo a extremo. En el caso de la entrega ordenada, la conmutación por error requiere coordinación para asegurarse de que los mensajes se escriban y se lean en el orden correcto.

  • Clave de acceso: cuando se usa la ordenación de mensajes, todos los mensajes con la misma clave de ordenación se envían al cliente suscriptor en el orden en el que los recibe el servicio. La retrollamada del usuario no se ejecuta hasta que se completa la retrollamada del mensaje anterior. El rendimiento máximo de los mensajes que comparten la misma clave de ordenación al enviarse a los suscriptores no está limitado por Pub/Sub , sino por la velocidad de procesamiento del cliente suscriptor. Una clave activa se produce cuando se acumula un backlog en una clave de ordenación individual porque el número de mensajes producidos por segundo supera el número de mensajes que el suscriptor puede procesar por segundo. Para mitigar las claves activas, usa las claves más granulares que puedas y minimiza el tiempo de procesamiento por mensaje. También puede monitorizar la métrica subscription/oldest_unacked_message_age para detectar un valor creciente, que podría indicar una tecla de acceso rápido.

Para obtener más información sobre cómo usar el orden de los mensajes, consulta los siguientes temas sobre prácticas recomendadas:

Comportamiento del cliente suscriptor para ordenar mensajes

Los clientes de suscriptor reciben los mensajes en el orden en el que se publicaron en una región específica. Pub/Sub admite diferentes formas de recibir mensajes, como clientes suscriptores conectados a suscripciones pull y push. Las bibliotecas de cliente usan streamingPull (excepto PHP).

Para obtener más información sobre estos tipos de suscripción, consulta Elegir un tipo de suscripción.

En las siguientes secciones se explica qué significa recibir mensajes en orden para cada tipo de cliente de suscriptor.

Clientes suscriptores de StreamingPull

Cuando se usan las bibliotecas de cliente con streamingPull, se debe especificar una retrollamada de usuario que se ejecute cada vez que un cliente suscriptor reciba un mensaje. Con las bibliotecas de cliente, para cualquier clave de ordenación, la retrollamada se ejecuta hasta completarse en los mensajes en el orden correcto. Si los mensajes se confirman en esa retrollamada, todos los cálculos de un mensaje se realizan en orden. Sin embargo, si la retrollamada del usuario programa otro trabajo asíncrono en los mensajes, el cliente del suscriptor debe asegurarse de que el trabajo asíncrono se realice en orden. Una opción es añadir mensajes a una cola de trabajo local que se procesa por orden.

Clientes de suscriptor de extracción

En el caso de los clientes suscriptores conectados a suscripciones de extracción, el orden de los mensajes de Pub/Sub admite lo siguiente:

  • Todos los mensajes de una clave de ordenación en PullResponse están en el orden correcto en la lista.

  • Solo puede haber un lote de mensajes pendiente para una clave de ordenación a la vez.

El requisito de que solo pueda haber un lote de mensajes pendiente a la vez es necesario para mantener la entrega ordenada, ya que el servicio Pub/Sub no puede asegurar el éxito ni la latencia de la respuesta que envía a la solicitud de extracción de un suscriptor.

Clientes suscriptores de inserción

Las restricciones de las notificaciones push son aún más estrictas que las de las notificaciones pull. En el caso de las suscripciones push, Pub/Sub solo admite un mensaje pendiente por cada clave de ordenación a la vez. Cada mensaje se envía a un endpoint de envío como una solicitud independiente. Por lo tanto, enviar las solicitudes en paralelo tendría el mismo problema que enviar varios lotes de mensajes con la misma clave de ordenación para extraer suscriptores simultáneamente. Las suscripciones push pueden no ser una buena opción para los temas en los que los mensajes se publican con frecuencia con la misma clave de ordenación o en los que la latencia es extremadamente importante.

Exportar clientes de suscriptor

Las exportaciones de suscripciones admiten mensajes ordenados. En el caso de las suscripciones de BigQuery, los mensajes con la misma clave de ordenación se escriben en su tabla de BigQuery en orden. En las suscripciones de Cloud Storage, es posible que no todos los mensajes con la misma clave de ordenación se escriban en el mismo archivo. Cuando están en el mismo archivo, los mensajes de una clave de ordenación están ordenados. Cuando se distribuyen en varios archivos, los mensajes posteriores de una clave de ordenación pueden aparecer en un archivo con un nombre que tenga una marca de tiempo anterior a la marca de tiempo del nombre del archivo con los mensajes anteriores.

Habilitar pedidos por mensaje

Para recibir los mensajes en orden, define la propiedad de ordenación de mensajes en la suscripción de la que recibes los mensajes. Recibir mensajes en orden puede aumentar la latencia. No puedes cambiar la propiedad de ordenación de mensajes después de crear una suscripción.

Puedes definir la propiedad de ordenación de mensajes al crear una suscripción con la Google Cloud consola, la CLI de Google Cloud o la API Pub/Sub.

Consola

Para crear una suscripción con la propiedad de ordenación de mensajes, sigue estos pasos:

  1. En la Google Cloud consola, ve a la página Suscripciones.

Ir a Suscripciones

  1. Haz clic en Crear suscripción.

  2. Introduce un ID de suscripción.

  3. Elige un tema del que quieras recibir mensajes.

  4. En la sección Orden de los mensajes, selecciona Ordenar mensajes con una clave de ordenación.

  5. Haz clic en Crear.

gcloud

Para crear una suscripción con la propiedad de orden de los mensajes, usa el comando gcloud pubsub subscriptions create y la marca --enable-message-ordering:

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

Sustituye SUBSCRIPTION_ID por el ID de la suscripción.

Si la solicitud se realiza correctamente, la línea de comandos muestra una confirmación:

Created subscription [SUBSCRIPTION_ID].

REST

Para crear una suscripción con la propiedad de orden de los mensajes, envía una solicitud PUT como la siguiente:

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

Haz los cambios siguientes:

  • PROJECT_ID: el ID del proyecto que contiene el tema
  • SUBSCRIPTION_ID: el ID de la suscripción

En el cuerpo de la solicitud, especifica lo siguiente:

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

Sustituye TOPIC_ID por el ID del tema que quieras asociar a la suscripción.

Si la solicitud se realiza correctamente, la respuesta es la suscripción en formato JSON:

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

C++

Antes de probar este ejemplo, sigue las instrucciones de configuración de C++ que se indican en la guía de inicio rápido sobre cómo usar bibliotecas de cliente. Para obtener más información, consulta la documentación de referencia de la API de C++ de Pub/Sub.

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#

Antes de probar este ejemplo, sigue las instrucciones de configuración de C# que se indican en la guía de inicio rápido sobre cómo usar bibliotecas de cliente. Para obtener más información, consulta la documentación de referencia de la API de C# de Pub/Sub.


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

En el siguiente ejemplo se usa la versión principal de la biblioteca de cliente de Pub/Sub de Go (v2). Si sigues usando la biblioteca v1, consulta la guía de migración a la versión 2. Para ver una lista de ejemplos de código de la versión 1, consulta los ejemplos de código obsoletos.

Antes de probar este ejemplo, sigue las instrucciones de configuración de Go que se indican en la guía de inicio rápido sobre cómo usar bibliotecas de cliente. Para obtener más información, consulta la documentación de referencia de la API Go de Pub/Sub.

import (
	"context"
	"fmt"
	"io"

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

func createWithOrdering(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()

	// Message ordering can only be set when creating a subscription.
	sub, err := client.SubscriptionAdminClient.CreateSubscription(ctx, &pubsubpb.Subscription{
		Name:                  subscription,
		Topic:                 topic,
		EnableMessageOrdering: true,
	})
	if err != nil {
		return fmt.Errorf("CreateSubscription: %w", err)
	}
	fmt.Fprintf(w, "Created subscription: %v\n", sub)
	return nil
}

Java

Antes de probar este ejemplo, sigue las instrucciones de configuración de Java que se indican en la guía de inicio rápido sobre cómo usar bibliotecas de cliente. Para obtener más información, consulta la documentación de referencia de la API de Java de Pub/Sub.

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

Antes de probar este ejemplo, sigue las instrucciones de configuración de Node.js que se indican en la guía de inicio rápido sobre cómo usar bibliotecas de cliente. Para obtener más información, consulta la documentación de referencia de la API de Node.js de Pub/Sub.

/**
 * 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

Antes de probar este ejemplo, sigue las instrucciones de configuración de Node.js que se indican en la guía de inicio rápido sobre cómo usar bibliotecas de cliente. Para obtener más información, consulta la documentación de referencia de la API de Node.js de Pub/Sub.

/**
 * 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

Antes de probar este ejemplo, sigue las instrucciones de configuración de Python que se indican en la guía de inicio rápido sobre cómo usar bibliotecas de cliente. Para obtener más información, consulta la documentación de referencia de la API de Python de Pub/Sub.

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

En el siguiente ejemplo se usa la biblioteca de cliente de Ruby Pub/Sub v3. Si sigues usando la biblioteca v2, consulta la guía de migración a la versión 3. Para ver una lista de ejemplos de código de Ruby v2, consulta los ejemplos de código obsoletos.

Antes de probar este ejemplo, sigue las instrucciones de configuración de Ruby que se indican en la guía de inicio rápido sobre cómo usar bibliotecas de cliente. Para obtener más información, consulta la documentación de referencia de la API de Ruby de Pub/Sub.

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

pubsub = Google::Cloud::PubSub.new
subscription_admin = pubsub.subscription_admin

subscription = subscription_admin.create_subscription \
  name: pubsub.subscription_path(subscription_id),
  topic: pubsub.topic_path(topic_id),
  enable_message_ordering: true

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

Siguientes pasos