Redis 트러블슈팅 가이드

마지막 업데이트 2026-07-29

Redis 트러블슈팅 가이드: 입력: 🏗 1. 시스템 개요, 주요 처리 단계: 🔑 4. 키 네이밍 전체 표, 결과: ⚙️ 9. 자주 쓰는 명령어 모음 흐름
동작 흐름 요약
  1. 입력: 🏗 1. 시스템 개요
  2. 주요 처리 단계: 🔑 4. 키 네이밍 전체 표
  3. 결과: ⚙️ 9. 자주 쓰는 명령어 모음
문서 읽는 법 · 설명식

이 문서는 이렇게 읽으면 됩니다

Redis 트러블슈팅 가이드의 핵심을 설명식으로 먼저 안내합니다. 기술적 결론과 원문 근거는 아래 본문에 보존되어 있습니다.

핵심 흐름 펼쳐 보기
  1. 비유와 핵심 질문으로 먼저 전체 구조를 잡습니다.
  2. 실제 컴포넌트·파일·데이터 흐름을 따라 내려갑니다.
  3. 코드 라인과 주의사항에서 구현 근거를 확인합니다.
  • 목차
  • 🏗 1. 시스템 개요
  • 🔌 2. 연결 설정 & 환경 변수
대상: apps/socket 클라이언트: ioredis 5.10.1 작성: 2026-05-24
한 줄 요약. apps/socket은 Redis에 강제 의존하며 메모리 폴백이 없습니다. REDIS_URL이 없거나 PING이 5초 내 응답하지 않으면 서버가 부팅 실패합니다. Redis 장애 = Socket.io 서버 전체 마비입니다.

목차

  1. 시스템 개요
  2. 연결 설정 & 환경 변수
  3. Redis 사용처
  4. 키 네이밍 전체 표
  5. 모니터링 & 헬스체크
  6. 장애 시 영향 범위
  7. 트러블슈팅 체크리스트
  8. 긴급 대응 런북
  9. 자주 쓰는 명령어 모음

🏗 1. 시스템 개요

apps/socket은 다중 인스턴스 Socket.io 서버이며 Redis를 다음 목적으로 사용합니다.

관련 클라이언트

3개의 ioredis 인스턴스가 동시 운영됩니다.

클라이언트용도
main모든 일반 읽기/쓰기 (room, peer, settings)
pubSocket.io Redis adapter - 발행
subSocket.io Redis adapter - 구독

핵심 코드 위치

🔌 2. 연결 설정 & 환경 변수

필수 환경 변수

변수기본값설명필수
REDIS_URLredis://127.0.0.1:6379Redis 연결 URL필수
STAGElocal키 prefix 구성 (ppi:socket:{STAGE}:*)필수
REDIS_TLSfalseTLS 활성화 여부
SOCKETIO_ADAPTERredisredis 또는 memory
INSTANCE_HEARTBEAT_INTERVAL_MS10000Heartbeat 갱신 주기
INSTANCE_HEARTBEAT_TTL_SECONDS30Heartbeat 키 TTL
ROOM_META_SOURCEredisredis / both / memory
REAPER_ENABLEDtrueDead instance 정리 활성화
RECORDING_PENDING_TTL_SECONDS3600녹화 복구 메타 TTL

연결 옵션 (ioredis)

{
  lazyConnect: true,
  maxRetriesPerRequest: 3,
  enableReadyCheck: true,
  enableOfflineQueue: false,  // disconnect 중 명령 큐 비활성
  tls: REDIS_TLS === "true" ? {} : undefined,
}

부팅 시 동작 (fail-fast)

  1. REDIS_URL 없으면 → throw (부팅 실패)
  2. main 클라이언트 connect
  3. PING 보내고 5초 내 PONG 안 오면 → throw
  4. pub/sub 클라이언트 connect (adapter 활성화 시)
  5. Heartbeat 타이머 시작 (10초 주기)
키 prefix 규칙. 모든 키는 ppi:socket:{STAGE}: 접두사를 가집니다. 예: ppi:socket:prod:room:abc123, ppi:socket:staging:peer:xyz.

