Enviar solicitudes por lotes

En este documento se muestra cómo hacer llamadas a la API JSON en lote para reducir el número de conexiones HTTP que tiene que hacer tu cliente al acceder a Cloud Storage.

Información general

Las conexiones HTTP que hace tu cliente generan un determinado volumen de sobrecarga. La API JSON de Cloud Storage admite el procesamiento por lotes para permitir que tu cliente incluya varias llamadas a la API en una sola solicitud HTTP.

Ejemplos de situaciones en las que puede ser útil usar el procesamiento por lotes:

  • Actualizar los metadatos de muchos objetos, como los permisos.
  • Eliminar muchos objetos.

En cada caso, en lugar de enviar cada llamada por separado, puedes agruparlas en una sola solicitud HTTP. Todas las solicitudes internas deben dirigirse a la API JSON de Cloud Storage.

No debe incluir más de 100 llamadas en una sola solicitud en lote. Si necesitas hacer más llamadas, usa varias solicitudes por lotes. La carga útil total de la solicitud por lotes debe ser inferior a 10 MB.

Detalles del lote

Una solicitud por lotes consta de varias llamadas a la API combinadas en una solicitud HTTP, que puede enviarse al endpoint por lotes de Cloud Storage, que es https://storage.googleapis.com/batch/storage/v1. En esta sección se ofrece una descripción detallada de la sintaxis del lote y más adelante se incluye un ejemplo.

Formato de una solicitud en lote

Una solicitud por lotes es una solicitud HTTP estándar que contiene varias llamadas a la API JSON de Cloud Storage. Esta solicitud principal usa el tipo de contenido multipart/mixed. Dentro de la solicitud HTTP principal, hay varias partes, cada una de las cuales contiene una solicitud HTTP anidada.

Cada parte empieza con su propio encabezado HTTP Content-Type: application/http. La parte también puede tener un encabezado Content-ID opcional. Estos encabezados marcan el inicio de la parte, pero son independientes de la solicitud HTTP anidada. Esto significa que, después de que el servidor desglose la solicitud por lotes en solicitudes independientes, se ignoran los encabezados de las partes.

El cuerpo de cada parte es una solicitud HTTP completa, con su propio verbo, URL, encabezados y cuerpo. Estas solicitudes HTTP solo deben contener la parte de la ruta de la URL. Las URLs completas pueden tener un comportamiento indefinido.

Los encabezados HTTP de la solicitud en lote externa, excepto los encabezados Content-, como Content-Type, también se aplican a todas las solicitudes anidadas. Sin embargo, si especificas un encabezado HTTP en la solicitud externa y en una solicitud anidada, el valor del encabezado de la solicitud anidada sustituirá al valor del encabezado de la solicitud por lotes externa en esa solicitud específica.

Por ejemplo, si proporciona un encabezado Authorization para una solicitud anidada específica, ese encabezado solo se aplicará a la solicitud que lo haya especificado. Si proporciona un encabezado Authorization para la solicitud externa, ese encabezado se aplicará a todas las solicitudes anidadas, a menos que lo anulen con un encabezado Authorization propio.

Cuando Cloud Storage recibe la solicitud por lotes, aplica los parámetros de consulta y los encabezados de la solicitud externa (según corresponda) a cada parte y, a continuación, trata cada parte como si fuera una solicitud HTTP independiente.

Respuesta a una solicitud en lote

La respuesta de Cloud Storage es una única respuesta HTTP estándar con un tipo de contenido multipart/mixed. Cada parte de esta respuesta principal es la respuesta a una de las solicitudes de la solicitud por lotes. El orden de las respuestas es el mismo que el de las solicitudes.

Al igual que todas las partes de una solicitud, cada parte de la respuesta contiene una respuesta HTTP completa, incluido un código de estado, encabezados y un cuerpo. Al igual que las partes de la solicitud, cada parte de la respuesta va precedida de un encabezado Content-Type que marca el inicio de la parte. Para obtener más información sobre los códigos de estado, consulta Códigos de estado y de error HTTP de la API JSON de Cloud Storage.

Si una parte determinada de la solicitud tenía un encabezado Content-ID, la parte correspondiente de la respuesta tiene un encabezado Content-ID coincidente. El encabezado Content-ID de la respuesta empieza por response-, seguido del Content-ID usado en la solicitud, como se muestra en el ejemplo.

Ejemplo

En el siguiente ejemplo de lote se actualizan los metadatos personalizados de tres objetos de example-bucket.

Ejemplo de solicitud HTTP por lotes

HTTP

