nestjs / terminus

Terminus module for Nest framework (node.js) :robot:
https://nestjs.com/
MIT License
670 stars 101 forks source link

Implementation of bullHealthcheck #1288

Open ajubin opened 3 years ago

ajubin commented 3 years ago

hi,

I have a working implementation of health check for @nest/bull

Should I PR here or release it in another place ?

To check liveliness, I use ´queue.client.ping()´ from redis io, is it a correct approach ?

TrejGun commented 3 years ago

Hi @ajubin

Maybe you have a health check for nest-redis as well?

ajubin commented 3 years ago

It's basically a check on whether the redis client answers to the ping. But the implementation is based on bull dependency injection to get client configuration

TrejGun commented 3 years ago

I can try to port it over to nest-redis

ajubin commented 3 years ago

Why not :) it shouldn't be too complicated (by injecting redis connection directly). I will keep you updated when the PR arrives :)

liaoliaots commented 3 years ago

I have a lib for redis with nestjs, support inject or get client via namespace, and health check.

https://github.com/liaoliaots/nestjs-redis

TrejGun commented 3 years ago

@liaoliaots This module is so good, thank you so much

BrunnerLivio commented 3 years ago

Sorry for the delay. I'd be happy to accept this as a PR! Though if possible, I'd recommend you to post the public API (how the user will use this healthcheck) here upfront, so I can already evaluate it and save you some time!

Lp-Francois commented 2 years ago

Hello! @ajubin any updates? :)

I was looking for a redis/bull implementation and I saw this issue

ArturChe commented 2 years ago

Hi guys, any updates?

Lp-Francois commented 2 years ago

🥲 up

KiwiKilian commented 2 years ago

Just for anyone else looking for a solution, I'm currently using the MicroserviceHealthIndicator from @nestjs/microservices likes this:

() =>
  this.microserviceHealthIndicator.pingCheck<RedisOptions>('redis', {
    transport: Transport.REDIS,
    options: {
      // Set your options just like you configure your BullModule
      host: this.configService.get('REDIS_HOST'),
      port: this.configService.get('REDIS_PORT'),
    },
  }),
stouch commented 2 years ago

I encounter a problem using url option. It looks like this works :

options: {
      host: this.configService.get('REDIS_HOST'),
      port: this.configService.get('REDIS_PORT'),
},

but not :

options: {
     url: 'redis://'+this.configService.get('REDIS_HOST')+':'+this.configService.get('REDIS_PORT'),
},

and I did not get why.

BrunnerLivio commented 2 years ago

@stouch I guess you’re using Nest v9? If so, there is a breaking change & urls are not supported with Redis anymore

https://docs.nestjs.com/migration-guide#redis-strategy-microservices

maxgaurav commented 1 year ago

Hey guys I know I am late to this but you can easily create a health check using the following service

import { Injectable } from '@nestjs/common';
import {
  HealthCheckError,
  HealthIndicator,
  HealthIndicatorResult,
} from '@nestjs/terminus';
import { QueueNames } from '../queue-names';
import { InjectQueue } from '@nestjs/bull';
import { Queue } from 'bull';

@Injectable()
export class QueueHealthService extends HealthIndicator {
  constructor(
    @InjectQueue(QueueNames.General) protected generalQueue: Queue,
    @InjectQueue(QueueNames.Emails) protected emailQueue: Queue,
    @InjectQueue(QueueNames.PushNotifications)
    protected pushNotificationQueue: Queue,
  ) {
    super();
  }

  public queueMap(queueName: QueueNames): Queue {
    switch (queueName) {
      case QueueNames.Emails:
        return this.emailQueue;
      case QueueNames.General:
        return this.generalQueue;
      case QueueNames.PushNotifications:
        return this.pushNotificationQueue;
    }
  }

  public async isQueueHealthy(
    queueName: QueueNames,
  ): Promise<HealthIndicatorResult> {
    const queue: Queue = this.queueMap(queueName);

    const isHealthy = !!(await queue.isReady());
    const data = {
      currentStatus: queue.client.status,
      totalJobs: await queue.count().catch(() => null),
    };

    const result = this.getStatus(
      this.healthKeyName(queueName),
      !!(await queue.isReady()),
      data,
    );

    if (!isHealthy) {
      throw new HealthCheckError(`Queue ${queueName} is not connected`, result);
    }

    return result;
  }

  /**
   * Generates the queue name
   * @param queue
   * @protected
   */
  protected healthKeyName(queue: QueueNames): `redis-queue:${QueueNames}` {
    return `redis-queue:${queue}`;
  }
}

and then in the Helthc controller


  @Get()
  @HealthCheck()
  public check() {
  const checks: HealthIndicatorFunction[] = [];
   checks.push(
      () => this.queueHealth.isQueueHealthy(QueueNames.General),
      () => this.queueHealth.isQueueHealthy(QueueNames.PushNotifications),
      () => this.queueHealth.isQueueHealthy(QueueNames.Emails),
    );

    return this.health.check(checks);
   }
cmclaughlin24 commented 1 year ago

Hi all,

I'm also late to this, but I came across this thread over the weekend while looking into implementing a RedisHealthIndicator for an application. This application uses @nestjs/bullmq and @nestjs/cache-manager. The issue with the existing MicroserviceHealthIndicator is that it doesn't support Redis Clusters (as far as I could tell).

I imagine it would look similar to this where the REDIS_OPTIONS_TYPE is specified in via a dynamic module. For simplicity, ioredis is used since Bullmq is now using it and Bull is in maintenance mode.

@Injectable()
export class RedisHealthIndicator
  extends HealthIndicator
  implements OnApplicationShutdown
{
  private connection: Redis | Cluster;

  constructor(@Inject(REDIS_OPTIONS_TOKEN) options: typeof REDIS_OPTIONS_TYPE) {
    super();
    this._connect(options);
  }

  /**
   * Checks if Redis responds in the amount of time specified in module options.
   * @param {string} key
   * @returns {Promise<HealthIndicatorResult>}
   */
  async pingCheck(key: string): Promise<HealthIndicatorResult> {
    let pingError: Error;

    try {
      if (!this.connection) {
        throw new Error('A Redis connection does not exist');
      }

      await this.connection.ping();
    } catch (error) {
      pingError = error;
    }

    const result = this.getStatus(key, !pingError, {
      error: pingError?.message,
    });

    if (!pingError) {
      return result;
    }

    throw new HealthCheckError('Redis Error', result);
  }

  /**
   * Establishes a connection to a Redis Server or Cluster.
   * @param {typeof REDIS_OPTIONS_TYPE} options
   */
  private _connect(options: typeof REDIS_OPTIONS_TYPE): void {
    if (options instanceof Cluster) {
      this.connection = options;
    } else {
      this.connection = new Redis(options.port, options.host, options.options);
    }
  }

  /**
   * Lifecycle hook method that disconnects from Redis during application shutdown. 
   * @param {string} signal
   */
  async onApplicationShutdown(signal?: string): Promise<void> {
    await this.connection?.quit();
  }
}

Would there be interest in the library having this functionality instead of requiring everyone to implement it separately?