Menulis ulang objek Cloud Storage

Menulis ulang objek di Cloud Storage akan menyalin atau mengubah objek yang ada, seperti memindahkannya ke berbagai lokasi, merotasi kunci enkripsinya, atau mengubah kelas penyimpanannya. Bergantung pada ukuran objek dan parameter penulisan ulang, Cloud Storage dapat menyelesaikan operasi dalam satu panggilan atau menampilkan progres perantara dengan rewrite_token. Saat panggilan menampilkan rewrite_token, teruskan token tersebut ke permintaan berikutnya hingga penulisan ulang selesai.

Dokumen ini menunjukkan cara mengonfigurasi permintaan penulisan ulang dan mendorong loop penulisan ulang menggunakan helper rewrite_until_done atau loop manual di Library Klien Cloud untuk Rust. Jika Anda baru menggunakan library klien, lihat Menggunakan Cloud Storage dengan Rust terlebih dahulu.

Sebelum memulai

Sebelum menjalankan kode dalam dokumen ini, selesaikan prasyarat berikut:

Dependensi

Tambahkan library klien Cloud Storage dan crate pendukung ke file Cargo.toml Anda:

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

Menyiapkan klien dan permintaan penulisan ulang

Helper otomatis dan loop manual dimulai dari klien StorageControl dan builder permintaan RewriteObject yang dikonfigurasi. Di dalam cakupan yang menerima bucket_name dalam format projects/_/buckets/BUCKET_ID, selesaikan langkah-langkah berikut:

  1. Lakukan inisialisasi klien StorageControl dengan kebijakan percobaan ulang. Sebaiknya berikan waktu minimal 30 detik per permintaan. Contoh ini menggunakan RetryableErrors dengan batas 5 percobaan dan tanpa batas waktu keseluruhan per operasi:

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

  2. Buat builder permintaan rewrite_object, tandai permintaan sebagai idempoten agar klien dapat mencoba lagi error sementara, dan tetapkan nama bucket serta objek sumber dan tujuan:

    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. Opsional: Batasi jumlah maksimum byte yang ditulis ulang per panggilan sehingga setiap permintaan menampilkan progres lebih cepat, bukan memerlukan waktu tunggu permintaan yang lebih lama. Contoh ini menetapkan batas kecil 1 MiB untuk memaksa beberapa iterasi; gunakan nilai yang lebih besar dalam produksi:

    builder = builder.set_max_bytes_rewritten_per_call(1024 * 1024);

  4. Opsional: Tentukan metadata objek tujuan, seperti mengubah kelas penyimpanan menjadi Nearline (NEARLINE):

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

Menjalankan loop penulisan ulang secara otomatis

Untuk sebagian besar kasus penggunaan, masukkan trait RewriteObjectExt ke dalam cakupan dan panggil rewrite_until_done pada builder permintaan. Helper ini mengelola rewrite_token di seluruh iterasi dan me-resolve ke metadata Object tujuan setelah penulisan ulang selesai:

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

Menjalankan loop penulisan ulang secara manual

Jika Anda perlu mencatat jumlah byte perantara, progres checkpoint, atau melanjutkan penulisan ulang dari proses lain, jalankan loop penulisan ulang secara manual:

  1. Tentukan fungsi bantuan yang mengirim satu permintaan RewriteObject, mencatat progres, dan menampilkan metadata Object yang telah selesai saat resp.done adalah true atau rewrite_token berikutnya:

    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. Panggil helper dalam loop, dengan meng-clone builder permintaan pada setiap iterasi dan meneruskan token yang ditampilkan ke set_rewrite_token setiap kali make_one_request menampilkan 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:?}");

Kode lengkap

Contoh berikut mengupload objek sumber 3 MiB dan menulisnya ulang ke kelas penyimpanan Nearline dalam penambahan 1 MiB menggunakan loop manual. Setelah penulisan ulang selesai, contoh akan menghapus objek pengujian dan bucket pengujian kosong:

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

Outputnya mirip dengan berikut ini, yang menunjukkan progres di tiga iterasi dan metadata objek tujuan:

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

Pembersihan

Jika sampel keluar lebih awal atau Anda menjalankan cuplikan individual tanpa helper pembersihan, hapus semua objek pengujian yang tersisa dan hapus bucket pengujian setelah selesai agar Anda tidak dikenai biaya berkelanjutan pada akun Google Cloud Anda.