📦 3. Redis 사용처

3.1 Socket.io Redis Adapter

여러 Socket.io 인스턴스 간 메시지 브로드캐스트. 내부적으로 {prefix}socket.io#* 채널 사용.

장애 시: 같은 인스턴스 내부 메시지만 동작, 다른 인스턴스로 가는 메시지 손실.

3.2 Room 메타데이터 (Hash)

키: {prefix}room:{roomId}

필드: roomId, createdAt, peerCount, guest, hosts (JSON), guestState (JSON), routerInstanceId, autoTransitionEnabled, realtimeModelVersion, vadSettings, externalSttEnabled, autoResponseEnabled, isTest, groupId

TTL: 없음. removeRoom() 호출까지 유지.

3.3 Room Instance Owner (String + CAS)

키: {prefix}room:{roomId}:instance / TTL 90초

역할. 같은 room의 모든 peer를 동일 instance로 라우팅. Lua 스크립트 PIN_ROOM_LUA로 takeover까지 원자 처리.

3.4 Peer 정보 (Hash + 역 인덱스)

키: {prefix}peer:{peerId}

역 인덱스 Set (검색용).

3.5 Heartbeat (Sorted Set + String)

Instance 생존성 추적. Reaper가 이걸 기준으로 dead instance 판정.

갱신 주기: 10초. Stale 판정: 45초 이상 미갱신.

3.6 Reaper Leader Lease (String)

키: {prefix}reaper:leader / TTL 90초

여러 instance가 동시에 reaper를 돌리지 않도록 leader election. SET NX EX로 취득, CAS Lua로 refresh.

3.7 Reaper (Dead instance 정리)

주기: 60초. 작업.

  1. Stale instance 찾기 (heartbeat 60초 이상 부재)
  2. 2-phase: 60초 후 재확인 (false positive 방지)
  3. Owner CAS로 room 삭제 (takeover된 room은 skip)
  4. Peer 정리
  5. Instance 인덱스 정리

3.8 Recording Pending (String, TTL 3600s)

키: {prefix}recording:pending:{roomId}

녹화 중 서버 재시작 시 메타데이터 복구. TTL 만료 시 자동 폐기.

3.9 Settings Store (String)

키: {prefix}settings:ambientNoiseEnabled

전역 환경 소음 알림 토글. 유일하게 fail-open 패턴(Redis 미연결 시 true 반환).

3.10 Pending Disconnect (String, JSON)

키: {prefix}pending-disconnect:pending:{key}

게스트 disconnect 후 grace period 동안 재접속 대기. TTL은 GRACE_PERIOD_MS.

🔑 4. 키 네이밍 전체 표

모든 키는 ppi:socket:{STAGE}: prefix를 가집니다. 표는 prefix를 생략한 형태로 표기.

키 패턴 타입 TTL 용도
room:{roomId}Hash-Room 메타데이터
room:{roomId}:instanceString90sRoom owner instance ID
room:{roomId}:guestString-게스트 peerId
room:{roomId}:peersSet-Room의 모든 peer
room:{roomId}:hostsSet-호스트 peer
rooms:activeSet-활성 room ID 목록
peer:{peerId}Hash-Peer 정보
socket:{socketId}:peersSet-Socket ID 역 인덱스
instance:{instanceId}:peersSet-Instance가 가진 peer
instance:{instanceId}:roomsSet-Instance가 소유한 room
instance:{instanceId}:heartbeatString30sInstance 생존 신호
instances:heartbeatZset-모든 instance heartbeat (score=timestamp)
monitor:{roomId}Set-Room 모니터링 peer
group-monitor:{groupId}Set-Group 모니터링 peer
reaper:leaderString90sReaper leader instance ID
pending-disconnect:pending:{key}Stringgrace대기 중 disconnect
recording:pending:{roomId}String3600s녹화 메타 복구
settings:ambientNoiseEnabledString-환경 소음 토글
socket.io#...Pub/Sub-Socket.io adapter 채널

