Reescrever objetos do Cloud Storage

A regravação de um objeto no Cloud Storage copia ou transforma um objeto existente, como movê-lo entre locais, alternar a chave de criptografia ou alterar a classe de armazenamento. Dependendo do tamanho do objeto e dos parâmetros de regravação, o Cloud Storage pode concluir a operação em uma única chamada ou retornar o progresso intermediário com um rewrite_token. Quando uma chamada retornar um rewrite_token, transmita esse token para as solicitações subsequentes até que a regravação seja concluída.

Neste documento, mostramos como configurar uma solicitação de reescrita e conduzir o loop de reescrita usando o auxiliar rewrite_until_done ou um loop manual nas bibliotecas de cliente do Cloud para Rust. Se você não conhece a biblioteca de cliente, consulte Como usar o Cloud Storage com Rust primeiro.

Antes de começar

Antes de executar o código neste documento, conclua os seguintes pré-requisitos:

Dependências

Adicione a biblioteca de cliente do Cloud Storage e os crates de suporte ao arquivo Cargo.toml:

cargo add google-cloud-storage google-cloud-gax anyhow bytes
cargo add tokio --features macros,rt-multi-thread

Preparar o cliente e reescrever a solicitação

O assistente automático e o loop manual começam com um cliente StorageControl e um builder de solicitações RewriteObject configurados. Em um escopo que aceite bucket_name no formato projects/_/buckets/BUCKET_ID, siga estas etapas:

  1. Inicialize um cliente StorageControl com uma política de repetição. Recomendamos permitir pelo menos 30 segundos por solicitação. Este exemplo usa RetryableErrors com um limite de cinco tentativas e sem limite de tempo geral por operação:

    let control = StorageControl::builder()
        .with_retry_policy(RetryableErrors.with_attempt_limit(5))
        .build()
        .await?;

  2. Crie o builder de solicitações rewrite_object, marque a solicitação como idempotente para que o cliente possa repetir erros temporários e defina os nomes de bucket e objeto de origem e destino:

    let mut builder = control
        .rewrite_object()
        .with_idempotency(true) // retry transient errors
        .set_source_bucket(bucket_name)
        .set_source_object(&source_object.name)
        .set_destination_bucket(bucket_name)
        .set_destination_name("rewrite-object-clone");

  3. Opcional: limite o número máximo de bytes reescritos por chamada para que as solicitações individuais retornem o progresso mais rapidamente, em vez de exigir um tempo limite de solicitação mais longo. Este exemplo define um limite pequeno de 1 MiB para forçar várias iterações. Use um valor maior na produção:

    builder = builder.set_max_bytes_rewritten_per_call(1024 * 1024);

  4. Opcional: especifique os metadados do objeto de destino, como alterar a classe de armazenamento para Nearline (NEARLINE):

    builder = builder.set_destination(Object::new().set_storage_class("NEARLINE"));

Executar o loop de reescrita automaticamente

Na maioria dos casos de uso, inclua a característica RewriteObjectExt no escopo e chame rewrite_until_done no builder de solicitações. Esse auxiliar gerencia o rewrite_token em todas as iterações e resolve os metadados Object de destino após a conclusão da regravação:

use google_cloud_storage::builder_ext::RewriteObjectExt;
let dest_object = builder.rewrite_until_done().await?;
println!("dest_object={dest_object:?}");

Executar o loop de reescrita manualmente

Se você precisar registrar contagens de bytes intermediárias, verificar o progresso ou retomar uma regravação de outro processo, controle o loop de regravação manualmente:

  1. Defina uma função auxiliar que envie uma única solicitação RewriteObject, registre o progresso e retorne os metadados Object concluídos quando resp.done for true ou o próximo rewrite_token:

    enum RewriteProgress {
        // This holds the rewrite token
        Incomplete(String),
        Done(Box<Object>),
    }
    
    async fn make_one_request(builder: RewriteObject) -> Result<RewriteProgress> {
        let resp = builder.send().await?;
        if resp.done {
            println!(
                "DONE:     total_bytes_rewritten={}; object_size={}",
                resp.total_bytes_rewritten, resp.object_size
            );
            return Ok(RewriteProgress::Done(Box::new(
                resp.resource
                    .expect("A `done` response must have an object."),
            )));
        }
        println!(
            "PROGRESS: total_bytes_rewritten={}; object_size={}",
            resp.total_bytes_rewritten, resp.object_size
        );
        Ok(RewriteProgress::Incomplete(resp.rewrite_token))
    }

  2. Chame o auxiliar em um loop, clonando o builder de solicitação em cada iteração e transmitindo o token retornado para set_rewrite_token sempre que make_one_request retornar RewriteProgress::Incomplete:

    let dest_object = loop {
        let progress = make_one_request(builder.clone()).await?;
        match progress {
            RewriteProgress::Incomplete(rewrite_token) => {
                builder = builder.set_rewrite_token(rewrite_token);
            }
            RewriteProgress::Done(object) => break object,
        };
    };
    println!("dest_object={dest_object:?}");