POST /batch/storage/v1 HTTP/1.1
Host: storage.googleapis.com
Content-Length: 960
Content-Type: multipart/mixed; boundary="===============7330845974216740156=="
Authorization: Bearer ya29.AHES6ZRVmB7fkLtd1XTmq6mo0S1wqZZi3-Lh_s-6Uw7p8vtgSwg

--===============7330845974216740156==
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: <b29c5de2-0db4-490b-b421-6a51b598bd22+1>

PATCH /storage/v1/b/example-bucket/o/obj1 HTTP/1.1
Content-Type: application/json
accept: application/json
content-length: 31

{"metadata": {"type": "tabby"}}
--===============7330845974216740156==
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: <b29c5de2-0db4-490b-b421-6a51b598bd22+2>

PATCH /storage/v1/b/example-bucket/o/obj2 HTTP/1.1
Content-Type: application/json
accept: application/json
content-length: 32

{"metadata": {"type": "tuxedo"}}
--===============7330845974216740156==
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: <b29c5de2-0db4-490b-b421-6a51b598bd22+3>

PATCH /storage/v1/b/example-bucket/o/obj3 HTTP/1.1
Content-Type: application/json
accept: application/json
content-length: 32

{"metadata": {"type": "calico"}}
--===============7330845974216740156==--

Bibliotecas de cliente

C++

La biblioteca cliente de C++ no admite solicitudes por lotes.

C#

La biblioteca cliente de C# no admite solicitudes por lotes.

Go

La biblioteca cliente de Go no admite solicitudes por lotes.

Java

Para obtener más información, consulta la documentación de referencia de la API Java de Cloud Storage.

import com.google.api.gax.paging.Page;
import com.google.cloud.storage.Blob;
import com.google.cloud.storage.BlobInfo;
import com.google.cloud.storage.Storage;
import com.google.cloud.storage.StorageBatch;
import com.google.cloud.storage.StorageBatchResult;
import com.google.cloud.storage.StorageException;
import com.google.cloud.storage.StorageOptions;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

public class BatchSetObjectMetadata {
  public static void batchSetObjectMetadata(
      String projectId, String bucketName, String pathPrefix) {
    // The ID of your GCP project
    // String projectId = "your-project-id";

    // The ID of your GCS bucket
    // String bucketName = "your-unique-bucket-name";

    // The directory prefix. All objects in the bucket with this prefix will have their metadata
    // updated
    // String pathPrefix = "yourPath/";

    Storage storage = StorageOptions.newBuilder().setProjectId(projectId).build().getService();
    Map<String, String> newMetadata = new HashMap<>();
    newMetadata.put("keyToAddOrUpdate", "value");
    Page<Blob> blobs =
        storage.list(
            bucketName,
            Storage.BlobListOption.prefix(pathPrefix),
            Storage.BlobListOption.delimiter("/"));
    StorageBatch batchRequest = storage.batch();

    // Add all blobs with the given prefix to the batch request
    List<StorageBatchResult<Blob>> batchResults =
        blobs
            .streamAll()
            .map(blob -> batchRequest.update(blob.toBuilder().setMetadata(newMetadata).build()))
            .collect(Collectors.toList());

    // Execute the batch request
    batchRequest.submit();
    List<StorageException> failures =
        batchResults.stream()
            .map(
                r -> {
                  try {
                    BlobInfo blob = r.get();
                    return null;
                  } catch (StorageException e) {
                    return e;
                  }
                })
            .filter(Objects::nonNull)
            .collect(Collectors.toList());

    System.out.println(
        (batchResults.size() - failures.size())
            + " blobs in bucket "
            + bucketName
            + " with prefix '"
            + pathPrefix
            + "' had their metadata updated successfully.");

    if (!failures.isEmpty()) {
      System.out.println("While processing, there were " + failures.size() + " failures");

      for (StorageException failure : failures) {
        failure.printStackTrace(System.out);
      }
    }
  }
}

Node.js

La biblioteca de cliente de Node.js no admite solicitudes por lotes.

PHP

La biblioteca cliente de PHP no admite solicitudes por lotes.

Python

Para obtener más información, consulta la documentación de referencia de la API de Python de Cloud Storage.


from google.cloud import storage


