Skip to content

persist: read GCS blobs in concurrent 8 MiB ranges - #40486

Merged
alex-hunt-materialize merged 3 commits into
MaterializeInc:mainfrom
alex-hunt-materialize:persist-gcs-chunked-get
Nov 10, 2026
Merged

alex-hunt-materialize merged 3 commits into
MaterializeInc:mainfrom
alex-hunt-materialize:persist-gcs-chunked-get

Conversation

@alex-hunt-materialize

@alex-hunt-materialize alex-hunt-materialize commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

GcsBlob::get streamed each blob over one HTTP/1.1 connection (the SDK forces HTTP/1.1, so the HTTP/2 pool from #39906 doesn't apply). It now reads the first 8 MiB to learn the size and generation, then fetches the remaining ranges concurrently, pinned to that generation. The SDK skips checksum validation for ranged reads, so get checks the total length and verifies the object's CRC32C itself, hashing each chunk as it arrives. This adds a direct crc32c dependency, already in Cargo.lock transitively.

128 MiB, n2-standard-16 in us-east1-b, median of 10:

get set
GcsBlob, single stream (before) ~520 ms ~880 ms
GcsBlob, chunked (this PR) ~125 ms unchanged
S3Blob over GCS S3 API, pool on ~620 ms ~450 ms

Writes remain slower than S3Blob and are left for a follow-up.

Tests: gcs_blob_get_chunk_boundaries covers empty objects (GCS rejects ranges on them with 416, which get maps to an empty result) and sizes around chunk boundaries.

gcs_blob_throughput is an ignored bench producing the numbers above.

The two commits are easier to review individually: the first changes get, the second adds the bench.

🤖 Generated with Claude Code

@alex-hunt-materialize
alex-hunt-materialize force-pushed the persist-gcs-chunked-get branch 4 times, most recently from 3d5536f to 8f81af3 Compare November 5, 2026 11:04
GcsBlob::get streamed each blob over a single HTTP/1.1 connection. It now
reads the first 8 MiB to learn the size and generation, then fetches the
remaining ranges concurrently, pinned to that generation. In us-east1 a
128 MiB get went from ~500ms to ~125ms, faster than S3Blob over GCS's S3
API with the HTTP pool (~600ms).

The SDK skips checksum validation for range reads, so get now verifies the
object's CRC32C itself, using the new direct crc32c dependency.

Adds gcs_blob_get_chunk_boundaries.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Times 128 MiB gets and sets through a single-stream read, GcsBlob, and,
when HMAC credentials are set, S3Blob against GCS's S3 API.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@alex-hunt-materialize
alex-hunt-materialize marked this pull request as ready for review November 5, 2026 12:08
@alex-hunt-materialize
alex-hunt-materialize requested a review from a team as a code owner November 5, 2026 12:08

@mtabebe mtabebe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One small comment, but otherwise LGTM

Comment thread src/persist/src/gcs.rs Outdated
}
// The SDK skips checksum validation for range reads, so check the
// whole object's CRC32C from the first response's metadata.
if let Some(expected_crc32c) = crc32c {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a valid case where we don't have an object's CRC in the first response metadata?
Or does the lack of a checksum indicate that the metadata we got back was garbage? If so, should we also error in the none case?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! We were silently skipping it 🤦. GCS only includes the CRC when the range covers the whole object. I just pushed a fix.

GCS sends x-goog-hash only when the requested range covers the whole
object, so multi-chunk gets silently skipped the CRC32C check. Fetch the
CRC32C from the object's metadata, pinned to the same generation and
concurrently with the remaining chunks, and error if there is none.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

@mtabebe mtabebe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM thanks for the fix

@alex-hunt-materialize
alex-hunt-materialize merged commit d7dfa98 into MaterializeInc:main Nov 10, 2026
86 checks passed
@alex-hunt-materialize
alex-hunt-materialize deleted the persist-gcs-chunked-get branch November 10, 2026 10:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants