Skip to content
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

Discussion: migrate from "unmaintained" redisio to node-redis #555

Open
vlrevolution opened this issue Nov 23, 2024 · 2 comments
Open

Discussion: migrate from "unmaintained" redisio to node-redis #555

vlrevolution opened this issue Nov 23, 2024 · 2 comments
Labels
bug Something isn't working

Comments

@vlrevolution
Copy link

vlrevolution commented Nov 23, 2024

are you guys aware of issue with redisio and cluster that has been present for several years and the issue is still open?
Please review this carefully:

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.

I propose we need to swith to node-redis which is maintained and works correctly

@vlrevolution vlrevolution added the bug Something isn't working label Nov 23, 2024
@vlrevolution
Copy link
Author

vlrevolution commented Nov 23, 2024

I propose something like this as a starting point for others that run into wanting to scale properly with sharded replicas:

// src/redis/redis.module.ts
import { DynamicModule, Global, Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import {
  createCluster,
  RedisClusterType,
  RedisFunctions,
  RedisModules,
  RedisScripts,
} from 'redis';

export const REDIS_CLIENT = 'REDIS_CLIENT';

@Global()
@Module({})
export class RedisModule {
  static forRoot(): DynamicModule {
    return {
      module: RedisModule,
      imports: [ConfigModule],
      providers: [
        {
          provide: REDIS_CLIENT,
          useFactory: async (
            configService: ConfigService,
          ): Promise<
            RedisClusterType<RedisModules, RedisFunctions, RedisScripts>
          > => {
            const nodes =
              configService
                .get<string>('REDIS_NODES')
                ?.split(',')
                .map((node) => {
                  const [host, port] = node.split(':');
                  return {
                    url: `redis://${host}:${port}`,
                  };
                }) || [];

            const cluster = createCluster({
              rootNodes: nodes,
              defaults: {
                password: configService.get('REDIS_PASSWORD'),
                socket: {
                  connectTimeout: 14444,
                  reconnectStrategy: (retries) => {
                    if (retries > 22) {
                      return new Error('Redis cluster connection failed');
                    }
                    return Math.min(retries * 77, 2222);
                  },
                },
              },
            });

            cluster.on('error', (err) => {
              console.error('Redis Cluster Error:', err);
            });

            cluster.on('connect', () => {
              console.log('Redis Cluster Connected');
            });

            await cluster.connect();
            return cluster;
          },
          inject: [ConfigService],
        },
      ],
      exports: [REDIS_CLIENT],
    };
  }
}

Then include it in your app.module.ts:

...
import { RedisModule } from './redis/redis.module';

@Module({
  imports: [
    ConfigModule.forRoot(),  
    RedisModule.forRoot(),
  ],
})
export class AppModule {}

And then use it in services:

import {
  Inject,
  Injectable,
  Logger,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { REDIS_CLIENT } from 'src/redis/redis.module';
import {
  RedisClusterType,
  RedisFunctions,
  RedisModules,
  RedisScripts,
} from 'redis';

@Injectable()
export default class MyService{
  private readonly logger = new Logger(MyService.name);

  constructor(
    @Inject(REDIS_CLIENT)
    private readonly redis: RedisClusterType<
      RedisModules,
      RedisFunctions,
      RedisScripts
    >,
  ) {}

  async deleteKeysByPattern(pattern: string) {
    this.logger.log(
      `Initiating deletion for keys matching pattern: ${pattern}`,
    );

    // In cluster mode, we need to scan each master node
    const keysToDelete = new Set<string>();

    // Get all keys from each master node
    await Promise.all(
      this.redis.masters.map(async (master) => {
        const client = await this.redis.nodeClient(master);
        for await (const key of client.scanIterator({
          MATCH: pattern,
          COUNT: 100,
        })) {
          keysToDelete.add(key);
        }
      }),
    );

    // Delete all found keys
    if (keysToDelete.size > 0) {
      const keys = Array.from(keysToDelete);
      this.logger.log(`Deleting keys: ${keys.join(', ')}`);
      await Promise.all(keys.map((key) => this.redis.del(key)));
    }

    this.logger.log(`Deletion for keys matching pattern: ${pattern} completed`);
  }
}

And example values you might want to have in your .env:

REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=SUPER_SECRET_PASSWORD
REDIS_NODES=localhost:7001,localhost:7002,localhost:7003,localhost:7004,localhost:7005,localhost:7006

And finally, here's a nice guide to help you setup redis with shards in cluster in case you are using docker:
https://medium.com/@ahmettuncertr/redis-cluster-using-docker-1c8458a93d4b

@liaoliaots
Copy link
Owner

liaoliaots commented Dec 14, 2024

No doubt, this is a work in progress.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants