NativeLink

Redis

Back the cache with Redis (standalone, sentinel or cluster) and the concurrency, chunking and timeout settings that keep it from timing out under a real build.

Who this is for: anyone who wants several NativeLink processes to share one low-latency store, or who needs the scheduler's pub/sub subscriptions. What you'll have at the end: a Redis-backed store with concurrency and timeouts set so it survives a burst. Time: twenty minutes.

Before you start

A config file you can edit (see Configuration), and a reachable Redis instance.

Redis sits between the filesystem and object storage: it is shared across machines like a bucket, and roughly a millisecond away like a local disk. What it is not is cheap per byte. Use it for small, hot, shared objects (the Action Cache, scheduler state, the index side of a dedup store) and put the bulk CAS somewhere that prices storage by the gigabyte.

The recipe

stores: [
  {
    name: "AC_MAIN_STORE",
    redis_store: {
      addresses: ["redis://127.0.0.1:6379/"],
      mode: "standard",
      key_prefix: "nativelink:ac:",
    },
  },
],

addresses is the only required field. The URL carries everything Redis needs: scheme, optional credentials, host, port and database index. redis://username:password@redis-server-url:6380/99 selects port 6380 and database 99. Keep credentials out of the file by writing redis://${REDIS_USER}:${REDIS_PASSWORD}@redis:6379/ and exporting the variables; see The config file.

RedisSpec

Standalone, sentinel or cluster

mode takes standard (the default), sentinel or cluster.

ModeWhat goes in addresses
standardExactly one server URL
sentinelExactly one sentinel URL, not a data node; the master name comes from a ?sentinelServiceName= query parameter (default master), and a redis+sentinel:// scheme is accepted
clusterAny subset of the cluster's nodes; the client discovers the rest

The store connects to the sentinel, asks it for the current master, and re-resolves the master when a write hits a demoted replica or the connection drops. Passing more than one address in standard or sentinel mode is rejected at startup.

RedisStore

key_prefix namespaces one Redis instance across several stores, and matters more here than with object storage: a Redis instance is much more likely to be shared with something else you run. Give the CAS, the AC and the scheduler distinct prefixes.

Sizing it so it doesn't time out

Redis is single-threaded per node, so the settings that matter are the ones bounding how much work you can have in flight.

FieldDefaultWhat it decides
max_client_permits500Ceiling on concurrent actions against the store
connection_pool_size3Accepted but not used by the current client, which holds one multiplexed connection
read_chunk_size64 KiBBytes pulled per chunk when streaming a blob out
max_chunk_uploads_per_update10Concurrent chunk uploads within one write
command_timeout_ms10000Per-command ceiling
connection_timeout_ms3000Ceiling on establishing a connection
health_check_timeout_ms4000Ceiling on the health-check PING
scan_count10000COUNT hint per SCAN iteration
key_ttl_s0 (never)Expiry set on every key this store writes; must exceed the longest upload

max_client_permits is the most useful lever. It exists precisely to stop timeouts caused by many, many inflight actions: when the ceiling is reached, work queues instead of piling onto Redis and blowing through command_timeout_ms. Lower it if you see timeouts under load; that is almost always the right first move.

Peak memory in the client process is roughly read_chunk_size × max_chunk_uploads_per_update × concurrent uploads. Working backwards from available memory, divide by about ten to leave room for everything else the process is doing.

max_count_per_cursor (default 1500) bounds how many entries one cursor returns; it exists to reduce thundering-herd effects when many workers hit the provisioner at once.

Three fields are deprecated

response_timeout_s is superseded by command_timeout_ms and is ignored with a warning; connection_timeout_s is superseded by connection_timeout_ms and is still honoured (converted to milliseconds) if the millisecond field is unset, but setting both is an error. broadcast_channel_capacity is unused by the current client. experimental_pub_sub_channel remains, and is what scheduler subscriptions ride on.

Where Redis actually earns its place

Rather than as a bulk CAS, Redis is usually the fast or index half of a composition:

{
  name: "CAS_MAIN_STORE",
  dedup: {
    index_store: {
      redis_store: {
        addresses: ["redis://redis:6379/"],
        key_prefix: "nativelink:index:",
      },
    },
    content_store: {
      experimental_cloud_object_store: {
        provider: "aws",
        region: "us-east-1",
        bucket: "my-nativelink-cas",
        key_prefix: "cas/",
      },
    },
  },
},

The index is small, hot and shared by every process; the content is large, cold and cheap per byte. Compose stores covers the rest of these shapes.

Steps

  1. Provision Redis with enough memory for what you intend to keep, and decide its eviction policy on the Redis side, because NativeLink does not manage it.

  2. Add the store with addresses, the right mode, and a key_prefix that won't collide with anything else using that instance.

  3. Reference credentials through the environment rather than writing them into the URL in the config file.

  4. Run a build, then run it again from a second machine pointed at the same Redis, to confirm the store really is shared.

  5. Load-test it and watch for command_timeout_ms errors. If you see them, lower max_client_permits before touching anything else.

You did it right if

  • Keys appear under key_prefix: SCAN with a MATCH on the prefix returns entries while a build runs.
  • A second machine pointed at the same Redis gets cache hits for objects the first one uploaded.
  • No command timeouts in the logs under your normal peak load.
  • Redis memory use plateaus where its own eviction policy says it should, rather than climbing to the instance limit.

When it doesn't work

NextCompose stores

Where a Redis store belongs inside a larger composition, usually as the index or fast half, not the bulk store.

SidewaysSchedulers and workers

Redis also backs scheduler state when more than one scheduler shares a queue.

On this page