node-redis icon indicating copy to clipboard operation
node-redis copied to clipboard

Redis create cluster issue

Open ellik95 opened this issue 3 years ago • 25 comments

Hello, I have a Redis MemoryDB cluster with 2 nodes in AWS and I'm trying to connect to it through createCluster, but the connection isn't established. Here is how I try to connect:

import { createCluster } from 'redis';
(async () => {
    const REDIS_USERNAME = 'test-username';
    const REDIS_PASSWORD = 'test-pass';
    const cluster = createCluster({
        rootNodes: [
            {
                url: `rediss://redis-region-0001-001.amazonaws.com:6379`,
                username: REDIS_USERNAME,
                password: REDIS_PASSWORD,
            },
            {
                url: `rediss://redis-region-0001-002.amazonaws.com:6379`,
                username: REDIS_USERNAME,
                password: REDIS_PASSWORD,
            },
        ]
    });
    cluster.on('error', (err) => console.log('Redis Cluster Error', err));
    console.log('before connection');
    await cluster.connect();
    console.log('connected to cluster...');
    await cluster.set('key', 'value');
    const value = await cluster.get('key');
    console.log(value);
    await cluster.disconnect();
})();

The log connected to cluster... is never showed up. If I add in the configuration of createCluster to the defaults option the master node endpoint like this:

defaults: {
            url: `rediss://redis-region-0001-001.amazonaws.com:6379`,
            username: REDIS_USERNAME,
            password: REDIS_PASSWORD,
        }

The connection is established and everything works fine! Why do I need the defaults option? Shouldn't it work only with rootNodes? Thank you!

Environment:

  • Node.js Version: 16-alpine3.12
  • Redis Server Version: 6
  • Node Redis Version: 4.0.0

ellik95 avatar Dec 14 '21 10:12 ellik95

As described in the clustering guide:

  • rootNodes - "An array of root nodes that are part of the cluster, which will be used to get the cluster topology. Each element in the array is a client configuration object. There is no need to specify every node in the cluster, 3 should be enough to reliably connect and obtain the cluster configuration from the server"
  • defaults - "The default configuration values for every client in the cluster. Use this for example when specifying an ACL user to connect with"

node-specific properties should be listed in rootNodes, the rest in defaults

createCluster({
 rootNodes: [{
   url: `rediss://redis-region-0001-001.amazonaws.com:6379`
 }, {
   url: `rediss://redis-region-0001-002.amazonaws.com:6379`
 }],
 defaults: {
   username: REDIS_USERNAME,
   password: REDIS_PASSWORD
 }
});

leibale avatar Dec 20 '21 20:12 leibale

I had tried this also and the same thing happened! Could you please reopen the issue?

ellik95 avatar Dec 21 '21 09:12 ellik95

@ellik95 It didn't work even when the username & password are in the defaults object? can you please share your code?

leibale avatar Dec 21 '21 14:12 leibale

Yes, with this code:

import { createCluster } from 'redis';
(async () => {
    const REDIS_USERNAME = 'test-username';
    const REDIS_PASSWORD = 'test-pass';
    const cluster = createCluster({
        rootNodes: [
            {
                url: `rediss://redis-region-0001-001.amazonaws.com:6379`
            },
            {
                url: `rediss://redis-region-0001-002.amazonaws.com:6379`
            },
        ],
       defaults: {
            username: REDIS_USERNAME,
            password: REDIS_PASSWORD,
        }
    });
    cluster.on('error', (err) => console.log('Redis Cluster Error', err));
    console.log('before connection');
    await cluster.connect();
    console.log('connected to cluster...');
    await cluster.set('key', 'value');
    const value = await cluster.get('key');
    console.log(value);
    await cluster.disconnect();
})();

it is not connected. Only if I add the url of the master node to the defaults options works. Like this:

defaults: {
            url: `rediss://redis-region-0001-001.amazonaws.com:6379`,
            username: REDIS_USERNAME,
            password: REDIS_PASSWORD,
        }

And I was wondering why is this needed.

ellik95 avatar Dec 21 '21 15:12 ellik95

createCluster({
  rootNodes: [{
    url: `rediss://redis-region-0001-001.amazonaws.com:6379`
  }, {
    url: `rediss://redis-region-0001-002.amazonaws.com:6379`
  }],
  defaults: {
    tls: true,
    username: REDIS_USERNAME,
    password: REDIS_PASSWORD
  }
});

