Skip to content

feat(cluster): support custom Redis class for internal connections#2097

Open
rit3sh-x wants to merge 3 commits into
redis:mainfrom
rit3sh-x:feat/cluster-custom-redis-class
Open

feat(cluster): support custom Redis class for internal connections#2097
rit3sh-x wants to merge 3 commits into
redis:mainfrom
rit3sh-x:feat/cluster-custom-redis-class

Conversation

@rit3sh-x
Copy link
Copy Markdown
Contributor

@rit3sh-x rit3sh-x commented Apr 10, 2026

Summary

Closes #2073.

Adds a redisClass option to ClusterOptions so users can pass a Redis subclass (e.g. class RedisWithInstrumentation extends Redis) and have it used for every internal connection the
Cluster creates.

Motivation

Users who subclass Redis for OpenTelemetry, logging, or custom error handling currently can't use their subclass with Cluster, because Cluster hardcodes new Redis(...) internally in multiple
places (pool, subscribers, slot refresher). This forces monkeypatching.

What changes

redisClass is threaded from ClusterOptions to every internal Redis construction site:

  • ConnectionPool — pool node connections
  • ClusterSubscriber — pub/sub subscriber
  • ShardedSubscriber (via ClusterSubscriberGroup) — sharded pub/sub subscribers
  • Cluster.getInfoFromNode — slot refresh connections (previously used redis.duplicate())

Defaults to Redis when not set — zero breaking changes.

Usage

class InstrumentedRedis extends Redis {
  sendCommand(command) {
    // add tracing, logging, error handling
    return super.sendCommand(command);
  }
}

const cluster = new Redis.Cluster(nodes, { redisClass: InstrumentedRedis });

Test plan

New test file test/functional/cluster/redis_class.ts with 8 behavior-focused tests:

  • Default Redis class used when option not set (backward compat)
  • Custom subclass used for pool node connections (multi-node)
  • Constructor invoked for every connection Cluster creates
  • Subscriber connections use subclass
  • Sharded subscriber connections use subclass
  • Slot refresh connections use subclass
  • sendCommand override actually intercepts commands end-to-end
  • Normal cluster functionality preserved with subclass

Full suite: 562 passing, 17 cluster passing, build + tsd pass.

Notes

  • Tests verify observable behavior (instanceof, constructor side-effects, command interception), not internal wiring, so they remain valid under future refactors.
  • Does not fix duplicate method should new subclasses #2053 (standalone duplicate()). That's a separate issue rooted in the Commander mixin system and warrants its own PR.

Note

Medium Risk
Touches core Cluster connection creation paths (pool nodes, subscribers, and slot-refresh connections), so regressions could affect connectivity and slot refresh behavior despite being opt-in. Backward compatibility is preserved by defaulting to Redis, and functional tests cover the new wiring.

Overview
Adds a new ClusterOptions.redisClass hook so a user-provided Redis subclass is used for all internal Cluster connections (node pool, subscriber, sharded subscribers, and slot refresh).

Threads redisClass through ConnectionPool, ClusterSubscriber, ShardedSubscriber/ClusterSubscriberGroup, and changes slot refresh to create a new connection via redisClass (instead of redis.duplicate()), then adds functional tests asserting default behavior and that instrumentation/overrides (e.g. sendCommand) apply end-to-end.

Reviewed by Cursor Bugbot for commit a51dd8c. Bugbot is set up for automated code reviews on this repo. Configure here.

  Adds a new 'redisClass' option to ClusterOptions that lets users pass
  a Redis subclass to be used for every internal connection the Cluster
  creates: pool nodes, pub/sub subscriber, sharded subscribers, and
  slot refresh connections.

  This enables instrumentation, logging, OpenTelemetry tracing, and
  custom error handling for Cluster without monkeypatching.

  Closes redis#2073
@PavelPashov
Copy link
Copy Markdown
Contributor

Thanks for the PR. I will review it and follow up once I have gone through it.

@PavelPashov
Copy link
Copy Markdown
Contributor

run sharded pub sub tests

@uglide
Copy link
Copy Markdown
Contributor

uglide commented Apr 11, 2026

Testcase Errors Failures Skipped Total
Root Suite 0 0 0 0
Sharded Pub/Sub E2E - Basic 0 0 0 4
Sharded Pub/Sub E2E - Connection Lifecycle 0 0 0 3
Sharded Pub/Sub E2E - Failure Recovery Multiple Subscribers 0 0 0 4
Sharded Pub/Sub E2E - Failure Recovery Single Subscriber 0 0 0 4

---- Details for maintainers

1 similar comment
@uglide
Copy link
Copy Markdown
Contributor

uglide commented Apr 11, 2026

Testcase Errors Failures Skipped Total
Root Suite 0 0 0 0
Sharded Pub/Sub E2E - Basic 0 0 0 4
Sharded Pub/Sub E2E - Connection Lifecycle 0 0 0 3
Sharded Pub/Sub E2E - Failure Recovery Multiple Subscribers 0 0 0 4
Sharded Pub/Sub E2E - Failure Recovery Single Subscriber 0 0 0 4

---- Details for maintainers

@uglide
Copy link
Copy Markdown
Contributor

uglide commented Apr 11, 2026

Testcase Errors Failures Skipped Total
Root Suite 0 0 0 0
Sharded Pub/Sub E2E - Basic 0 0 0 4
Sharded Pub/Sub E2E - Connection Lifecycle 0 0 0 3
Sharded Pub/Sub E2E - Failure Recovery Multiple Subscribers 0 1 0 4
Sharded Pub/Sub E2E - Failure Recovery Single Subscriber 0 1 0 4

---- Details for maintainers

@rit3sh-x
Copy link
Copy Markdown
Contributor Author

@uglide thanks for running this.

I can't access the CAE action logs from outside, could you share the failure output for the two Failure Recovery cases (Multiple Subscribers / Single Subscriber)?

I want to confirm whether this is a regression from threading redisClass through the reconnection path, or a pre-existing flake on main. If it's the PR, the most likely culprit is a missed new Redis(...)new this.RedisClass(...) replacement somewhere in the recovery code, I'll audit that path once I see the error.

@rit3sh-x
Copy link
Copy Markdown
Contributor Author

@PavelPashov @uglide quick update on the CAE Failure Recovery failures.

I spun up a local branch (test/2053-plus-2097) that merges this PR on top of #2053 (the applyMixin + duplicate() subclass fix). My theory was that the failure-recovery path relies on this.constructor / duplicate() behavior that was broken before #2053, so #2097 alone would be missing the foundation it needs.

What I tested, against both a local Redis cluster and a single-node instance:

On test/2053-plus-2097 (combined branch):

On feat/cluster-custom-redis-class alone (this PR without #2053):

  • npm run build: clean
  • test/functional/spub_ssub.ts: 9/9 passing, same results as the combined branch

So the local functional suite passes on both branches, which means the CAE Failure Recovery cases are exercising a code path my local tests do not cover. I cannot reproduce the CAE harness locally, so I cannot directly confirm the hypothesis from my side.

@rit3sh-x
Copy link
Copy Markdown
Contributor Author

@PavelPashov any updates?

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.

Add support for custom ConnectionPool or Redis class in Cluster duplicate method should new subclasses

3 participants