서비스 규모가 커지면 RDBMS 하나로는 감당이 안 되어 캐시 계층(Redis)을 붙이고, 비동기 작업 큐(RabbitMQ)와 대규모 이벤트 스트리밍(Kafka)을 도입하게 됩니다.
그런데 이런 미들웨어들은 잘 돌 때는 마법 같지만, 문제가 생기면 원인 파악이 정말 까다롭습니다. 에러 로그는 API 서버에서 찍히는데 실제 병목은 메시지 큐의 리밸런싱 지옥에 있거나, 레디스의 거대한 키 하나가 싱글 스레드를 쥐고 흔들고 있는 경우가 허다하기 때문이죠.
이번 글에서는 실무에서 Redis, RabbitMQ, Kafka를 운영하면서 밤샘 원인이 되곤 했던 대표적인 장애 증상들과, 이를 시스템적으로 방지하는 팁들을 정리해 보았습니다.
1. Redis: 싱글 스레드를 멈추게 만드는 ‘빅키(BigKey)’
Redis는 인메모리 기반으로 초당 수만 건의 연산을 가볍게 처리하지만, 싱글 스레드 이벤트 루프로 돌아간다는 점을 결코 잊어서는 안 됩니다.
빅키(BigKey)의 위험성
수백만 개의 원소를 가진 거대한 Hash나 Set 자료구조에 HGETALL이나 SMEMBERS 명령을 날리면, 레디스 프로세스가 그 요청을 처리하느라 수 초 동안 다른 모든 클라이언트의 요청을 블로킹(Blocking)해 버립니다. 다른 서버들에는 타임아웃이 와르르 터지게 되죠.
빅키 탐색과 안전한 삭제
# 1. 운영 환경에서 메모리를 가장 많이 먹는 상위 키 스캔 (논블로킹 스캔)
redis-cli -p 6379 --bigkeys
# 2. 크기를 가늠하기 위한 메모리 사용량 확인 (bytes 단위)
MEMORY USAGE user:activity:ranking
- 빅키 삭제 시 주의점: 수백 MB짜리 키를
DEL명령어로 지우면 지우는 동안 레디스가 멈춥니다. 반드시 백그라운드 스레드에서 비동기로 날려주는UNLINK명령어를 사용하세요.
메모리가 가득 찼을 때 (maxmemory-policy)
레디스 메모리가 꽉 차면 새 데이터를 쓸 때 OOM 에러가 터집니다. 캐시 용도라면 LRU/LFU 방출 정책을 켜두어야 안전합니다.
volatile-lru: 만료 시간(TTL)이 설정된 키 중에서 가장 오래 안 쓴 것부터 삭제.allkeys-lru: 모든 키 중에서 가장 오래 안 쓴 것부터 삭제. (순수 캐시 계층에 가장 추천)
2. RabbitMQ: 무한 루프 방지와 쿼럼 큐(Quorum Queues)
RabbitMQ는 가볍고 라우팅 기능이 강력해서 백그라운드 작업 큐로 많이 쓰입니다.
1) 데드 레터 교환기 (Dead Letter Exchange, DLX)
컨슈머가 메시지를 처리하다가 버그로 예외(Exception)를 뿜고 basic.nack(requeue=true)를 치면, 그 메시지가 큐 맨 앞에 다시 들어가고 ➔ 또 실패하고 ➔ 큐를 영원히 맴도는 포이즌 메시지(Poison Message) 무한 루프가 발생합니다.
- 해결책: 실패한 메시지는 3~5회 재시도 후 **데드 레터 큐(DLQ)**로 격리(Dead-lettering)시키고, 운영자가 사후 분석할 수 있도록 알림을 보내는 구조를 만들어야 본 큐가 막히지 않습니다.
2) 클래식 미러 큐 대신 쿼럼 큐(Quorum Queues)
과거에는 고가용성을 위해 클래식 미러 큐(Mirrored Queues)를 썼지만, 네트워크 단절 시 데이터 불일치와 동기화 부하가 컸습니다. Raft 합의 알고리즘 기반의 **쿼럼 큐(Quorum Queue)**를 쓰면 브로커 노드가 죽더라도 메시지 유실 없이 안전하게 복구됩니다.
3. Apache Kafka: 리밸런싱 지옥과 컨슈머 랙(Lag)
Kafka는 대량의 로그와 결제 이벤트를 초당 수십만 건 이상 유실 없이 쏟아부을 때 진가를 발휘하지만, 컨슈머 그룹 관리가 만만치 않습니다.
1) 컨슈머 리밸런싱 지옥(Rebalance Storm) 탈출
컨슈머가 파티션에서 한 번에 500개씩 메시지를 땡겨왔는데(max.poll.records), 비즈니스 로직 처리가 너무 오래 걸려서 다음 poll() 호출 주기가 타임아웃(max.poll.interval.ms, 기본 5분)을 넘겨버리면 어떻게 될까요?
카프카 브로커는 “어? 이 컨슈머 죽었네?”라고 판단하고 파티션을 다른 컨슈머에게 넘기는 **리밸런싱(Rebalance)**을 시작합니다. 그런데 다른 컨슈머도 똑같이 지연되면서 컨슈머 그룹 전체가 일은 안 하고 리밸런싱만 무한 반복하는 대참사가 터집니다.
- 해결책:
max.poll.records를 50~100 정도로 줄여서 한 번에 가져오는 일감을 가볍게 만듭니다.- 무거운 I/O 처리는 컨슈머 스레드 내부에서 별도의 워커 스레드 풀로 넘겨서 처리합니다.
2) 프로듀서 멱등성(Idempotence)과 유실 방지 설정
결제나 주문 이벤트처럼 중복이나 유실이 절대 없어야 하는 데이터는 프로듀서 설정 3총사를 세팅해 둡니다.
# 브로커 전원(ISR)이 디스크에 쓸 때까지 대기
acks=all
# 네트워크 일시 장애 시 자동 재시도
retries=2147483647
# 재시도로 인한 메시지 중복 생성을 원천 차단 (멱등성 프로듀서)
enable.idempotence=true
미들웨어 모니터링 한 줄 요약
- Redis:
INFO stats의instantaneous_ops_per_sec와used_memory_human상시 체크 - RabbitMQ: Unacked 메시지 수와 Consumers 개수 알람 걸기
- Kafka: Burrow나 Prometheus를 통해 Consumer Lag(소비 지연 건수) 모니터링하기
먼저 읽어볼 가이드
검색 유입이 많은 핵심 글부터 이어서 보세요.
- Vercel 배포하다가 빌드 터졌을 때: OOM부터 504 타임아웃까지 실전 해결법 로컬에선 잘 되던 프로젝트가 Vercel에 올리기만 하면 터지는 이유와 해결책. 빌드 메모리 초과(OOM), 504 게이트웨이 타임아웃, 환경 변수 누락, 빠른 롤백 팁을 정리했습니다.
- 실무에서 써본 AI 에이전트 & RAG 아키텍처: 삽질 줄이는 핵심 설계 팁 단순 챗봇을 넘어 스스로 생각하고 행동하는 AI 에이전트 만들기. LLM 파라미터(Temperature/Top-P) 튜닝, 프롬프트 캐싱, RAG vs 파인튜닝 선택 기준, LangGraph 상태 관리까지 실전 정리.
심사 대기 중에는 광고 대신 관련 가이드를 먼저 보여줍니다.