Skip to content

🏙️ feat: Add Redis Cluster Support - #17

Open
pedrojreis wants to merge 9 commits into
LibreChat-AI:mainfrom
nosportugal:feature-memorystore-cluster
Open

🏙️ feat: Add Redis Cluster Support#17
pedrojreis wants to merge 9 commits into
LibreChat-AI:mainfrom
nosportugal:feature-memorystore-cluster

Conversation

@pedrojreis

Copy link
Copy Markdown

Overview

Adds opt-in Redis Cluster support to every service component. Standalone
Redis remains the default — existing deployments require zero configuration
changes
and behave exactly as before.

Validated in production against Google Cloud Memorystore in cluster mode with
TLS and CA-certificate verification.


Motivation

The service previously constructed Redis connections with inline
new IORedis({ ... }) calls in four separate modules, each hardcoded to
standalone mode. Connecting to a clustered Redis (GCP Memorystore cluster, AWS
ElastiCache cluster) was impossible: the client would only ever reach a single
shard and fail with MOVED/CROSSSLOT errors under load.

This PR centralizes connection creation behind a single factory and teaches
every component to speak the Redis Cluster protocol when asked.


What's new

🔌 Cluster mode (opt-in, auto-detected)

Enable it either explicitly or implicitly:

# Explicit
USE_REDIS_CLUSTER=true
REDIS_HOST=node1.example.com:6379

# Auto-detected — a comma in REDIS_HOST turns on cluster mode
REDIS_HOST=node1:6379,node2:6379,node3:6379

🔐 TLS with CA-certificate validation

REDIS_TLS=true
REDIS_CA=/etc/redis-tls/ca.crt   # PEM file → full cert verification

When REDIS_CA is set it takes precedence and enables validated TLS.
REDIS_TLS=true on its own keeps the previous rejectUnauthorized: false
behaviour for backward compatibility.

🧩 BullMQ cluster-safety

Queue, Worker and QueueEvents receive a {codeapi} hash-tag prefix in cluster
mode so all BullMQ keys map to a single hash slot (a hard requirement for BullMQ
on Redis Cluster). Standalone deployments keep their existing key layout — no
migration needed.


New environment variables

Variable Default Description
USE_REDIS_CLUSTER false Force cluster mode. Also auto-enabled when REDIS_HOST contains a comma.
REDIS_CA (unset) Path to a PEM CA-cert file. Enables TLS with full certificate validation; takes precedence over REDIS_TLS.

Existing variables are unchanged and fully backward-compatible:
REDIS_HOST, REDIS_PORT, REDIS_PASSWORD, REDIS_TLS,
REDIS_USE_ALTERNATIVE_DNS_LOOKUP, REDIS_KEEP_ALIVE_MS.


Implementation

service/src/redis-connection.ts (new — single source of truth)

Export Responsibility
createRedisConnection(overrides) Returns Redis | Cluster based on env; each caller passes its own retry / readyCheck overrides
isClusterMode() USE_REDIS_CLUSTER=true or comma in REDIS_HOST
parseRedisNodes() Parses REDIS_HOST into [{ host, port }] startup nodes
buildTlsOptions() REDIS_CA{ ca } (validated); else REDIS_TLS=true{ rejectUnauthorized: false }; else no TLS
bullmqPrefix() '{codeapi}' in cluster mode, undefined otherwise

Refactored clients

All four inline new IORedis({ ... }) blocks now call createRedisConnection():

  • queue.ts — shared BullMQ connection + prefix: bullmqPrefix() on Queue / QueueEvents
  • workers.tsprefix: bullmqPrefix() on both Worker instances
  • egress-ledger.ts — mutation-connection pool made cluster-safe (Cluster has no .duplicate(), so a fresh createRedisConnection() is used instead)
  • tool-call-server.ts, file-server.ts — session-state clients

service/src/service/replay-state.ts

scanKeys() is now cluster-aware. ioredis.Cluster has no top-level
scanStream, so in cluster mode the helper fans out across every master node
via cluster.nodes('master') and streams SCAN on each. Masters own disjoint
hash-slot ranges, so results never overlap. This fixes the runtime crash:

TypeError: <client>.scanStream is not a function

service/src/config.ts

Adds the USE_REDIS_CLUSTER flag to the parsed env.

Helm chart (helm/codeapi/)

New values.yaml surface:

