Skip to content

add gcs.allow_multipart_upload and gcs.allow_multipart_download for parallel transfers of large files#1469

Merged
Slach merged 5 commits into
masterfrom
gcs_multipart
Jul 16, 2026
Merged

add gcs.allow_multipart_upload and gcs.allow_multipart_download for parallel transfers of large files#1469
Slach merged 5 commits into
masterfrom
gcs_multipart

Conversation

@Slach

@Slach Slach commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

Summary

Implements parallel multipart transfers to/from GCS for large files, fix #1028.

Single-stream transfers of 100GB+ parts were the bottleneck for large-table backups (12h+ reported in the issue). Google shipped the feature requested in googleapis/google-cloud-go#3219 in two pieces, both used here:

  • Upload: the experimental EnableParallelUpload writer surface in cloud.google.com/go/storage v1.62+ (repo uses v1.63.1) — the stream splits into parts uploaded concurrently as temporary objects (gcs-go-sdk-pu-tmp/ prefix in the bucket root) and composed into the final object. gRPC client only.
  • Download: transfermanager.Downloader (preview) — parallel range reads of a single object into a temporary file, works over the existing HTTP client.

New config options (gcs section)

option env default
allow_multipart_upload GCS_ALLOW_MULTIPART_UPLOAD false experimental, opt-in
upload_concurrency GCS_UPLOAD_CONCURRENCY 0 → SDK min(4+CPU/2, 16) parts of one file uploaded in parallel
multipart_upload_min_size GCS_MULTIPART_UPLOAD_MIN_SIZE 1GB smaller files keep the single-stream path
allow_multipart_download GCS_ALLOW_MULTIPART_DOWNLOAD false mirrors s3.allow_multipart_download
download_concurrency GCS_DOWNLOAD_CONCURRENCY CPU/2 + 1 parts of one file downloaded in parallel, must be > 1

Part size for both directions is the existing chunk_size (default 16MiB; the SDK bumps upload parts below 5MiB to 5MiB).

Implementation notes

  • Upload works only via a gRPC client, so Connect() creates a dedicated storage.NewGRPCClient next to the pooled HTTP clients; retry policy is set client-level because part uploads create fresh ObjectHandles that per-object Retryer doesn't cover. ValidateConfig rejects allow_multipart_upload combined with endpoint, force_http or encryption_key (gRPC goes only to storage.googleapis.com; Compose requires the same CSEK for source parts and destination).
  • Download needs no gRPC and supports CSEK (with retry-without-key fallback for old unencrypted backups) and custom endpoints. It buffers the object to a temporary file next to the extraction path (same trade-off as S3), and inherits the bwlimit bypass from Replace post-hoc sleep throttling with token-bucket rate limiter #1377: with a rate limit active, DownloadCompressedStream forces the streaming reader.
  • UploadPath now passes the real file size to PutFile so the multipart_upload_min_size threshold also works for uncompressed (upload_by_part) uploads.
  • Metadata files and object-disk transfers keep the existing paths.
  • Docs mention the recommended bucket lifecycle rule for gcs-go-sdk-pu-tmp/ (SDK cleanup of temporary upload parts is best-effort).

Testing

  • make test passes; ValidateConfig unit cases for the incompatible combinations and the download_concurrency > 1 requirement.
  • New TestGCSMultipart integration test against the real altinity-qa-test bucket: ~30MB of Go-side-generated random data (LZ4-incompressible; randomString() is missing in old ClickHouse versions) with 1MiB threshold / 5MiB chunk_size, asserts the upload log went through putFileMultipart and the download log through the multipart path, then restore verifies all rows. Databases are dropped via the version-safe dropDatabase helper (SYNC for Atomic, otherwise the async drop leaves store/<uuid> behind and restore fails on 21.3). Runs with GCS_ENCRYPTION_KEY= unset per command since run.sh exports it globally for the CSEK coverage in TestGCS.
  • Verified on ClickHouse 26.3 and 21.3.

🤖 Generated with Claude Code

Slach added 5 commits July 15, 2026 21:44
Files bigger than gcs.parallel_upload_min_size (default 1GB) upload to GCS
as multiple parallel parts via a dedicated gRPC client and compose into the
final object, using the experimental EnableParallelUpload writer surface
from cloud.google.com/go/storage v1.62+.

Single-stream uploads of 100GB+ parts were the bottleneck for large-table
backups (12h+), see #1028.

- parallel_upload_part_size (0 = SDK default 16MiB, min 5MiB) and
  parallel_upload_max_concurrency (0 = SDK default min(4+CPU/2, 16))
- rejected together with endpoint, force_http and encryption_key: PCU works
  only via gRPC to storage.googleapis.com and Compose requires the same CSEK
  for source parts and destination
- retry policy set client-level: part uploads create fresh ObjectHandles,
  per-object Retryer does not cover them
- UploadPath now passes the real file size to PutFile so the min_size
  threshold works for uncompressed uploads
- TestGCSParallelUpload runs against the real bucket with GCS_ENCRYPTION_KEY
  unset per command (run.sh exports it globally for CSEK coverage in TestGCS)
Signed-off-by: slach <bloodjazman@gmail.com>
@Slach Slach changed the title add gcs.parallel_upload for parallel composite uploads of large files add gcs.allow_multipart_upload and gsc.allow_multipart_download for large files Jul 16, 2026
@Slach Slach changed the title add gcs.allow_multipart_upload and gsc.allow_multipart_download for large files add gcs.allow_multipart_upload and gcs.allow_multipart_download for parallel transfers of large files Jul 16, 2026
@coveralls

Copy link
Copy Markdown

Coverage Report for CI Build 29478480961

Coverage increased (+0.2%) to 68.357%

Details

  • Coverage increased (+0.2%) from the base build.
  • Patch coverage: 33 uncovered changes across 1 file (88 of 121 lines covered, 72.73%).
  • 3 coverage regressions across 2 files.

Uncovered Changes

File Changed Covered %
pkg/storage/gcs.go 97 64 65.98%
Total (3 files) 121 88 72.73%

Coverage Regressions

3 previously-covered lines in 2 files lost coverage.

File Lines Losing Coverage Coverage
pkg/storage/object_disk/object_disk.go 2 70.8%
pkg/metadata/backup_metadata.go 1 75.0%

Coverage Stats

Coverage Status
Relevant Lines: 23942
Covered Lines: 16366
Line Coverage: 68.36%
Coverage Strength: 43566.48 hits per line

💛 - Coveralls

@Slach
Slach merged commit 9ff52c1 into master Jul 16, 2026
55 of 56 checks passed
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.

Upload to GCS performance improvement for large parts

2 participants