Preencher código

O exemplo a seguir faz upload de um objeto de origem de 3 MiB e o reescreve na classe de armazenamento Nearline Storage em incrementos de 1 MiB usando o loop manual. Depois que a regravação é concluída, o exemplo exclui os objetos de teste e o bucket de teste vazio:

use gcs::Result;
use gcs::builder::storage_control::RewriteObject;
use gcs::client::StorageControl;
use gcs::model::Object;
use gcs::retry_policy::RetryableErrors;
use google_cloud_gax::options::RequestOptionsBuilder;
use google_cloud_gax::retry_policy::RetryPolicyExt as _;
use google_cloud_storage as gcs;

pub async fn rewrite_object(bucket_name: &str) -> anyhow::Result<()> {
    let source_object = upload(bucket_name).await?;

    let control = StorageControl::builder()
        .with_retry_policy(RetryableErrors.with_attempt_limit(5))
        .build()
        .await?;

    let mut builder = control
        .rewrite_object()
        .with_idempotency(true) // retry transient errors
        .set_source_bucket(bucket_name)
        .set_source_object(&source_object.name)
        .set_destination_bucket(bucket_name)
        .set_destination_name("rewrite-object-clone");

    // Optionally limit the max bytes written per request.
    builder = builder.set_max_bytes_rewritten_per_call(1024 * 1024);

    // Optionally change the storage class to force GCS to copy bytes
    builder = builder.set_destination(Object::new().set_storage_class("NEARLINE"));

    let dest_object = loop {
        let progress = make_one_request(builder.clone()).await?;
        match progress {
            RewriteProgress::Incomplete(rewrite_token) => {
                builder = builder.set_rewrite_token(rewrite_token);
            }
            RewriteProgress::Done(object) => break object,
        };
    };
    println!("dest_object={dest_object:?}");

    cleanup(control, bucket_name, &source_object.name, &dest_object.name).await;
    Ok(())
}

enum RewriteProgress {
    // This holds the rewrite token
    Incomplete(String),
    Done(Box<Object>),
}

async fn make_one_request(builder: RewriteObject) -> Result<RewriteProgress> {
    let resp = builder.send().await?;
    if resp.done {
        println!(
            "DONE:     total_bytes_rewritten={}; object_size={}",
            resp.total_bytes_rewritten, resp.object_size
        );
        return Ok(RewriteProgress::Done(Box::new(
            resp.resource
                .expect("A `done` response must have an object."),
        )));
    }
    println!(
        "PROGRESS: total_bytes_rewritten={}; object_size={}",
        resp.total_bytes_rewritten, resp.object_size
    );
    Ok(RewriteProgress::Incomplete(resp.rewrite_token))
}

// Upload an object to rewrite
async fn upload(bucket_name: &str) -> anyhow::Result<Object> {
    let storage = gcs::client::Storage::builder().build().await?;
    // We need the size to exceed 1MiB to exercise the rewrite token logic.
    let payload = bytes::Bytes::from(vec![65_u8; 3 * 1024 * 1024]);
    let object = storage
        .write_object(bucket_name, "rewrite-object-source", payload)
        .send_unbuffered()
        .await?;
    Ok(object)
}

// Clean up the resources created in this sample
async fn cleanup(control: StorageControl, bucket_name: &str, o1: &str, o2: &str) {
    let _ = control
        .delete_object()
        .set_bucket(bucket_name)
        .set_object(o1)
        .send()
        .await;
    let _ = control
        .delete_object()
        .set_bucket(bucket_name)
        .set_object(o2)
        .send()
        .await;
    let _ = control.delete_bucket().set_name(bucket_name).send().await;
}

A saída é semelhante à seguinte, mostrando o progresso em três iterações e os metadados do objeto de destino:

PROGRESS: total_bytes_rewritten=1048576; object_size=3145728
PROGRESS: total_bytes_rewritten=2097152; object_size=3145728
DONE:     total_bytes_rewritten=3145728; object_size=3145728
dest_object=Object { name: "rewrite-object-clone", ... }

Limpar

Se a amostra for encerrada antes do tempo ou se você executar snippets individuais sem o assistente de limpeza, exclua todos os objetos de teste restantes e exclua o bucket de teste quando terminar para não gerar cobranças contínuas na sua conta do Google Cloud .