NativeLink

Google Cloud Storage

Back the cache with a GCS bucket: authentication, the resumable-upload chunk size, and the timeout fields whose names disagree with their units.

Who this is for: anyone running NativeLink on Google Cloud who wants the durable tier to be a GCS bucket. What you'll have at the end: a bucket-backed CAS behind a local fast tier, authenticated by the ambient service account. Time: twenty minutes, most of it IAM.

Before you start

A config file you can edit (see Configuration), and a GCS bucket you can write to.

GCS is one provider value of the same cloud object store used by S3 and compatible, Azure Blob and OCI Object Storage. The composition around it is identical; only the provider block changes.

The recipe

stores: [
  {
    name: "CAS_MAIN_STORE",
    fast_slow: {
      fast: {
        filesystem: {
          content_path: "/var/lib/nativelink/cas/content",
          temp_path: "/var/lib/nativelink/cas/tmp",
          eviction_policy: { max_bytes: "50gb" },
        },
      },
      slow: {
        experimental_cloud_object_store: {
          provider: "gcs",
          bucket: "my-nativelink-cas",
          key_prefix: "cas/",
          retry: { max_retries: 6, delay: 0.3, jitter: 0.5 },
        },
      },
    },
  },
],

bucket is the only field you have to set. No region is needed: a GCS bucket's location is a property of the bucket, decided when you create it, and the client reaches it the same way wherever it lives. Put the bucket in the region your workers run in; cross-region reads are both slower and billed.

ExperimentalGcsSpec

Authentication

Credentials come from the ambient Google credential chain: a credentials file named by GOOGLE_APPLICATION_CREDENTIALS (or inline JSON in GOOGLE_APPLICATION_CREDENTIALS_JSON), the application-default credentials written by gcloud auth application-default login, or otherwise the metadata server on GCE, GKE or Cloud Run. Nothing secret goes in the config file.

The service account needs to read and write objects in the bucket; roles/storage.objectUser scoped to the bucket covers it (overwriting an existing object in GCS also needs the delete permission that role includes). NativeLink itself never deletes objects; expiry is a bucket lifecycle rule.

authentication_required defaults to false: if no credential is found, the store starts anyway and issues anonymous requests to storage.googleapis.com (there is no endpoint override for an emulator). Set it to true for anything real, so a misconfigured credential chain fails loudly at startup rather than as a permission error on the first request.

GcsClient

Options worth setting

FieldDefaultWhat it decides
key_prefixnoneNamespacing inside one bucket; give the CAS cas/ and the AC ac/
resumable_chunk_size2 MBChunk size for resumable uploads of large objects; can only be lowered
authentication_requiredfalseWhether to error at startup when no credential is found
retryExponential backoff with jitter, shared with every other provider
consider_expired_after_s0 (never)How old an object may be and still count as present

Objects under 5 MB with a known size go up in one request; everything else uses a resumable upload, sent chunk by chunk with resumable_chunk_size bytes per request. The value is rounded to a multiple of 256 KiB and capped at the 2 MB default, so it can only be lowered, which trades more round trips per object for less memory held per in-flight upload. No setting raises it.

GcsStore

The remaining shared options behave as described in the shared options section, with one difference: here multipart_max_concurrent_uploads (default 10) caps how many requests this store has in flight to GCS at once, across all operations, and the chunks of one resumable upload are sent one after another. max_retry_buffer_per_request is raised to at least the chunk size, and insecure_allow_http and disable_http2 are not consulted by the GCS client.

Steps

  1. Create the bucket in the region your workers run in. Uniform bucket-level access is fine and simpler than per-object ACLs.

  2. Grant the service account object read and write on that bucket, and nothing wider.

  3. Set authentication_required: true so a missing credential fails at startup. A resumable upload session that a killed process leaves behind expires on its own after a week; there is nothing to clean up.

  4. Add the store as the slow half of a fast_slow pair whose fast half is a local filesystem store.

  5. Run a build twice (once cold, once after emptying the local fast tier) and watch object counts in the bucket.

You did it right if

  • Objects appear under key_prefix in the bucket during the first build.
  • The second build is a cache hit even after content_path is emptied, which proves reads fall through to the bucket.
  • Bucket request counts are far lower than blob counts, which proves the fast tier is absorbing repeat reads.

When it doesn't work

NextCompose stores

Deduplication, compression and existence caching in front of the bucket, the layers that decide what it actually costs.

SidewaysS3 and compatible

The same store type with a different provider, and the shared options in full.

On this page