Skip to content

createSentinel() hangs while connecting to Redis Sentinel #2962

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
aryamohanan opened this issue May 13, 2025 · 4 comments
Closed

createSentinel() hangs while connecting to Redis Sentinel #2962

aryamohanan opened this issue May 13, 2025 · 4 comments
Labels

Comments

@aryamohanan
Copy link

aryamohanan commented May 13, 2025

Description

When using the new createSentinel() API introduced in redis v5, the client hangs indefinitely during the .connect() call. This issue does not occur when using the older createClient() method with Sentinel options.

❌ Not Working (New API)

const { createSentinel } = require('redis');

async function connectToRedisSentinel() {
  const client = await createSentinel({
    name: 'mymaster',
    sentinelRootNodes: [
      { host: 'localhost', port: 5000 },
      { host: 'localhost', port: 5001 },
      { host: 'localhost', port: 5002 },
    ],
  })
    .on('error', (err) => console.error('Redis Sentinel Error', err))
    .connect();
  await client.set('key', 'hello');
  const value = await client.get('key');
  console.log('Value:', value);
}

connectToRedisSentinel();

✅ Working (Legacy API)

const { createClient } = require('redis');

async function connectToRedisSentinel() {
  const client = await createClient({
    sentinel: {
      sentinels: [
        { host: 'localhost', port: 5000 },
        { host: 'localhost', port: 5001 },
        { host: 'localhost', port: 5002 },
      ],
      name: 'mymaster',
    },
  });

  client.on('error', (err) => console.error('Redis Sentinel Error:', err));

    await client.connect();
    console.log('Redis Sentinel connected');
    await client.set('key', 'hello');
    const value = await client.get('key');
    console.log('Value:', value);
}

connectToRedisSentinel();

Here is the content of my sentinel.conf file (used on all three Sentinels):

port 5000
sentinel monitor mymaster redis-master 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 10000
sentinel parallel-syncs mymaster 1

Each of the three Sentinels has a similar configuration with appropriate port changes.

The createSentinel() approach never resolves the .connect() call, and no meaningful error is logged. Meanwhile, the legacy createClient() approach connects and functions without issues.

Is there something I might be missing in the usage of createSentinel()? Could you please verify whether this is a bug or share a working example using the new API?

Please let me know if any additional details are needed.

Thank you!

Node.js Version

20.6

Redis Server Version

6

Node Redis Version

5.0.1

Platform

macOS (M1)

Logs

@htemelski
Copy link
Collaborator

Hello @aryamohanan,
By "Legacy API", do you mean node-redis 4.7 or 5?
Also, are you using TypeScript or JavaScript?
The RedisClientOptions interface, which is accepted by the createSentinel object, does not have a sentinel property.
In your working example, I suspect the client is connecting directly to redis-server, skipping the sentinel altogether.

@aryamohanan
Copy link
Author

Hello @htemelski,

Thank you for the response.

By "Legacy API", I was referring to the use of createClient() from Redis v5. You might be right—it’s possible that it’s not actually connecting through Sentinel. However, the Sentinel instances are up and running, and I can access them via the terminal.

With the new createSentinel() approach (as shown in my initial example), the .connect() call hangs indefinitely without logging any meaningful error, even though the Sentinel nodes are reachable and healthy.

If there's a more complete or correct usage pattern for createSentinel(), I’d be grateful if you could share an example or point me to relevant documentation. For context, I’m running Redis in a Docker Compose setup.

Also, I’m using JavaScript (not TypeScript).

Docker-Compose