def batch_request(bucket_name, prefix=None):
    """
    Use a batch request to patch a list of objects with the given prefix in a bucket.

    Note that Cloud Storage does not support batch operations for uploading or downloading.
    Additionally, the current batch design does not support library methods whose return values
    depend on the response payload.
    See https://cloud.google.com/python/docs/reference/storage/latest/google.cloud.storage.batch
    """
    # The ID of your GCS bucket
    # bucket_name = "my-bucket"
    # The prefix of the object paths
    # prefix = "directory-prefix/"

    client = storage.Client()
    bucket = client.bucket(bucket_name)

    # Accumulate in a list the objects with a given prefix.
    blobs_to_patch = [blob for blob in bucket.list_blobs(prefix=prefix)]

    # Use a batch context manager to edit metadata in the list of blobs.
    # The batch request is sent out when the context manager closes.
    # No more than 100 calls should be included in a single batch request.
    with client.batch():
        for blob in blobs_to_patch:
            metadata = {"your-metadata-key": "your-metadata-value"}
            blob.metadata = metadata
            blob.patch()

    print(
        f"Batch request edited metadata for all objects with the given prefix in {bucket.name}."
    )

Ruby

Para saber cómo hacer una solicitud por lotes con Ruby, consulta la documentación de referencia de la API de Ruby de Cloud Storage.

Ejemplo de respuesta HTTP en lote

Esta es la respuesta a la solicitud HTTP de ejemplo de la sección anterior.

HTTP/1.1 200 OK
Content-Type: multipart/mixed; boundary=batch_pK7JBAk73-E=_AA5eFwv4m2Q=
Date: Mon, 22 Jan 2018 18:56:00 GMT
Expires: Mon, 22 Jan 2018 18:56:00 GMT
Cache-Control: private, max-age=0
Content-Length: 3767

--batch_pK7JBAk73-E=_AA5eFwv4m2Q=
Content-Type: application/http
Content-ID: <response-b29c5de2-0db4-490b-b421-6a51b598bd22+1>

HTTP/1.1 200 OK
ETag: "lGaP-E0memYDumK16YuUDM_6Gf0/V43j6azD55CPRGb9b6uytDYl61Y"
Content-Type: application/json; charset=UTF-8
Date: Mon, 22 Jan 2018 18:56:00 GMT
Expires: Mon, 22 Jan 2018 18:56:00 GMT
Cache-Control: private, max-age=0
Content-Length: 846

{
 "kind": "storage#object",
 "id": "example-bucket/obj1/1495822576643790",
 .
 .
 .
 "metadata": {
  "type": "tabby"
  },
  .
  .
  .
}

--batch_pK7JBAk73-E=_AA5eFwv4m2Q=
Content-Type: application/http
Content-ID: <response-b29c5de2-0db4-490b-b421-6a51b598bd22+2>

HTTP/1.1 200 OK
ETag: "lGaP-E0memYDumK16YuUDM_6Gf0/91POdd-sxSAkJnS8Dm7wMxBSDKk"
Content-Type: application/json; charset=UTF-8
Date: Mon, 22 Jan 2018 18:56:00 GMT
Expires: Mon, 22 Jan 2018 18:56:00 GMT
Cache-Control: private, max-age=0
Content-Length: 846

{
 "kind": "storage#object",
 "id": "example-bucket/obj2/1495822576643790",
 .
 .
 .
 "metadata": {
  "type": "tuxedo"
  },
  .
  .
  .
}

--batch_pK7JBAk73-E=_AA5eFwv4m2Q=
Content-Type: application/http
Content-ID: <response-b29c5de2-0db4-490b-b421-6a51b598bd22+3>

HTTP/1.1 200 OK
ETag: "lGaP-E0memYDumK16YuUDM_6Gf0/d2Z1F1_ZVbB1dC0YKM9rX5VAgIQ"
Content-Type: application/json; charset=UTF-8
Date: Mon, 22 Jan 2018 18:56:00 GMT
Expires: Mon, 22 Jan 2018 18:56:00 GMT
Cache-Control: private, max-age=0
Content-Length: 846

{
 "kind": "storage#object",
 "id": "example-bucket/obj3/1495822576643790",
 .
 .
 .
 "metadata": {
  "type": "calico"
  },
  .
  .
  .
}

--batch_pK7JBAk73-E=_AA5eFwv4m2Q=--

Si la solicitud general no tiene el formato correcto y Cloud Storage no puede analizarla en subsolicitudes, recibirá un error 400. De lo contrario, Cloud Storage devuelve un código de estado 200, aunque se produzca un error en algunas o en todas las subsolicitudes.

Cuando la solicitud general devuelve un código de estado 200, la respuesta contiene los resultados de cada subsolicitud, incluido un código de estado para cada una, que indica si la subsolicitud se ha realizado correctamente o no. Por ejemplo, al eliminar objetos por lotes, cada subsolicitud correcta contiene un código de estado 204 No Content.