NestJs + Bull jobs don't reach consumers - nestjs

I'm developing a NestJs service heavily utilizing bull. For quite a while everything was fine. But recently, I got a problem in all hosted environments - no jobs reach consumers. I monitor flow with a bull-monitor. Every job from every queue gets to the active state, hangs there for ~30 seconds, and gets failed with a message job stalled more than allowable limit.
The jobs are processed in sandboxed processes.
const queues = [
BullModule.registerQueueAsync({
name: PROCESS_NEW_EVENT,
useFactory: async () => ({
defaultJobOptions: JOBS_CONFIG,
processors: [
{
path: join(__dirname, 'jobs/handle-new-event.processor.js'),
concurrency: DEFAULT_CONCURRENCY,
},
],
}),
}),
];
#Module({
imports: [
AnotherModule,
...queues,
],
providers: [Repository, Service],
controllers: [Controller],
exports: `[Repository,` ...queues],
})
export class EventModule {}
If jobs/handle-new-event.processor.js the following code
export default async function processNewEvent(job) {
console.log('got job');
await executor(job);
console.log('job processed');
}
Then got job is never printed.
I thought it might be a problem with a Redis server. But everything is fine when I connect to the same server from the local environment.
I would appreciate any suggestions and directions.

Related

NestJS-Telegraf Telegram bot only handles one request at a time

I started building a simple telegram bot using nestjs-telegraf package.
Following the docs, I created the basic AppUpdate service:
#Update()
#Injectable()
export class AppUpdate {
#Command('random')
async onRandom(#Ctx() ctx: Context) {
const resp = await asyncRequestToGetData(ctx.message.text);
await ctx.reply(resp);
}
}
And provided it like this in app.module
#Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
TelegrafModule.forRootAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
token: configService.get<string>('TELEGRAM_BOT_TOKEN'),
}),
inject: [ConfigService],
}),
HttpModule,
],
controllers: [AppController],
providers: [AppUpdate, ...],
})
export class AppModule {}
The asyncRequestToGetData is an http call that sometimes takes 10 seconds or more.
And for some reason if another user tries to call the same command on the bot while request is processing, the onRandom is not called until the processing is finished.
Basically, not able to handle concurrent requests.
Was not able to find the reason/solution so far.

How to establish database connection within a Nest.js e2e test

I'm having an issue connecting to CosmosDB within a Nest.js e2e test.
I've been following the Nest.js documentation for creating e2e tests but it doesn't contain any documentation on connecting to a test database. Some of the articles I found (both on SO, and numerous other websites) stated that I should just initialize the database as I would within a regular module, but inside of the testing module, but it didn't work for me.
Module for CosmosDB that I'm using is #nestjs/azure-database.
I tried to initialize the AzureCosmosDbModule through the imports of the testing module as mentioned above but without any success.
Below is a snippet of what I tried. (I changed the actual name of the controller, module, etc. to a random "cats" one)
Environment variable loading is set up correctly, as it works outside of the tests.
describe('CatsController (e2e)', () => {
let app: INestApplication;
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [
ConfigModule.forRoot({
validationSchema: schema,
isGlobal: true,
}),
AzureCosmosDbModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (cfg: ConfigService) => ({
endpoint: cfg.get<string>('AZURE_COSMOS_DB_ENDPOINT'),
dbName: cfg.get<string>('AZURE_COSMOS_DB_TEST_NAME'),
key: cfg.get<string>('AZURE_COSMOS_DB_KEY'),
}),
inject: [ConfigService],
}),
CatsModule,
],
}).compile();
app = moduleFixture.createNestApplication();
app.setGlobalPrefix('api');
await app.init();
});
afterAll(async () => {
await app.close();
});
});
Error message I get after running e2e test.
[Nest] 2355 - 08/25/2022, 11:27:33 AM ERROR [AzureCosmosDbModule] Unable to connect to the cosmos db database. Retrying (1)...
[Nest] 2355 - 08/25/2022, 11:27:36 AM ERROR [AzureCosmosDbModule] Unable to connect to the cosmos db database. Retrying (2)...

Nest could not find Sequelize element when calling `app.close()`

