Redis 트러블슈팅 가이드

대상: 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"

금지 명령