ioredis icon indicating copy to clipboard operation
ioredis copied to clipboard

ssubscribe within a cluster is not working properly

Open darrachequesne opened this issue 2 years ago • 9 comments

Hi! It seems there is an issue with the spublish()/ssubscribe() methods added in https://github.com/luin/ioredis/commit/6285e80ffb47564dc01d8e9940ff9a103bf70e2d:

import { Cluster } from "ioredis";

const clusterNodes = [
  {
    host: "localhost",
    port: 7000,
  },
  {
    host: "localhost",
    port: 7001,
  },
  {
    host: "localhost",
    port: 7002,
  },
  {
    host: "localhost",
    port: 7003,
  },
  {
    host: "localhost",
    port: 7004,
  },
  {
    host: "localhost",
    port: 7005,
  },
];

const pubClient = new Cluster(clusterNodes);
const subClient = pubClient.duplicate();

subClient.ssubscribe("foo", () => {
  pubClient.spublish("foo", "bar");
});

subClient.on("smessage", (_, message) => {
  console.log("got", message); // never received
});

The smessage event is not received. It works with classic publish()/subscribe() though:

subClient.subscribe("foo", () => {
  pubClient.publish("foo", "bar");
});

subClient.on("message", (_, message) => {
  console.log("got", message); // prints "got bar"
});

My docker-compose.yml, for reproducibility:

services:
  redis-cluster:
    image: grokzen/redis-cluster:7.0.10
    ports:
      - "7000-7005:7000-7005"

The test case here looks a bit weird, shouldn't it be something like:

const pub = new Cluster(options);
const ssub = new Cluster(options);

ssub.ssubscribe("test cluster", function () {
  pub.spublish("test shard channel", "hi");

  // node1.write(node1.findClientByName("ioredis-cluster(subscriber)"), [
  //   "smessage",
  //   "test shard channel",
  //   "hi",
  // ]);
});

Thanks in advance!

darrachequesne avatar May 13 '23 06:05 darrachequesne

Thanks for raising this up! Yeah we currently don't route messages to the right node and also don't subscribe to the right node. Should think about a solution for that.

luin avatar May 19 '23 10:05 luin

I'm facing the same problem, I'm not getting the 'smessage' event when I register the sSubscribe function.

NurimOnsemiro avatar Jun 05 '23 07:06 NurimOnsemiro

With a total of 3 Redis servers (ports 6379, 6380, 6381) deployed in a Redis Cluster using Docker Compose, I executed the code below.

import { Cluster, Redis } from 'ioredis'

async function getRedisClient(): Promise<Cluster> {
  return new Promise(async (resolve, reject) => {
    const redisClient = new Redis.Cluster([
      {
        host: 'localhost',
        port: 6379
      }
  ], {
      scaleReads: 'slave',
      retryDelayOnMoved: 100,
      redisOptions: {
        username: 'default',
        password: 'redispw',
        tls: undefined
      },
    })
    redisClient.on('error', (err: Error) => {
      console.error('[Ioredis] on ERROR', err)
    })
    redisClient.on('connect', async () => {
      console.log('Redis cluster connected')
      resolve(redisClient)
    })
  })
}

async function main() {
  const publisher = await getRedisClient()
  const subscriber = await getRedisClient()

  subscriber.on('smessage', (channel: string, message: string) => {
    console.log('smessage', channel, message)
  })

  subscriber.on('message', (channel: string, message: string) => {
    console.log('message', channel, message)
  })

  await subscriber.ssubscribe('hello1') // Too many redirection error (127.0.0.1:6380)
  await subscriber.ssubscribe('hello2') // Too many redirection error (127.0.0.1:6381)
  await subscriber.ssubscribe('hello3') // ok (127.0.0.1:6379)

  await publisher.spublish('hello3', '{"name": "honsemiro"}')
}
main()

Of these, only the 'hello3' channel was successful in subscribing to the shard channel. My guess is that there are different shards that are assigned based on the checksum value, and that's why it subscribes to certain shards and not others. Here's the error message.

Redis cluster connected
Redis cluster connected
c:\Users\<redacted>\Documents\nodejs\tsc_test_01\node_modules\ioredis\built\cluster\index.js:508
            handlers.maxRedirections(new Error("Too many Cluster redirections. Last error: " + error));
                                     ^
Error: Too many Cluster redirections. Last error: ReplyError: MOVED 11613 127.0.0.1:6381
    at EventEmitter.handleError (c:\Users\<redacted>\Documents\nodejs\tsc_test_01\node_modules\ioredis\built\cluster\index.js:508:38)
    at Command.command.reject (c:\Users\<redacted>\Documents\nodejs\tsc_test_01\node_modules\ioredis\built\cluster\index.js:362:23)
    at EventEmitter.handleReconnection (c:\Users\<redacted>\Documents\nodejs\tsc_test_01\node_modules\ioredis\built\Redis.js:509:30)
    at DataHandler.returnError (c:\Users\<redacted>\Documents\nodejs\tsc_test_01\node_modules\ioredis\built\DataHandler.js:41:20)
    at JavascriptRedisParser.returnError (c:\Users\<redacted>\Documents\nodejs\tsc_test_01\node_modules\ioredis\built\DataHandler.js:15:22)
    at JavascriptRedisParser.execute (c:\Users\<redacted>\Documents\nodejs\tsc_test_01\node_modules\redis-parser\lib\parser.js:542:14)
    at Socket.<anonymous> (c:\Users\<redacted>\Documents\nodejs\tsc_test_01\node_modules\ioredis\built\DataHandler.js:25:20)
    at Socket.emit (node:events:513:28)
    at Socket.emit (node:domain:489:12)
    at addChunk (node:internal/streams/readable:315:12)

NurimOnsemiro avatar Jul 03 '23 02:07 NurimOnsemiro

is there any updates on this? @luin are there any workarounds until this will be supported?

m4tty-d avatar Jul 23 '23 16:07 m4tty-d

If it's helpful to anyone reading this -- the library node-redis (v4) supports sharded pubsub commands properly (SSUBSCRIBE, SPUBLISH, SUNSUBCRIBE)

For my project I had to migrate off of ioredis and use node-redis instead because of this issue with ioredis not supporting sharded pubsub commands

BrentLayne avatar Nov 01 '23 17:11 BrentLayne

Any news about it ? I'd prefer not to migrate to node-redis.

tominou avatar Jun 13 '24 19:06 tominou

@tominou we ended up using node-redis for this part

m4tty-d avatar Jun 28 '24 15:06 m4tty-d