dabroek / node-cache-manager-redis-store

Redis store for node-cache-manager using node_redis.
MIT License
170 stars 59 forks source link

How to manage retry_strategy in store ? #33

Open lakshmipriyamukundan opened 3 years ago

lakshmipriyamukundan commented 3 years ago

HappyHappy1996 commented 2 years ago

this is how I do this with cache-manager-redis-store of version 2:

// `redis-cache.module.ts` file
import {
  CacheModule,
  CACHE_MANAGER,
  Inject,
  Logger,
  Module,
  OnModuleDestroy,
} from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import type { ClientOpts } from 'redis';
import { Cache } from 'cache-manager';

import { RedisCacheStoreProvider } from './redis-cache-store.provider';

@Module({
  imports: [
    ConfigModule,
    CacheModule.registerAsync<ClientOpts>({
      imports: [RedisCacheModule],
      useFactory: async (redisCacheStoreProvider: RedisCacheStoreProvider) => {
        return {
          store: await redisCacheStoreProvider.build(),
        };
      },
      inject: [RedisCacheStoreProvider],
      isGlobal: true,
    }),
  ],
  providers: [RedisCacheStoreProvider],
  exports: [RedisCacheStoreProvider],
})
export class RedisCacheModule implements OnModuleDestroy {
  private readonly logger: Logger = new Logger(RedisCacheModule.name);

  constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}

  onModuleDestroy(): void {
    this.logger.log('Closing Redis cache connection');
    const client = this.cacheManager.store.getClient();
    // https://github.com/nestjs/nest/issues/8020
    client.quit();
  }
}

// ---------------------------------------------------------------------------------------
// `redis-cache-store.provider.ts` file

import redisStore from 'cache-manager-redis-store';
import { Inject, Logger, Injectable } from '@nestjs/common';
import { ConfigType } from '@nestjs/config';

import { redisRetryCallback } from './redis-retry-callback';

import { redisConfig } from 'config';

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

  constructor(
    @Inject(redisConfig.KEY)
    private config: ConfigType<typeof redisConfig>,
  ) {}

  async build() {
    const store = await redisStore.create({
      host: this.config.host,
      port: this.config.port,
      // https://github.com/node-cache-manager/node-cache-manager/issues/51
      enable_offline_queue: false,
      retryStrategy: redisRetryCallback(this.config, this.logger),
    });

    return store;
  }
}

// ---------------------------------------------------------------------------------------
// `redis-retry-callback.ts` file
import { RetryStrategyOptions } from '@nestjs/microservices/external/redis.interface';
import { Logger } from '@nestjs/common';
import * as Sentry from '@sentry/node';

import { IRedisConfig } from 'config';

interface RedisRetryStrategyOptions
  // in fact `total_retry_time` and `times_connected` properties are given in camelCase notation - idk why
  extends Omit<RetryStrategyOptions, 'total_retry_time' | 'times_connected'> {
  // so we manually override the interface
  totalRetryTime: number;
  timesConnected: number;
}

export const redisRetryCallback =
  (redisConfig: IRedisConfig, logger: Logger) =>
  ({ error, attempt, totalRetryTime, timesConnected }: RedisRetryStrategyOptions) => {
    const { retryDelay, retryAttempts } = redisConfig;

    if (attempt > retryAttempts) {
      logger.error(`Redis connection retry time exhausted`, {
        totalRetryTime,
        timesConnected,
        retryDelay,
        retryAttempts,
      });
      throw new Error('Redis connection retry time exhausted');
    }

    if (error) {
      logger.error(`Redis connection error. Attempt: ${attempt}. Error: ${error}`);
      Sentry.captureMessage('Redis connection error.', {
        extra: {
          attempt,
          totalRetryTime,
          timesConnected,
          retryDelay,
          retryAttempts,
        },
      });
    }
    return retryDelay;
  };