leibale avatar Dec 21 '21 16:12 leibale

It's not working either and tls option does not exist in type Partial<RedisClusterClientOptions>.

ellik95 avatar Jan 02 '22 11:01 ellik95

@ellik95

createCluster({
  rootNodes: [{
    url: `rediss://redis-region-0001-001.amazonaws.com:6379`
  }, {
    url: `rediss://redis-region-0001-002.amazonaws.com:6379`
  }],
  defaults: {
    socket: {
      tls: true
    },
    username: REDIS_USERNAME,
    password: REDIS_PASSWORD
  }
});

leibale avatar Jan 02 '22 12:01 leibale

Unfortunately it is not working either. Only with the below code is:

createCluster({
  rootNodes: [{
    url: `rediss://redis-region-0001-001.amazonaws.com:6379`
  }, {
    url: `rediss://redis-region-0001-002.amazonaws.com:6379`
  }],
  defaults: {
    url: `rediss://redis-region-0001-001.amazonaws.com:6379`
    username: REDIS_USERNAME,
    password: REDIS_PASSWORD
  }
});

ellik95 avatar Jan 02 '22 12:01 ellik95

Also when I connect I sometimes get the error ReplyError: MOVED 12539 rediss://redis-region-0001-002.amazonaws.com:6379 and I cannot get the value from the key.

ellik95 avatar Jan 21 '22 10:01 ellik95

Same issue as @ellik95. I'm trying to connect with a Redis cluster running on Kubernetes and the connection is always ReplyError: MOVED xxxx In my case I have Load Balancer to connect with, so I'm using createClient rather than createCluster.

Connection through redis-client command line works fine, though. Is there any specific configuration we have to do with createClient when using Load Balanced redis cluster?

lohanbodevan avatar Feb 10 '22 17:02 lohanbodevan

I'm having the same issue with elasticache and the createCluster function, it just hangs indefinitely when you call cluster.connect() unless you provide a url entry in the defaults object.

However, when you do that it seems like it only connects to that node. If you try to access a key that gets sorted onto one of the other nodes you get the MOVED error.

@leibale I see that you mentioned this issue on https://github.com/redis/node-redis/pull/1827 but I'm not sure how that would apply to solve this issue. My server has access to the elasticache nodes just fine, it can connect to any of them individually with createClient but createCluster isn't able to connect unless you provide that default url.

acb avatar Feb 16 '22 21:02 acb

@acb this line should fix it

leibale avatar Feb 16 '22 21:02 leibale

@leibale oh, awesome! Do you have any idea when that might be pushed out in a release?

acb avatar Feb 16 '22 22:02 acb

@acb Sunday/Monday :)

leibale avatar Feb 16 '22 22:02 leibale

@leibale I just tried release 4.0.4 and unfortunately I'm still having the same problem. With the code

const client = createCluster({
    rootNodes: [
        {
            url: "rediss://redis-1-url:6379",
        },
        {
            url: "rediss://redis-2-url:6379",
        },
        {
            url: "rediss://redis-3-url:6379",
        },
    ],
    defaults: {
        password: "password123"
    }
});
client.on("error", (err: any) => {
    console.log("REDIS ERROR");
    console.log(err);
});

console.log('connecting to redis');
client.connect().then(() => {
    console.log('connected to redis');
});

I see 'connecting to redis' in my logs but it seems to hang and never actually connects. If I update my defaults to be

    defaults: {
        url: "rediss://redis-1-url:6379",
        password: "password123"
    }

Then in my logs I get 'connecting to redis' followed immediately by 'connected to redis', but the connection is only to the one redis server that's in the default. If I try to sample random keys 2/3rds of them return with something like MOVED 11388 redis-2-url.

acb avatar Feb 22 '22 19:02 acb

@acb try this

const client = createCluster({
    rootNodes: [
        {
            url: "rediss://redis-1-url:6379",
        },
        {
            url: "rediss://redis-2-url:6379",
        },
        {
            url: "rediss://redis-3-url:6379",
        },
    ],
    defaults: {
        password: "password123",
        socket: { tls: true }
    }
});