services:
  redis-master:
    image: redis:latest
    command: ['redis-server', '--appendonly', 'yes', '--protected-mode', 'no']

  redis-slave-1:
    image: redis:latest
    depends_on:
      - redis-master
    command:
      [
        'redis-server',
        '--replicaof',
        'redis-master',
        '6379',
        '--protected-mode',
        'no',
      ]

  redis-slave-2:
    image: redis:latest
    depends_on:
      - redis-master
    command:
      [
        'redis-server',
        '--replicaof',
        'redis-master',
        '6379',
        '--protected-mode',
        'no',
      ]

  redis-slave-3:
    image: redis:latest
    depends_on:
      - redis-master
    command:
      [
        'redis-server',
        '--replicaof',
        'redis-master',
        '6379',
        '--protected-mode',
        'no',
      ]

  sentinel-1:
    image: redis:latest
    ports:
      - "5000:26379"
    depends_on:
      - redis-master
      - redis-slave-1
      - redis-slave-2
      - redis-slave-3
    command: >
      sh -c 'echo "sentinel resolve-hostnames yes" > /etc/sentinel.conf &&
            echo "sentinel monitor mymaster redis-master 6379 2" >> /etc/sentinel.conf &&
            echo "sentinel down-after-milliseconds mymaster 1000" >> /etc/sentinel.conf &&
            echo "sentinel failover-timeout mymaster 5000" >> /etc/sentinel.conf &&
            echo "sentinel parallel-syncs mymaster 1" >> /etc/sentinel.conf &&
            redis-server /etc/sentinel.conf --sentinel'

  sentinel-2:
    image: redis:latest
    ports:
      - "5001:26379"
    depends_on:
      - redis-master
      - redis-slave-1
      - redis-slave-2
      - redis-slave-3
    command: >
      sh -c 'echo "sentinel resolve-hostnames yes" > /etc/sentinel.conf &&
            echo "sentinel monitor mymaster redis-master 6379 2" >> /etc/sentinel.conf &&
            echo "sentinel down-after-milliseconds mymaster 1000" >> /etc/sentinel.conf &&
            echo "sentinel failover-timeout mymaster 5000" >> /etc/sentinel.conf &&
            echo "sentinel parallel-syncs mymaster 1" >> /etc/sentinel.conf &&
            redis-server /etc/sentinel.conf --sentinel'

  sentinel-3:
    image: redis:latest
    ports:
      - "5002:26379"
    depends_on:
      - redis-master
      - redis-slave-1
      - redis-slave-2
      - redis-slave-3
    command: >
      sh -c 'echo "sentinel resolve-hostnames yes" > /etc/sentinel.conf &&
            echo "sentinel monitor mymaster redis-master 6379 2" >> /etc/sentinel.conf &&
            echo "sentinel down-after-milliseconds mymaster 1000" >> /etc/sentinel.conf &&
            echo "sentinel failover-timeout mymaster 5000" >> /etc/sentinel.conf &&
            echo "sentinel parallel-syncs mymaster 1" >> /etc/sentinel.conf &&
            redis-server /etc/sentinel.conf --sentinel'

Is there something I might be missing in the usage of createSentinel()?

Thanks again for your time and assistance!

@htemelski
Copy link
Collaborator

There's a fundamental mistake with your Docker-Compose setup.
By default, Docker uses a bridge network
In your setup, when Sentinel analyzes the topology, it reports that the master node address is 172.x.x.x, which your host machine does not have access to.

There are two ways to resolve the issue:

  1. Using host network driver
  2. Running your application within a container

If you decide to go with approach one, you need to properly configure all services (master, slave and sentinels) to use unique IPs and you need to manually provide the local IP address (127.0.0.1) to the configuration files.

For the second approach you need to define a new service inside the docker container that will run your script/program, you would also need to modify a little bit the script itself.

Here's an example for approach 2