📊 5. 모니터링 & 헬스체크

Prometheus 메트릭

메트릭설명알람 기준 예시
ppi_socket_redis_adapter_connected pub/sub 연결 상태 (1=연결, 0=장애) 5분 이상 0 → 즉시 알람
ppi_socket_redis_op_duration_seconds Redis 명령 latency (operation별) p99 > 100ms → 경고
ppi_socket_instance_reaper_runs_total Reaper 실행 횟수 10분 이상 증가 없음 → 경고
ppi_socket_instance_reaper_killed_rooms_total Reaper가 정리한 room 수 급증 시 false positive 의심
ppi_socket_room_pin_redirect_total JOIN_ROOM redirect 횟수 비정상 급증 → 라우팅 이슈
ppi_socket_room_meta_mismatch_total 메모리 vs Redis 불일치 (both 모드) 지속 증가 → 동기화 버그

헬스체크 엔드포인트

/health에서 pingRedis() 호출. PONG 응답 시 healthy.

Web Admin Debug 페이지

/app/main/debug/redis에서 실시간 확인 가능.

⚠️ 6. 장애 시 영향 범위

Redis 완전 장애 시 영향. 대부분 FATAL입니다.

기능영향설명
부팅 FATAL PING 5초 타임아웃 → 부팅 실패. 컨테이너 재시작 루프
Room 메타 조회 FATAL getAllActiveRooms() → null. 방 목록 표시 안 됨
Peer 추적 FATAL upsertPeer/getPeer 실패 → peer 동기화 깨짐
다중 인스턴스 라우팅 FATAL tryPinRoom() 실패 → redirect 무한 루프 위험
Socket.io 브로드캐스트 Degraded 같은 instance 내부만 동작. 크로스 인스턴스 메시지 손실
Reaper (정리) Degraded 죽은 instance 자원 정리 불가 → 고아 peer/room 축적
환경 소음 토글 OK 유일하게 fail-open (기본값 true 반환)

🔍 7. 트러블슈팅 체크리스트

7.1 Redis 연결 실패 긴급

증상

로그에 REDIS_URL is required 또는 Redis PING timeout. 컨테이너 재시작 반복.

확인 순서
  1. 환경 변수 확인. echo $REDIS_URL && echo $STAGE
  2. Redis 도달성 테스트. redis-cli -u "$REDIS_URL" PING
  3. 네트워크 도달성. nc -zv <host> <port>
  4. 보안 그룹/방화벽 (EC2 인바운드, VPC 라우팅)
  5. TLS 활성화 시. openssl s_client -connect <host>:<port>

7.2 키 prefix 오염 조심

증상

다른 서비스(ppi-api, ppi-batch) 키가 섞이거나, dev 환경 키가 prod에 보임.

원인

STAGE 변수 잘못 설정 (prod vs staging 혼동), 또는 외부 도구가 잘못된 prefix로 작성.

확인
redis-cli -u "$REDIS_URL" KEYS "ppi:socket:*" | awk -F: '{print $1":"$2":"$3}' | sort -u
정리 (주의: 데이터 손실)
# 잘못된 prefix만 삭제 (예시)
redis-cli -u "$REDIS_URL" --scan --pattern 'ppi:socket:wrong-stage:*' | \
  xargs -L 100 redis-cli -u "$REDIS_URL" DEL

7.3 Heartbeat 타임아웃 & False Positive Reaper

증상

heartbeat failed 로그 반복 + 정상 instance가 reap됨.

확인
# 자신의 heartbeat 확인
redis-cli -u "$REDIS_URL" GET "ppi:socket:prod:instance:$(hostname):heartbeat"
redis-cli -u "$REDIS_URL" TTL "ppi:socket:prod:instance:$(hostname):heartbeat"

# 전체 instance heartbeat
redis-cli -u "$REDIS_URL" ZRANGEBYSCORE "ppi:socket:prod:instances:heartbeat" -inf +inf WITHSCORES
조치
  • Instance 간 네트워크 지연 확인. time redis-cli PING
  • 긴 GC 또는 이벤트 루프 블로킹 의심 (Node.js)
  • 임시 완화. INSTANCE_HEARTBEAT_TTL_SECONDS=60으로 상향

