Enviar pedidos em lote

Este documento mostra como agrupar chamadas da API JSON para reduzir o número de ligações HTTP que o cliente tem de fazer quando acede ao Cloud Storage.

Vista geral

Cada ligação HTTP que o seu cliente faz resulta numa determinada quantidade de sobrecarga. A API JSON do Cloud Storage suporta o processamento em lote, para permitir que o seu cliente coloque várias chamadas API num único pedido HTTP.

Exemplos de situações em que pode querer usar o processamento em lote:

  • Atualizar metadados, como autorizações, em muitos objetos.
  • A eliminar muitos objetos.

Em cada caso, em vez de enviar cada chamada separadamente, pode agrupá-las numa única solicitação HTTP. Todos os pedidos internos têm de ser enviados para a API JSON do Cloud Storage.

Não deve incluir mais de 100 chamadas num único pedido em lote. Se precisar de fazer mais chamadas do que isso, use vários pedidos em lote. A carga útil total do pedido em lote tem de ser inferior a 10 MB.

Detalhes do lote

Um pedido em lote consiste em várias chamadas API combinadas num pedido HTTP, que pode ser enviado para o ponto final em lote do Cloud Storage, que é https://storage.googleapis.com/batch/storage/v1. Esta secção descreve a sintaxe de comandos em lote em detalhe. Mais tarde, é apresentado um exemplo.

Formato de um pedido em lote

Um pedido em lote é um único pedido HTTP padrão que contém várias chamadas da API JSON do Cloud Storage. Este pedido principal usa o multipart/mixedtipo de conteúdo. No pedido HTTP principal, existem várias partes que contêm cada uma um pedido HTTP aninhado.

Cada parte começa com o seu próprio cabeçalho HTTP Content-Type: application/http. A parte também pode ter um cabeçalho Content-ID opcional. Estes cabeçalhos marcam o início da parte, mas estão separados do pedido HTTP aninhado. Isto significa que, depois de o servidor anular a união do pedido em lote em pedidos separados, os cabeçalhos das partes são ignorados.

O corpo de cada parte é, em si mesmo, um pedido HTTP completo, com o seu próprio verbo, URL, cabeçalhos e corpo. Estes pedidos HTTP só podem conter a parte do caminho do URL. Os URLs completos podem ter um comportamento indefinido.

Os cabeçalhos HTTP do pedido em lote externo, exceto os cabeçalhos Content-, como Content-Type, também se aplicam a todos os pedidos aninhados. No entanto, se especificar um determinado cabeçalho HTTP no pedido exterior e num pedido aninhado, o valor do cabeçalho do pedido aninhado substitui o valor do cabeçalho do pedido em lote exterior para esse pedido específico.

Por exemplo, se fornecer um cabeçalho Authorization para um pedido aninhado específico, esse cabeçalho aplica-se apenas ao pedido que o especificou. Se fornecer um cabeçalho Authorization para o pedido externo, esse cabeçalho aplica-se a todos os pedidos aninhados, a menos que o substituam por um cabeçalho Authorization próprio.

Quando o Cloud Storage recebe o pedido em lote, aplica os parâmetros de consulta e os cabeçalhos do pedido externo (conforme adequado) a cada parte e, em seguida, trata cada parte como se fosse um pedido HTTP separado.

Resposta a um pedido em lote

A resposta do Cloud Storage é uma única resposta HTTP padrão com um multipart/mixedtipo de conteúdo; cada parte desta resposta principal é a resposta a um dos pedidos no pedido em lote. A ordem das respostas é a mesma que a dos pedidos.

Tal como todas as partes num pedido, cada parte da resposta contém uma resposta HTTP completa, incluindo um código de estado, cabeçalhos e um corpo. Tal como as partes no pedido, cada parte da resposta é precedida por um cabeçalho Content-Type que marca o início da parte. Para mais informações sobre códigos de estado, consulte o artigo Códigos de estado e de erro HTTP para a API JSON do Cloud Storage.

Se uma determinada parte do pedido tiver um cabeçalho Content-ID, a parte correspondente da resposta tem um cabeçalho Content-ID correspondente. O cabeçalho Content-ID da resposta começa com response-, seguido do Content-ID valor usado no pedido, conforme mostrado no exemplo.

Exemplo

O exemplo de lote seguinte atualiza os metadados personalizados de três objetos em example-bucket.

Exemplo de pedido HTTP em lote

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 cliente

C++

A biblioteca de cliente C++ não suporta pedidos em lote.

C#

A biblioteca de cliente C# não suporta pedidos em lote.

Go

A biblioteca de cliente Go não suporta pedidos em lote.

Java

Para mais informações, consulte a documentação de referência da API Java do 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

A biblioteca de cliente Node.js não suporta pedidos em lote.

PHP

A biblioteca de cliente PHP não suporta pedidos em lote.

Python

Para mais informações, consulte a documentação de referência da API Python do 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 como fazer um pedido em lote através do Ruby, consulte a documentação de referência da API Ruby do Cloud Storage.

Exemplo de resposta HTTP em lote

Esta é a resposta ao pedido de exemplo HTTP na secção 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=--

Se o pedido geral não estiver formatado corretamente e o Cloud Storage não conseguir analisá-lo em pedidos secundários, recebe um erro 400. Caso contrário, o Cloud Storage devolve um código de estado 200, mesmo que alguns ou todos os pedidos secundários falhem.

Quando o pedido geral é devolvido com um código de estado 200, a resposta contém resultados para cada subpedido, incluindo um código de estado para cada um, que indica se o subpedido foi bem-sucedido ou falhou. Por exemplo, quando elimina objetos em lote, cada subpedido bem-sucedido contém um código de estado 204 No Content.