Downloading a large object in Cloud Storage sequentially over a single connection can leave available network bandwidth unused. By dividing an object into byte-range segments called stripes and reading those segments concurrently, you can improve download throughput for large objects using the Cloud Client Libraries for Rust.
This document shows you how to seed large test objects using
object composition, download byte ranges concurrently with
ReadRange::segment, and write stripes to a local file at fixed offsets. If
you're new to the client library, see
Using Cloud Storage with Rust first.
Before you begin
Before running the code in this document, complete the following prerequisites:
- Create or select a Google Cloud project with billing enabled.
- Create a dedicated test Cloud Storage bucket in your project.
- Configure Application Default Credentials for your environment.
- Grant your principal an Identity and Access Management (IAM) role with permission to
read, write, compose, and delete objects in the bucket, such as Storage Admin
(
roles/storage.admin).
Dependencies
Add the Cloud Storage client library and supporting crates to your
Cargo.toml file:
cargo add google-cloud-storage google-cloud-gax anyhow bytes futures
cargo add tokio --features macros,rt-multi-thread
Create large test objects
To test striped downloads without uploading gigabytes of data over the network,
upload a small seed object and use the Cloud Storage compose
operation to concatenate copies server-side. Inside an
asynchronous seed function that accepts client, control, and bucket_name
in the format projects/_/buckets/BUCKET_ID, complete the
following steps:
To reference existing objects in compose requests, import
SourceObject:Upload a 1 MiB seed object named
1MiB.txt:To create a 32 MiB object named
32MiB.txt, concatenate 32 copies of1MiB.txton the server:To create a 1 GiB object named
1GiB.txt, concatenate 32 copies of32MiB.txt:To build power-of-two test objects from
1GiB.txt(2GiB.txt,4GiB.txt,8GiB.txt,16GiB.txt, and32GiB.txt), repeat the compose operation:
Coordinate concurrent stripe downloads
To download an object in parallel, query its size and generation,
preallocate the local destination file, and spawn one future per stripe. Inside
a download function that accepts client, control, bucket_name,
object_name, stripe_size, and destination, complete the following steps:
Fetch the object metadata using
StorageControl::get_objectso that you can inspect the total object size and lock subsequent reads to a single generation:Create the destination file and preallocate it to the exact object size so that worker tasks can write at independent byte offsets:
Calculate each stripe offset using
stripe_sizeaslimitand create awrite_stripefuture per segment, appending a final partial stripe when the object size isn't evenly divisible bylimit:Run all stripe futures concurrently with
futures::future::join_alland propagate any errors:
Download and write an individual stripe
Each write_stripe task reads a single byte range from Cloud Storage
and writes the received chunks at the corresponding offset in the shared local
file handle. To implement write_stripe, complete the following steps:
Import
ReadRangeand define thewrite_stripefunction:Create a
ReadObjectrequest builder withStorage::read_object, and send the request after configuring the generation and read-range options in the following steps:To pin all stripes to the same object generation and prevent inconsistent reads if another process overwrites the object, call
set_generationon the request builder before callingsend:To restrict the read to the stripe's byte range, call
set_read_rangewithReadRange::segment(offset, limit)on the request builder:Stream chunks from the reader and write each chunk to the destination file at
current_posinsidetokio::task::spawn_blocking. Because Rust's standard library lacks a cross-platform positional write method, this example usesFileExt::write_all_aton Unix andFileExt::seek_writeon Windows:
Performance considerations
Several factors determine the effective throughput of striped downloads:
- Local disk speed. Writing chunks to slow local storage can become the bottleneck. Using high-throughput local SSD storage helps keep disk I/O ahead of network reads.
- VM CPU capacity. Cloud Storage encrypts object data in transit, so the client decrypts every chunk it receives. Ensure your VM has enough vCPUs to handle concurrent TLS decryption across stripes.
- Bucket and VM location. Downloading across regions introduces wide-area network latency and limits throughput. Colocate your VM in the same region as your bucket when possible.
You can also experiment with tuning stripe_size to avoid leaving a very small
remainder stripe or creating too many tiny range requests.
Complete code
The following example combines the seed, download, and write_stripe
functions into a complete module:
When you run download on a 32 MiB object with a 1 MiB stripe_size,
the output is similar to the following, reporting the downloaded size, elapsed
time, stripe count, and effective bandwidth:
Completed 32.00 MiB download in 412.5ms, using 32 stripes, effective bandwidth = 77.58 MiB/s
Clean up
To avoid incurring charges to your Google Cloud account for the test
objects created by seed, delete the test objects and
delete your test bucket when you're finished. Also remove the
local destination file.