redis:
  cluster:
    enabled: false
    nodes: ""                        # "host1:6379,host2:6379,host3:6379"
  tls:
    enabled: false
    caSecretName: ""                 # Secret holding the CA cert
    caKey: "ca"
    caMountPath: /etc/redis-tls/ca.crt
  useAlternativeDnsLookup: false     # required for GCP Memorystore cluster TLS

New _helpers.tpl templates — codeapi.redis.clusterEnabled,
codeapi.redis.tlsEnv, codeapi.redis.caVolume, codeapi.redis.caVolumeMount
— are wired into all five component Deployments, including mounting the CA cert
from a Secret into each pod.

service/.env.example

Documents every new variable with inline guidance.


Tests

New service/src/redis-connection.test.ts — 18 unit tests, no live Redis required:

Suite Coverage
parseRedisNodes single host, embedded port, comma list, whitespace trimming, default fallback
isClusterMode explicit flag, comma auto-detect, standalone
buildTlsOptions no TLS, REDIS_TLS only, REDIS_CA file read, CA precedence over REDIS_TLS, missing CA file
bullmqPrefix standalone, cluster via flag, cluster via comma host
✓ 18 pass   redis-connection.test.ts
✓ 35 pass   egress-ledger / egress-gateway / replay-state (unchanged, still green)

Backward compatibility

  • ✅ Standalone is the default — no env changes for existing deployments.
  • REDIS_TLS=true without REDIS_CA keeps the prior rejectUnauthorized: false behaviour.
  • ✅ BullMQ key prefixes are added only in cluster mode; standalone key layout is untouched.
  • ✅ No breaking changes to any existing environment variable.

How to verify

cd service
bun test src/redis-connection.test.ts     # 18/18 pass

# render the Helm chart in cluster mode
helm template codeapi helm/codeapi \
  --set redis.enabled=false \
  --set redis.cluster.enabled=true \
  --set redis.cluster.nodes="n1:6379\,n2:6379\,n3:6379" \
  --set redis.tls.enabled=true \
  --set redis.tls.caSecretName=my-memorystore-secret

@CLAassistant

CLAassistant commented Jul 7, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@pedrojreis
pedrojreis force-pushed the feature-memorystore-cluster branch 3 times, most recently from 0aabfff to 418e509 Compare July 7, 2026 21:36
@danny-avila

Copy link
Copy Markdown
Collaborator

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 418e509eb3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread service/src/redis-connection.ts
Comment thread helm/codeapi/templates/_helpers.tpl
Comment thread helm/codeapi/README.md Outdated
Comment thread service/src/tool-call-server.ts
Comment thread helm/codeapi/templates/_helpers.tpl
@pedrojreis

pedrojreis commented Jul 29, 2026

Copy link
Copy Markdown
Author

@danny-avila all issues should be fixed

Added : Merged from upstream to fix conflicts

@pedrojreis
pedrojreis force-pushed the feature-memorystore-cluster branch from 0deb596 to 045d5a2 Compare August 12, 2026 10:41
… handling

* Refactor job processing in workers.ts for improved readability and maintainability.
* Introduce Redis connection management in redis-connection.ts.
* Add tests for Redis connection utilities in redis-connection.test.ts.
* Implement TLS options handling for secure Redis connections.
* Enhance error handling and logging throughout the job processing flow.
* Updated the project dependency to version 2.3.1.
* Ensured compatibility with existing codebase.
* Ran tests to verify functionality post-upgrade.
- Hash-tag per-execution Redis keys ({execution_id}) in replay-state and
  tool-call-server so multi-key Lua scripts, MULTI/EXEC and multi-key DELs
  stay on one Cluster slot (avoids CROSSSLOT errors)
- Add shared hashTag/stripHashTag/scanKeys helpers in redis-connection
- Replace blocking KEYS with cluster-aware SCAN in tool-call-server cleanup
- Fix wait-for-redis probe to target a single startup node in cluster mode
  via new codeapi.redis.probeHost/probePort helpers
- Gate REDIS_TLS/CA env and volumes behind external Redis (not bundled subchart)
- Document redis.enabled=false requirement for cluster mode in README
@pedrojreis
pedrojreis force-pushed the feature-memorystore-cluster branch from 045d5a2 to 7477efe Compare August 12, 2026 10:47
@pedrojreis

Copy link
Copy Markdown
Author

Rebased yet again :)

@LibreChat-AI LibreChat-AI deleted a comment from chatgpt-codex-connector Bot Sep 2, 2026
@LibreChat-AI LibreChat-AI deleted a comment from pedrojreis Sep 2, 2026
@danny-avila

Copy link
Copy Markdown
Collaborator