leibale avatar Feb 22 '22 21:02 leibale

@leibale you're a wizard! That did it, I'm getting successful cluster connections now. I thought that having rediss as the protocol would handle the tls-ing but I guess cluster mode handles things a little differently.

Thanks so much for the help!

acb avatar Feb 25 '22 16:02 acb

For me, I found both the :6379 and the extra s in rediss:// to be unnecessary. This worked:

const client = createCluster({
    rootNodes: [
        {
            url: "redis://redis-1-url",
        },
        {
            url: "redis://redis-2-url",
        },
        {
            url: "redis://redis-3-url",
        },
    ],
    defaults: {
        password: "password123",
        socket: { tls: true }
    }
});

killdash9 avatar Mar 29 '22 20:03 killdash9

I am still getting the Error MOVED 12218 ip:6379. Following is the code

import {createClient} from "redis"; // redis: ^4.0.1
const client = createClient({url: "redis://xyz.abc.clustercfg.use2.cache.amazonaws.com:6379"});
await client.connect();
console.log("client connected");
console.log(await client.ping());

I do get the output : "client connected" and "PONG". But when i do await client.get() or await client.set() i get the MOVED error.

nixRajan avatar Jun 09 '22 20:06 nixRajan

This is to way to connect for above issue.

    const redis = require('redis');

    const client = redis.createCluster({
      rootNodes: [
        {
          url: `redis://${ConfigurationEndpoint}:${port}`,
        },
      ],
      useReplicas: true,
     });

nixRajan avatar Jun 10 '22 20:06 nixRajan

Has this ever been addressed? I can't go to production with MemoryDB if this isn't fixed, and the approaches noted here result in an infinite loop of trying connection to localhost.

nibblesnbits avatar Aug 16 '23 09:08 nibblesnbits

@nibblesnbits none of these worked for you? https://github.com/redis/node-redis/issues/1782#issuecomment-1048241231 https://github.com/redis/node-redis/issues/1782#issuecomment-1003704869

wanna share some code?

leibale avatar Aug 16 '23 14:08 leibale

Well it looks like lack of sleep and attention to detail killed me. I missed that the call was createCluster, not createClient. 🤦‍♂️

But now I'm curious; is it more correct to use the cluster(s)' hostname, or the individual nodes? Seems logical that the latter is more correct.

nibblesnbits avatar Aug 16 '23 23:08 nibblesnbits

For anyone trying to connect to a MemoryDb cluster using TLS, this has proven to be the solution:

    const redisClient = redis.createCluster({
        rootNodes: [{
            url: `redis://${redisHost}:6379`
        }],
        useReplicas: true,
        defaults: {
            socket: { tls: true }
        }
    });

Well it looks like lack of sleep and attention to detail killed me. I missed that the call was createCluster, not createClient. 🤦‍♂️

But now I'm curious; is it more correct to use the cluster(s)' hostname, or the individual nodes? Seems logical that the latter is more correct.

AFAIK you want to use the cluster hostname(cluster endpoint), especially for MemoryDb where you may add or delete nodes dynamically.

timvi402 avatar Feb 27 '24 15:02 timvi402

For me, I found both the :6379 and the extra s in rediss:// to be unnecessary. This worked [...]

@killdash9 correct. Not sure what the implications are, but redis:// instead of rediss:// appears to work. Port seems unnecessary too.

With those changes in mind, my code now becomes:

import { createCluster } from "redis";

const socket = {
	tls: true,
	rejectUnauthorized: false, // Disable certificate validation
};

// Assuming you're using a Redis client that supports cluster mode and TLS
const redisClient = createCluster({
	rootNodes: [
		{
			url: "redis://<shard #1, node #1 endpoint>",
		},
		{
			url: "redis://<shard #2, node #1 endpoint>",
		},
		{
			url: "redis://<shard #3, node #1 endpoint>",
		},
	],
	defaults: {
		password: "TODO",
		socket,
	},
});

// Handle errors
redisClient.on("error", (err) => {
	console.error("Redis Cluster Error", err);
});

// Connect to the cluster
await redisClient.connect().catch(console.error);

Example of <shard #2, node #1 endpoint>

example-0002-001.example.abc123.use1.cache.amazonaws.com

cb-rnag avatar Mar 04 '24 22:03 cb-rnag