Cloud Storage-Objekte umschreiben

Beim Umschreiben eines Objekts in Cloud Storage wird ein vorhandenes Objekt kopiert oder transformiert, z. B. durch Verschieben an einen anderen Standort, Rotieren des Verschlüsselungsschlüssels oder Ändern der Speicherklasse. Je nach Objektgröße und Rewrite-Parametern schließt Cloud Storage den Vorgang möglicherweise in einem einzigen Aufruf ab oder gibt den Zwischenfortschritt mit einem rewrite_token zurück. Wenn ein Aufruf ein rewrite_token zurückgibt, übergeben Sie dieses Token an nachfolgende Anfragen, bis die Neuerstellung abgeschlossen ist.

In diesem Dokument wird beschrieben, wie Sie eine Rewrite-Anfrage konfigurieren und die Rewrite-Schleife mit dem rewrite_until_done-Hilfsprogramm oder einer manuellen Schleife in den Cloud-Clientbibliotheken für Rust steuern. Wenn Sie die Clientbibliothek noch nicht kennen, lesen Sie zuerst Cloud Storage mit Rust verwenden.

Hinweis

Bevor Sie den Code in diesem Dokument ausführen, müssen die folgenden Voraussetzungen erfüllt sein:

Abhängigkeiten

Fügen Sie die Cloud Storage-Clientbibliothek und die unterstützenden Crates der Datei Cargo.toml hinzu:

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

Client und Rewrite-Anfrage vorbereiten

Sowohl der automatische Helfer als auch die manuelle Schleife beginnen mit einem konfigurierten StorageControl-Client und einem RewriteObject-Anfrage-Builder. Führen Sie in einem Bereich, der bucket_name im Format projects/_/buckets/BUCKET_ID akzeptiert, die folgenden Schritte aus:

  1. Initialisieren Sie einen StorageControl-Client mit einer Wiederholungsrichtlinie. Wir empfehlen, mindestens 30 Sekunden pro Anfrage einzuplanen. In diesem Beispiel wird RetryableErrors mit einem Limit von 5 Versuchen und ohne Gesamtzeitlimit pro Vorgang verwendet:

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

  2. Erstellen Sie den rewrite_object-Anfrage-Builder, markieren Sie die Anfrage als idempotent, damit der Client vorübergehende Fehler wiederholen kann, und legen Sie die Namen des Quell- und Ziel-Buckets sowie des Objekts fest:

    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. Optional: Begrenzen Sie die maximale Anzahl von Bytes, die pro Aufruf neu geschrieben werden, damit einzelne Anfragen den Fortschritt schneller zurückgeben, anstatt ein längeres Zeitlimit für Anfragen zu erfordern. In diesem Beispiel wird ein kleines Limit von 1 MiB festgelegt, um mehrere Iterationen zu erzwingen. Verwenden Sie in der Produktion einen größeren Wert:

    builder = builder.set_max_bytes_rewritten_per_call(1024 * 1024);

  4. Optional: Geben Sie Metadaten für das Zielobjekt an, z. B. die Änderung der Speicherklasse in Nearline (NEARLINE):

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

Rewrite-Schleife automatisch ausführen

In den meisten Anwendungsfällen müssen Sie das Merkmal RewriteObjectExt in den Bereich einbeziehen und rewrite_until_done für den Request Builder aufrufen. Dieser Helfer verwaltet das rewrite_token über Iterationen hinweg und löst die Metadaten des Ziels Object auf, nachdem das Umschreiben abgeschlossen ist:

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

Rewrite-Schleife manuell ausführen

Wenn Sie Zwischenbytezahlen protokollieren, den Prüfpunktfortschritt speichern oder einen Rewrite aus einem anderen Prozess fortsetzen müssen, führen Sie die Rewrite-Schleife manuell aus:

  1. Definieren Sie eine Hilfsfunktion, die eine einzelne RewriteObject-Anfrage sendet, den Fortschritt protokolliert und entweder die abgeschlossenen Object-Metadaten zurückgibt, wenn resp.done den Wert true hat, oder die nächste 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. Rufen Sie den Helper in einer Schleife auf. Klonen Sie den Request Builder bei jeder Iteration und übergeben Sie das zurückgegebene Token an set_rewrite_token, wenn make_one_request den Wert RewriteProgress::Incomplete zurückgibt:

    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:?}");

Vollständiger Code

Im folgenden Beispiel wird ein 3 MiB großes Quellobjekt hochgeladen und mit der manuellen Schleife in 1 MiB-Schritten in die Nearline Storage-Klasse umgeschrieben. Nach Abschluss des Umschreibens werden im Beispiel die Testobjekte und der leere Test-Bucket gelöscht:

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

Die Ausgabe sieht in etwa so aus und zeigt den Fortschritt über drei Iterationen und die Metadaten des Zielobjekts:

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

Bereinigen

Wenn das Beispiel vorzeitig beendet wird oder Sie einzelne Snippets ohne den Bereinigungs-Helper ausführen, löschen Sie alle verbleibenden Testobjekte und löschen Sie Ihren Test-Bucket, wenn Sie fertig sind, damit Ihnen keine laufenden Kosten für Ihr Google Cloud -Konto entstehen.