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

Implementation of bullHealthcheck #1288

Open
ajubin opened this issue Jun 18, 2021 · 15 comments
Open

Implementation of bullHealthcheck #1288

ajubin opened this issue Jun 18, 2021 · 15 comments

Comments

@ajubin
Copy link

ajubin commented Jun 18, 2021

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
Copy link

TrejGun commented Jun 20, 2021

Hi @ajubin

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

@ajubin
Copy link
Author

ajubin commented Jun 20, 2021

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
Copy link

TrejGun commented Jun 20, 2021

I can try to port it over to nest-redis

@ajubin
Copy link
Author

ajubin commented Jun 21, 2021

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

@liaoliaots
Copy link

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
Copy link

TrejGun commented Jun 30, 2021

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

@BrunnerLivio
Copy link
Member

BrunnerLivio commented Jul 8, 2021

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

This comment was marked as spam.

@ArturChe

This comment was marked as spam.

@Lp-Francois

This comment was marked as spam.

@KiwiKilian
Copy link

KiwiKilian commented Jun 21, 2022

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
Copy link

stouch commented Jul 19, 2022

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
Copy link
Member

@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
Copy link

maxgaurav commented Apr 27, 2023

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
Copy link

cmclaughlin24 commented Jul 5, 2023

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

10 participants