7.4 Room Pin 경합 (Redirect 무한 루프) 긴급

증상

JOIN_ROOM 시 WRONG_INSTANCE 응답 반복. 클라이언트가 무한 redirect.

진단
# Redirect 메트릭 급증 확인
curl http://localhost:3001/metrics | grep ppi_socket_room_pin_redirect

# 해당 room owner 확인
redis-cli -u "$REDIS_URL" GET "ppi:socket:prod:room:{roomId}:instance"

# Owner instance의 heartbeat 확인
redis-cli -u "$REDIS_URL" GET "ppi:socket:prod:instance:{ownerId}:heartbeat"
# 없으면 stale → reaper가 정리할 때까지 대기 (60~120초)
긴급 조치
# 강제로 pin 해제 (해당 room의 모든 peer가 끊김)
redis-cli -u "$REDIS_URL" DEL "ppi:socket:prod:room:{roomId}:instance"

7.5 메모리 vs Redis 불일치

증상

Web admin의 /app/main/debug/redis에서 "필드 불일치" 표시.

확인
echo $ROOM_META_SOURCE  # redis / both / memory

# 특정 room 직접 비교
redis-cli -u "$REDIS_URL" HGETALL "ppi:socket:prod:room:{roomId}"
조치
  • Mode를 redis로 전환 (Redis가 SoT)
  • 불일치 메트릭 추이 관찰. 지속 증가 시 동기화 버그

7.6 Recording Metadata 손실

증상

서버 재시작 후 진행 중이던 녹화 세션의 green dot 사라짐.

확인
redis-cli -u "$REDIS_URL" GET "ppi:socket:prod:recording:pending:{roomId}"
redis-cli -u "$REDIS_URL" TTL "ppi:socket:prod:recording:pending:{roomId}"
조치

다운타임이 1시간 이상일 가능성 → RECORDING_PENDING_TTL_SECONDS 연장 (예: 7200).

7.7 Redis 메모리 급증

확인
redis-cli -u "$REDIS_URL" INFO memory | grep used_memory_human
redis-cli -u "$REDIS_URL" --bigkeys
redis-cli -u "$REDIS_URL" --memkeys
패턴별 메모리 측정
redis-cli -u "$REDIS_URL" --scan --pattern 'ppi:socket:prod:room:*' | \
  xargs -L 1 redis-cli -u "$REDIS_URL" MEMORY USAGE
정리 (주의)

고아 키 정리는 Reaper의 책임이지만 긴급 시 수동 정리 가능. removeRoom() 로직과 동일한 패턴 사용 필요.

7.8 Reaper Leader 경합

증상

Reaper leader lease lost 로그 반복.

확인
redis-cli -u "$REDIS_URL" GET "ppi:socket:prod:reaper:leader"
redis-cli -u "$REDIS_URL" TTL "ppi:socket:prod:reaper:leader"
조치
  • 여러 instance가 동시에 leader 시도 → 네트워크 분할 의심
  • 강제 해제. redis-cli DEL "ppi:socket:prod:reaper:leader"

7.9 크로스 인스턴스 브로드캐스트 실패

증상

한 instance 내 클라이언트끼리만 메시지 전달, 다른 instance로 안 감.

확인
# Adapter 연결 상태
curl http://localhost:3001/metrics | grep ppi_socket_redis_adapter_connected

# pub/sub 채널 활성 확인
redis-cli -u "$REDIS_URL" PUBSUB CHANNELS '*socket.io*'
조치
  • 해당 instance 재시작 (pub/sub 클라이언트 재연결)
  • SOCKETIO_ADAPTER=redis 확인 (memory로 잘못 설정됐을 수 있음)

🚨 8. 긴급 대응 런북