We have a web server running Nest 8.0.8. With the given module setup:
#Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
cache: true,
validate,
load: [configuration]
}),
SequelizeModule.forRootAsync({
imports: [ConfigModule],
useFactory: (config: ConfigService) => config.get('db1'),
inject: [ConfigService]
}),
SequelizeModule.forRootAsync({
name: 'db2',
imports: [ConfigModule],
useFactory: (config: ConfigService) => config.get('db2'),
inject: [ConfigService],
}),
controllers: [...],
providers: [...]
})
To start the application, we have this main.ts file:
async function bootstrap() {
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter(...)
);
useContainer(app.select(AppModule), { fallbackOnErrors: true });
app.useGlobalPipes(
new ValidationPipe({
exceptionFactory: (errors) => new ValidationException(errors),
transform: true,
whitelist: true,
validationError: {
target: false,
value: false
}
})
);
app.setGlobalPrefix('v1');
app.useWebSocketAdapter(new RedisIoAdapter(app));
app.enableCors({ origin: '*' });
/**
* When SIGTERM, run functions before shutting down
*/
app.enableShutdownHooks();
if (process.env.SETUP) {
const service = app.get(FooService);
await service.setup();
await app.close();
return;
}
await app.listen(3000, '0.0.0.0');
}
bootstrap().then();
When running out unit tests, we use Nest to automatically configure the database. Initially after initializing the server with the command SETUP=true nest start that will force Nest to not listen the port and early return after running service.setup(). (note that this problem also happens when calling the app.listen(...) and app.close() functions)
The problem happens at the call app.close() we expect Nest to close all connections, including the two SequelizeModule of the 2 databases connected.
Unfortunaly Nest closes the first connection and tries to close the second, but the module (we suspect) is already closed. Giving the error:
This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason:
Error: Nest could not find Sequelize element (this provider does not exist in the current context)
at InstanceLinksHost.get (/Users/me/Repository/project_nest/node_modules/#nestjs/core/injector/instance-links-host.js:21:19)
at Object.find (/Users/me/Repository/project_nest/node_modules/#nestjs/core/injector/module-ref.js:38:55)
at Object.get (/Users/me/Repository/project_nest/node_modules/#nestjs/core/injector/module.js:345:28)
at SequelizeCoreModule.<anonymous> (/Users/me/Repository/project_nest/node_modules/#nestjs/sequelize/dist/sequelize-core.module.js:81:47)
at Generator.next (<anonymous>)
at /Users/me/Repository/project_nest/node_modules/#nestjs/sequelize/dist/sequelize-core.module.js:20:71
at new Promise (<anonymous>)
at __awaiter (/Users/me/Repository/project_nest/node_modules/#nestjs/sequelize/dist/sequelize-core.module.js:16:12)
at SequelizeCoreModule.onApplicationShutdown (/Users/me/Repository/project_nest/node_modules/#nestjs/sequelize/dist/sequelize-core.module.js:80:16)
at Object.callAppShutdownHook (/Users/me/Repository/project_nest/node_modules/#nestjs/core/hooks/on-app-shutdown.hook.js:51:35)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at NestApplication.callShutdownHook (/Users/me/Repository/project_nest/node_modules/#nestjs/core/nest-application-context.js:199:13)
at NestApplication.close (/Users/me/Repository/project_nest/node_modules/#nestjs/core/nest-application-context.js:83:9)
at bootstrap (/Users/me/Repository/project_nest/src/main.ts:143:5)
We initially thing that is a problem on Nest but we are not sure. This used to work in the past but somehow after a update the issue started happening and we cannot downgrade the library due some constraints with other dependencies.
I would like to know what can be done to either debug or sort the issue, we tried to debug the application but nothing points out to issues in our codebase. Could you please help?
Regards, Pedro
you need to give name for you second configuration SequelizeModule.
Example for your situation
SequelizeModule.forRootAsync({
name: 'db2',
imports: [ConfigModule],
useFactory: (config: ConfigService) => {
name: 'db2',
...config.get('db2')
},
inject: [ConfigService],
}),
The reason of this problem is getConnectionToken function which uses for getting connection by name from options. And if you not use specific name, SequelizeModule will use one connection between two modules and will close them both

How Redis Sentinel work with Bull queue (using NestJS)

I am working on a NestJs project using Bull queue.
Here is the code I am using to connect Redis, and it works well。
BullModule.registerQueueAsync(
{
name: 'test',
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
redis: {
host: configService.get('REDIS_ADDR'),
port: configService.get('REDIS_PORT'),
},
}),
inject: [ConfigService],
}
)
Now, I need to switch to using Redis Sentinel.
I searched online, but I could not find an appropriate tutorial.
I appreciate any help you can provide.

Nestjs - connect bull-board in a normal controller

I created an app using nest.js and bull.
I added bull-board package to monitor my queues, but in documentation, only one way to add it to the app is mount as middleware:
In main.ts:
app.use('/admin/queues', bullUI);
Is there any way to add bullUI in a normal nest controller, after jwt auth? Like:
#UseGuards(JwtAuthGuard)
#Get("queues")
activate() {
return UI
}
You can use any express middleware like this inside controllers, but maybe some cases cause errors like serving static files with Guard exception and etc.
#UseGuards(JwtAuthGuard)
#Get("queues/*")
activate(#Req() req, #Res() res) {
bullUI(req, res)
}
I've got this working via a middleware consumer, so something like this:
import { router } from 'bull-board';
#Module({
imports: [
NestBullModule.forRoot({ redis }),
],
providers: [],
})
export class BullModule {
configure(consumer: MiddlewareConsumer): void {
consumer
.apply(router)
.forRoutes('/admin/queues');
}
}
I'd like to extend the original answer of #JCF, mainly, because it's working and much easier to understand.
I am using not default bull, with #nestjs/queue, but an improved version of BullMQ from anchan828 repo, with NestJS decorators, but I guess in both cases, the result will be the same.
The queue.module file:
#Module({
imports: [
BullModule.forRoot({
options: {
connection: {
host: redisConfig.host,
port: redisConfig.port,
},
},
}),
/** DI all your queues and Redis connection */
BullModule.registerQueue('yourQueueName'),
],
controllers: [],
providers: [],
})
export class QueueModule {
constructor (
#BullQueueInject('yourQueueName')
private readonly queueOne: Queue,
) {
/** Add queues with adapter, one-by-one */
setQueues([new BullMQAdapter(this.queueOne, { readOnlyMode: false })])
}
configure(consumer: MiddlewareConsumer): void {
consumer
.apply(router)
.forRoutes('/admin/queues');
}
}
Then just add it, to parent AppModule, via import, like that:
I am not sure, that Redis connection is needed here, for parent AppModule
#Module({
imports: [
BullModule.forRoot({
options: {
connection: {
host: redisConfig.host,
port: redisConfig.port,
},
},
}),
QueueModule
],
controllers: [],
providers: [],
})
export class AppModule {}
run the main.js, and visit, localhost:8000/admin/queues

Resources