@codex review the latest head

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 2, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-02T06:48:00.365618Z 7477efe Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7477efe4e7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread service/src/redis-connection.ts
Comment thread service/src/redis-connection.ts
Comment thread helm/codeapi/README.md
Comment thread service/src/service/replay-state.ts
Comment thread service/src/redis-connection.ts
Comment thread service/src/redis-connection.ts Outdated
Resolve conflicts in config.ts, queue.ts, workers.ts, replay-state.ts,
values.yaml and api-deployment.yaml.
main's bridge stores took a concrete Redis; the cluster branch's shared
connection is Redis | Cluster. Widen both constructors so the merged
tree typechecks.
@danny-avila

Copy link
Copy Markdown
Collaborator

Hi @pedrojreis — this had drifted into conflict against main, so I resolved it on a branch you can pull from: pr-17-redis-cluster-merged (b6375395).

git remote add upstream https://github.com/LibreChat-AI/code-interpreter.git
git fetch upstream pr-17-redis-cluster-merged
git reset --hard upstream/pr-17-redis-cluster-merged
git push --force-with-lease

What conflicted, and why there was so much of it

14 hunks across 6 files. Only 8 of the 19 files you touched had actually moved on main, and by ~270 lines — the rest of the collision came from formatting. The branch reformats every file it touches from 2-space to 4-space with arrowParens: avoid, which matches the repo's .prettierrc.yaml but not the committed style of service/src. About half the ±4,500 diff is whitespace, and it's what made small upstream changes land as whole-file conflicts.

Worth deciding before this merges: left as-is, the next commit to file-server.ts, tool-call-server.ts, egress-ledger.ts or replay-state.ts will re-conflict the same way.

How I resolved each

File Resolution
api-deployment.yaml Both sides additive — kept your cluster env vars and main's pairing-rollback epoch
values.yaml Kept main's strategy: Recreate + pullPolicy: Always security fence, reindented to your style
config.ts Main's resolvePositiveIntEnv, the remote-bridge backend and 8 new env keys, plus your USE_REDIS_CLUSTER
queue.ts Main's execution-profile queue registry, with your cluster prefix threaded through getQueueResources
replay-state.ts Main's 4 new ExecutionState fields — your hashTag work auto-merged cleanly
workers.ts Took main's file and re-applied your 3 lines

That last one is the only judgment call worth flagging. workers.ts had 6 badly-aligned hunks, but your entire semantic contribution to it was the bullmqPrefix import and two prefix: options — hand-porting main's manifest-signing and egress-grant logic into 4-space carried real risk for no gain. That file is now +3 instead of +506. Nothing of yours was dropped.

I also widened bridge/store.ts and bridge/pairing.ts to accept RedisClient. Main added those stores after this branch was opened and they take a concrete Redis, so the merge exposed them to your union type — that breakage was merge-induced, so it's fixed on the branch. tsc confirms Cluster supports every command they use.

Still blocking, and yours to resolve

The branch does not typecheck — but it didn't before I touched it either, and my merge added zero new errors (verified against your branch's own baseline). 10 errors remain:

  • 8 × RedisClient is not assignable to Redisruntime-session/registry.ts (269, 277), runtime-session/throttle.ts (21, 28), service/replay-state.ts (302, 315), and the two I fixed. createRedisConnection returns Redis | Cluster, but these call sites still require a concrete Redis. Same one-line-per-site widening as the bridge stores, assuming Cluster supports what each uses.
  • 2 × redis-connection.ts (162, 190)disconnectTimeout isn't a key of CommonRedisOptions, so the Pick<> and the assembled options object both fail. Did you mean connectTimeout?

Everything else checks out: 701/703 tests pass, and both failures are pre-existing jq canonicalization tests that fail identically on main. The chart renders in standalone and cluster mode, emitting USE_REDIS_CLUSTER=true, the node list, REDIS_TLS, REDIS_CA and the CA volume across all five components.

Thanks for this — the hashTag work in particular is the right shape.

@danny-avila danny-avila changed the title feat: add Redis Cluster mode support (GCP Memorystore, AWS ElastiCache) 🏙️ feat: Add Redis Cluster Support Sep 2, 2026
@pedrojreis
pedrojreis force-pushed the feature-memorystore-cluster branch from e1fd741 to a7c1ea3 Compare September 2, 2026 13:31
@pedrojreis

pedrojreis commented Sep 2, 2026

Copy link
Copy Markdown
Author

@danny-avila did what you said and fixed issues previously detected :)
thanks for the upstream branch

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

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.

3 participants