시나리오 A. Redis 완전 다운

  1. 확인. redis-cli -u "$REDIS_URL" PING → 응답 없음
  2. 인프라 팀에 즉시 에스컬레이션. ElastiCache/Valkey 콘솔에서 인스턴스 상태 확인
  3. Failover 대기. Multi-AZ 구성이라면 자동 failover (1~2분)
  4. Socket 서버는 자동 복구. ioredis가 재연결 시도. 단, 부팅 단계라면 컨테이너 재시작 필요
  5. 사용자 영향 공지. 일시적으로 신규 room 생성 불가, 진행 중 세션은 끊김

시나리오 B. Redis 응답 지연 (latency 급증)

  1. redis-cli --latency -u "$REDIS_URL"로 지연 측정
  2. SLOWLOG GET 20으로 느린 쿼리 확인
  3. CLIENT LIST로 비정상 클라이언트 탐색
  4. --bigkeys로 거대 키 점검
  5. 필요시 read replica 추가 또는 인스턴스 스케일업

시나리오 C. 데이터 일관성 깨짐 (잘못된 room 표시)

  1. Web admin /app/main/debug/redis에서 불일치 식별
  2. 특정 room이 stale이라면 강제 정리. DEL room:{id}* peer:{id}*
  3. Reaper가 자동 정리할 때까지 기다리는 것이 안전 (60~120초)

시나리오 D. 전체 키 삭제 필요 (재해 복구)

금지: FLUSHALL 절대 사용 금지 (다른 서비스 키까지 삭제).

# prefix 한정 삭제 (안전)
redis-cli -u "$REDIS_URL" --scan --pattern 'ppi:socket:prod:*' | \
  xargs -L 100 redis-cli -u "$REDIS_URL" DEL

삭제 후 모든 Socket 서버 재시작 필요 (인메모리 캐시와 Redis 정합 회복).

⚙️ 9. 자주 쓰는 명령어 모음

기본 진단

# 연결
redis-cli -u "$REDIS_URL" PING

# 서버 정보
redis-cli -u "$REDIS_URL" INFO server
redis-cli -u "$REDIS_URL" INFO memory
redis-cli -u "$REDIS_URL" INFO clients

# 지연 측정
redis-cli -u "$REDIS_URL" --latency
redis-cli -u "$REDIS_URL" --latency-history -i 1

# 느린 쿼리
redis-cli -u "$REDIS_URL" SLOWLOG GET 20
redis-cli -u "$REDIS_URL" SLOWLOG RESET

키 탐색

# 활성 room 목록
redis-cli -u "$REDIS_URL" SMEMBERS "ppi:socket:prod:rooms:active"

# 특정 room 메타
redis-cli -u "$REDIS_URL" HGETALL "ppi:socket:prod:room:{roomId}"

# Instance heartbeat 전체 (score=timestamp)
redis-cli -u "$REDIS_URL" ZRANGEBYSCORE "ppi:socket:prod:instances:heartbeat" -inf +inf WITHSCORES

# 패턴 스캔 (KEYS는 절대 사용 금지, prod에서 차단됨)
redis-cli -u "$REDIS_URL" --scan --pattern 'ppi:socket:prod:room:*'

모니터링

# 실시간 명령 모니터 (prod에서 사용 주의)
redis-cli -u "$REDIS_URL" MONITOR

# 활성 클라이언트
redis-cli -u "$REDIS_URL" CLIENT LIST

# 키 공간 통계
redis-cli -u "$REDIS_URL" INFO keyspace

긴급 정리

# 특정 room 강제 정리
redis-cli -u "$REDIS_URL" DEL \
  "ppi:socket:prod:room:{roomId}" \
  "ppi:socket:prod:room:{roomId}:instance" \
  "ppi:socket:prod:room:{roomId}:peers" \
  "ppi:socket:prod:room:{roomId}:hosts" \
  "ppi:socket:prod:room:{roomId}:guest"
redis-cli -u "$REDIS_URL" SREM "ppi:socket:prod:rooms:active" "{roomId}"

# Reaper leader 강제 해제
redis-cli -u "$REDIS_URL" DEL "ppi:socket:prod:reaper:leader"

금지 명령