Infra · Redis · BullMQ

Master + Replica vs 단일 — BullMQ 발송 시스템 패턴 분석

2026 베스트 프랙티스 + Rinda 실측 데이터 기반 권장

📅 2026-06-05 🔬 beta + alpha 실측 📦 redis 8.6 / bullmq

한 줄 결론

Master + Replica + Sentinel 패턴은 BullMQ 발송 시스템에 over-engineering. 단일 인스턴스 + AOF + EBS snapshot 으로 충분하며, BullMQ 의 멱등성·재시도 정책이 1-2분 downtime 을 흡수합니다. Sentinel HA 의 ~30초 자동 failover 보다 메모리 2배·컨테이너 4개·race-condition 위험 cost 가 큽니다. alpha 도 beta 처럼 단일로 일관화 권장.

1. 현재 상태

환경구조평가
alpha (dev/test) master + replica + Sentinel x3 (quorum=2) HA, 운영 복잡
beta (prod) master 1개 + AOF + restart:always 단순, BullMQ 적합
역구조 — prod(beta)가 single, dev(alpha)가 HA. 일반적으로 prod 가 더 강한 가용성이어야 하지만, BullMQ 시나리오에서는 단일이 더 안전. 일관성도 깨짐.

2. 2026 패턴 비교

패턴 장점 단점 BullMQ 적합
단일 + AOF + EBS snapshot 단순, 메모리 1배, 운영 복잡도 ↓ RTO ~1-2분 (manual restart) ★★★★★
BullMQ 공식 권장
Sentinel HA (alpha 현재) 자동 failover ~30s 메모리 2배, 컨테이너 4개, split-brain 위험, race condition ★★☆☆☆
over-engineering
Redis Cluster (sharded) horizontal scale BullMQ 비호환 (Lua multi-key, hash tag 강제) ★☆☆☆☆
비권장
Managed (ElastiCache/Upstash) 자동 backup/maintenance cost ($50-300/월) ★★★★☆
Dragonfly (Redis 호환 멀티스레드) 25배 throughput, 메모리 효율 edge command 일부 차이 ★★★★☆

3. BullMQ 컨텍스트에서 Sentinel 의 함정

  1. Lock + Stalled job race condition — failover 동안 bull:*:stalled-check lock 이 끊김 → 두 worker 가 같은 job 처리 → 이메일 중복 발송 위험 (멱등성 강제 필수)
  2. evalsha script cache loss — replica → master promote 시 script SHA 사라짐 → NOSCRIPT 에러 + 재로드 overhead
  3. AOF rewrite + replica sync 충돌 — AOF rewrite 중 replica sync 가 delay → 일시적 lag
  4. 오늘 발생한 OOM 은 HA 와 무관 — 메모리 한도 자체가 부족했던 것 (4G → 8G 해결). Sentinel 이 막아주지 않음.

4. 2026 베스트 프랙티스 (영업 메일 발송 시스템)

Redis 공식 + BullMQ 공식 가이드 종합:

✅ 단일 Redis 인스턴스
   --appendonly yes
   --appendfsync everysec       # 1초 데이터 유실 허용
   --maxmemory 8gb              # 또는 워크로드 기반
   --maxmemory-policy noeviction
   --save 900 1                 # 보조 RDB snapshot
   restart: always              # docker restart on OOM
   oom_score_adj: -900          # OOM killer 보호
   mem_swappiness: 0            # swap I/O 절대 금지
   memswap_limit == mem_limit   # swap 영역 0

✅ EBS gp3 snapshot 1시간 cron    # 디스크 레벨 backup
✅ BullMQ idempotent job design    # at-least-once 보장
✅ Worker auto-reconnect            # Redis restart 동안 재연결
근거: BullMQ 공식 문서 — "A single Redis instance is sufficient for most use cases". 영업 메일은 retry-safe (멱등 키 강제), 1-2분 downtime 허용. Sentinel split-brain 위험 > 자동 failover 가치.

5. 권장 액션

P0 단기 현재 변경 안정화

  • 적용 완료: maxmemory 4G → 8G, 컨테이너 5G → 10G, mem_swappiness: 0, memswap_limit == mem_limit
  • 1-2일 모니터 — OOM 재발 없는지, 메모리 추세 확인

P1 중기 (별도 PR)

  • alpha 의 redis-replica + Sentinel x3 제거 → beta 와 같은 single 구조로 통일
  • BullMQ retention 강화sequence-email completed 1000 → 100, repeatable scheduler pruning 확대 (현재 796개 stale)
  • 다중 SES 계정 라운드로빈 fix — pool 확장 후 기존 enrollment 재할당 로직 추가. 현재 56만 delayed jobs 적체의 근본 원인.

P2 장기

  • ElastiCache 또는 Dragonfly 검토 — 운영 부담 ↓ + 성능 ↑
  • BullMQ → 분산 큐 (Kafka / SQS) 마이그레이션 검토 (트래픽 10배 시)

6. 오늘 OOM 사고 정리

측정
Redis used / max4.00 GB / 4.00 GB (100%)
maxmemory-policynoeviction — 가득 차면 새 write 거부
bull:sequence-email:* keys582,744
bull:sequence-email:delayed ZSET562,956 jobs (≈3.5 GB)
lead-on-demand-enrich:wait68,759 적체
repeatable scheduler796 (정상 ~10)

배포 실패 원인은 우리 PR 과 무관 — Redis 가 며칠 동안 100% full 상태였고, zero-downtime deploy 의 새 worker 시동 시 BullMQ Lua script enqueue 가 OOM 거부 → worker error → healthcheck 240s timeout.