JOB
30s
BullMQ Timeout
stalled • attempt • worker

BullMQ Job stalled với ElastiCache dù Redis vẫn chạy

Phân tích nguyên nhân và cách khắc phục lỗi BullMQ timeout, Job stalled khi dùng AWS ElastiCache (Redis).

7 phút đọc15/06/2026

Vấn đề gặp phải

Khi deploy NestJS application lên production sử dụng BullMQ với AWS ElastiCache (Redis), chúng ta thường gặp lỗi job bị stall dù Redis vẫn đang hoạt động bình thường:

Error: Job stalled
  attempts made: 1
  timeout of 30000ms exceeded

Job queue vẫn nhận message, nhưng worker bị disconnect sau một thời gian idle.

Nguyên nhân

AWS ElastiCache có cấu hình TCP keepalive timeout mặc định là 350 giây. Nếu connection không có activity trong khoảng thời gian đó, ElastiCache sẽ đóng connection từ phía server.

BullMQ sử dụng long-lived Redis connections để lắng nghe các blocking operations (BRPOP, BLMOVE). Nếu không có job nào trong queue, connection sẽ idle và bị ElastiCache đóng mà client không hay biết.

Khi worker cố gắng dùng connection đã bị đóng này, nó sẽ throw timeout error.

Cấu hình BullMQ đúng cách

Có hai cách khắc phục:

Enable TCP Keepalive

import { BullModule } from '@nestjs/bullmq';

BullModule.forRoot({
  connection: {
    host: process.env.REDIS_HOST,
    port: 6379,
    keepAlive: 30000,       // gửi keepalive mỗi 30s
    connectTimeout: 10000,
    maxRetriesPerRequest: 3,
  },
});

Sử dụng ioredis với explicit options

import Redis from 'ioredis';

const redisConnection = new Redis({
  host: process.env.REDIS_HOST,
  port: 6379,
  keepAlive: 30000,
  enableOfflineQueue: false,
  retryStrategy: (times) => Math.min(times * 50, 2000),
  reconnectOnError: (err) => {
    const targetError = 'READONLY';
    return err.message.includes(targetError);
  },
});

BullModule.forRoot({ connection: redisConnection });

Tăng timeout ở ElastiCache

Trong ElastiCache parameter group, tăng tcp-keepalive lên 60 và timeout về 0 (disable):

tcp-keepalive = 60
timeout = 0

Kết luận

Vấn đề không phải ở ElastiCache hay BullMQ — mà ở sự không khớp giữa TCP idle timeout của AWS và connection lifecycle của BullMQ. Giải pháp tốt nhất là:

  1. Set keepAlive: 30000 trong ioredis config
  2. Tăng connectTimeout lên 10-15s
  3. Implement proper retry strategy

Với cấu hình này, worker sẽ chủ động gửi TCP keepalive packets trước khi ElastiCache đóng connection.