docker-compose.yaml
services:
  redis-master:
    image: redis:latest
    command: ['redis-server', '--appendonly', 'yes', '--protected-mode', 'no']

  redis-slave-1:
    image: redis:latest
    depends_on:
      - redis-master
    command:
      [
        'redis-server',
        '--replicaof',
        'redis-master',
        '6379',
        '--protected-mode',
        'no',
      ]

  redis-slave-2:
    image: redis:latest
    depends_on:
      - redis-master
    command:
      [
        'redis-server',
        '--replicaof',
        'redis-master',
        '6379',
        '--protected-mode',
        'no',
      ]

  redis-slave-3:
    image: redis:latest
    depends_on:
      - redis-master
    command:
      [
        'redis-server',
        '--replicaof',
        'redis-master',
        '6379',
        '--protected-mode',
        'no',
      ]

  sentinel-1:
    image: redis:latest
    depends_on:
      - redis-master
      - redis-slave-1
      - redis-slave-2
      - redis-slave-3
    command: >
      sh -c 'echo "sentinel resolve-hostnames yes" > /etc/sentinel.conf &&
            echo "sentinel monitor mymaster redis-master 6379 2" >> /etc/sentinel.conf &&
            echo "sentinel down-after-milliseconds mymaster 1000" >> /etc/sentinel.conf &&
            echo "sentinel failover-timeout mymaster 5000" >> /etc/sentinel.conf &&
            echo "sentinel parallel-syncs mymaster 1" >> /etc/sentinel.conf &&
            redis-server /etc/sentinel.conf --sentinel'

  sentinel-2:
    image: redis:latest
    depends_on:
      - redis-master
      - redis-slave-1
      - redis-slave-2
      - redis-slave-3
    command: >
      sh -c 'echo "sentinel resolve-hostnames yes" > /etc/sentinel.conf &&
            echo "sentinel monitor mymaster redis-master 6379 2" >> /etc/sentinel.conf &&
            echo "sentinel down-after-milliseconds mymaster 1000" >> /etc/sentinel.conf &&
            echo "sentinel failover-timeout mymaster 5000" >> /etc/sentinel.conf &&
            echo "sentinel parallel-syncs mymaster 1" >> /etc/sentinel.conf &&
            redis-server /etc/sentinel.conf --sentinel'

  sentinel-3:
    image: redis:latest
    depends_on:
      - redis-master
      - redis-slave-1
      - redis-slave-2
      - redis-slave-3
    command: >
      sh -c 'echo "sentinel resolve-hostnames yes" > /etc/sentinel.conf &&
            echo "sentinel monitor mymaster redis-master 6379 2" >> /etc/sentinel.conf &&
            echo "sentinel down-after-milliseconds mymaster 1000" >> /etc/sentinel.conf &&
            echo "sentinel failover-timeout mymaster 5000" >> /etc/sentinel.conf &&
            echo "sentinel parallel-syncs mymaster 1" >> /etc/sentinel.conf &&
            redis-server /etc/sentinel.conf --sentinel'
            
  nodejs-app:
    image: node:20
    working_dir: /app
    volumes:
      - .:/app
    depends_on:
      - redis-master
      - sentinel-1
      - sentinel-2
      - sentinel-3
    command: >
      sh -c "npm install && node index.js"
    environment:
      - NODE_ENV=development
      - SENTINEL_HOST_1=sentinel-1
      - SENTINEL_HOST_2=sentinel-2
      - SENTINEL_HOST_3=sentinel-3
      - SENTINEL_PORT=26379
      - SENTINEL_MASTER_NAME=mymaster

And here is how you need to modify your code in order to take into consideration the env variables and properly connect to sentinel

index.js
import { createSentinel } from 'redis';

const sentinelHost1 = process.env.SENTINEL_HOST_1 || '127.0.0.1';
const sentinelHost2 = process.env.SENTINEL_HOST_2 || '127.0.0.1';
const sentinelHost3 = process.env.SENTINEL_HOST_3 || '127.0.0.1';
const sentinelPort = parseInt(process.env.SENTINEL_PORT || '5000', 10);
const masterName = process.env.SENTINEL_MASTER_NAME || 'mymaster';

const client = createSentinel({
  name: masterName, 
  sentinelRootNodes: [{
    host: sentinelHost1, 
    port: sentinelPort
  },{
    host: sentinelHost2, 
    port: sentinelPort
  },{
    host: sentinelHost3, 
    port: sentinelPort
  }]
});

await client.connect();
console.log(await client.ping());
await client.close()

@aryamohanan
Copy link
Author

Hey, thank you for the clear explanation! You were absolutely right about the network issue. I tried both approaches — running the app in a container and updating the Sentinel config to use 127.0.0.1 — and both worked.

The issue I faced earlier was that there was no error when the config was incorrect, which made it a bit tricky to debug. Really appreciate your help!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants