Riscrivi gli oggetti Cloud Storage

La riscrittura di un oggetto in Cloud Storage copia o trasforma un oggetto esistente, ad esempio spostandolo da una località all'altra, ruotando la relativa chiave di crittografia o modificando la relativa classe di archiviazione. A seconda delle dimensioni dell'oggetto e dei parametri di riscrittura, Cloud Storage potrebbe completare l'operazione in una singola chiamata o restituire un avanzamento intermedio con un rewrite_token. Quando una chiamata restituisce un rewrite_token, passa il token alle richieste successive fino al completamento della riscrittura.

Questo documento mostra come configurare una richiesta di riscrittura e gestire il loop di riscrittura utilizzando l'helper rewrite_until_done o un loop manuale nelle librerie client di Cloud per Rust. Se non hai mai utilizzato la libreria client, consulta prima Utilizzo di Cloud Storage con Rust.

Prima di iniziare

Prima di eseguire il codice in questo documento, completa i seguenti prerequisiti:

Dipendenze

Aggiungi la libreria client di Cloud Storage e i crate di supporto al file Cargo.toml:

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

Prepara il client e riscrivi la richiesta

Sia l'helper automatico che il loop manuale iniziano da un client StorageControl e un generatore di richieste RewriteObject configurati. All'interno di un ambito che accetta bucket_name nel formato projects/_/buckets/BUCKET_ID, completa i seguenti passaggi:

  1. Inizializza un client StorageControl con una policy di ripetizione. Ti consigliamo di concedere almeno 30 secondi per richiesta. Questo esempio utilizza RetryableErrors con un limite di 5 tentativi e nessun limite di tempo complessivo per operazione:

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

  2. Crea il generatore di richieste rewrite_object, contrassegna la richiesta come idempotente in modo che il client possa riprovare in caso di errori temporanei e imposta i nomi dei bucket e degli oggetti di origine e di destinazione:

    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. (Facoltativo) Limita il numero massimo di byte riscritti per chiamata in modo che le singole richieste restituiscano l'avanzamento più velocemente anziché richiedere un timeout della richiesta più lungo. Questo esempio imposta un limite ridotto di 1 MiB per forzare più iterazioni. Utilizza un valore più elevato in produzione:

    builder = builder.set_max_bytes_rewritten_per_call(1024 * 1024);

  4. (Facoltativo) Specifica i metadati dell'oggetto di destinazione, ad esempio modificando la classe di archiviazione in Nearline (NEARLINE):

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

Esegui automaticamente il ciclo di riscrittura

Per la maggior parte dei casi d'uso, porta il tratto RewriteObjectExt nell'ambito e chiama rewrite_until_done sul generatore di richieste. Questo helper gestisce il rewrite_token tra le iterazioni e si risolve nei metadati Object di destinazione al termine della riscrittura:

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

Esegui manualmente il ciclo di riscrittura

Se devi registrare i conteggi dei byte intermedi, controllare l'avanzamento o riprendere una riscrittura da un altro processo, gestisci manualmente il loop di riscrittura:

  1. Definisci una funzione helper che invia una singola richiesta RewriteObject, registra l'avanzamento e restituisce i metadati Object completati quando resp.done è true o il rewrite_token successivo:

    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. Chiama l'helper in un loop, clonando il generatore di richieste a ogni iterazione e passando il token restituito a set_rewrite_token ogni volta che make_one_request restituisce 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:?}");

Completamento codice

L'esempio seguente carica un oggetto di origine di 3 MiB e lo riscrive nella classe di archiviazione Nearline Storage in incrementi di 1 MiB utilizzando il loop manuale. Al termine della riscrittura, l'esempio elimina gli oggetti di test e il bucket di test vuoto:

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;
}

L'output è simile al seguente e mostra l'avanzamento in tre iterazioni e i metadati dell'oggetto di destinazione:

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", ... }

Esegui la pulizia

Se l'esecuzione dell'esempio termina in anticipo o se esegui singoli snippet senza l'helper di pulizia, elimina tutti gli oggetti di test rimanenti ed elimina il bucket di test al termine, in modo da non incorrere in addebiti continui sul tuo account Google Cloud .