Notice
Recent Posts
Recent Comments
Link
«   2026/06   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
Tags
more
Archives
Today
Total
관리 메뉴

ssuperjun 님의 블로그

[장애 이력 자동 작성 도구 17] 최종 발표 자료 본문

인턴

[장애 이력 자동 작성 도구 17] 최종 발표 자료

ssuperjun 2026. 3. 12. 21:45

프로젝트 기간: 3주(실제 소요 기간 11일)

역할: 프로젝트 기획, Redis 및 MySQL 장애 연출, 장애 수집-분석 스크립트 작성, 최종 산출물을 사내 메신저에 등록

협업 사항: 프로젝트 기획, input 및 output 선정 시 은빈 인턴님과 협업해 결정

[1]프로젝트 제목: 장애 이력 자동 작성 도구


[2]목적

  • DB 담당자가 장애 알림을 받으면, 원활한 서비스를 위해 DB 정상화를 최우선으로 진행
  • 추후 장애 이력 보고 시 장애 이력을 시간 순서대로 정리해 놓은 타임라인이 필요

 

한 장애가 발생했을 때 장애 이력 보고에 들어갈 항목: 23가지(장애 관리팀 & 장애 담당자)

 

=> 이 '장애 이력 자동 작성 도구'가 없다면, 장애가 발생한 DB의 로그를 일일이 확인해 타임라인을 정리해야 함


[3]실행 환경

배경: 로컬 맥북 -> hcon 서버 -> ca801(MySQL 마스터, Redis 슬레이브)/cb801(MySQL 슬레이브, Redis 마스터) 서버에 접속

 

실제 환경 유추

 

 

방법1: DB와 다른 망에서 스크립트 실행: 스크립트 실행 ip가 폐쇄망과 연결 가능해야 함(ACL 등록 필요)

 

 

방법2: DB와 같은 망에서 스크립트 실행: 폐쇄망 내 스크립트 실행 ip가 두레이 API ACL에 등록되어야 함

 

방법1 채택

폐쇄망의 ip를 두레이 API ACL에 등록하기 어렵다고 판단

 

구현 고려사항

  • DB가 설치된 cb801에서 스크립트를 실행하면, 두레이 태스크 등록 없이 타임라인 출력
  • hcon(DB가 설치된 서버 외부)에서 스크립트를 실행하면, 타임라인 출력 및 두레이 태스크 등록

[4]실행 흐름

  1. 담당자가 장애 알람 인지
  2. 담당자가 스크립트 실행(input = 장애가 발생한 서버 hostname)
  3. 해당되는 DB 서버에서 로그 수집
  4. Output 출력 및 두레이 태스크 등록
  • DB 다운, 마스터-슬레이브 간 복제 중단 등 중요 알람이 뜨면 담당자는 '사후 보고용 타임라인 작성'이 필요하다고 판단 -> 정상화 후 스크립트 실행
  • 스크립트는 정상화 이후 한번만 실행할 목적으로 만들어짐
  • (장애 발생 때 한번 실행해놓은 뒤 장애 정상화 시점까지 백그라운드에서 장애 정상화 여부를 주기적으로 polling할 경우, 이미 장애가 발생한 서버에 부하를 줌)
  • 물론 정상화 이전에 스크립트를 실행해도 타임라인이 생성되도록 만들었으며, 스크립트 재실행 권장 메시지를 출력함

 

장애 알람 중요도 구분

[Redis]

1. 직관적인 DOWN 증상(매우 중요): Redis down, 시스템 다운, Redis port 다운(접속 불가)
2. STOP 및 DOWN으로 이어지는 증상(중요): 디스크 error, reject된 커넥션, Missing Master, Cluster Flapping(클러스터 내의 특정 노드(서버)나 서비스가 정상과 비정상 상태를 아주 짧은 시간 동안 끊임없이 반복해서 오가는 현상)
3. 마스터-슬레이브 간 문제: 복제 중단

*아래 2가지 알람이 뜨면 담당자가 직접 판단해서 필요한 상황에만 자율적으로 스크립트 실행
1. 성능 저하(심각하면 DOWN으로 이어짐): CPU 사용률, Memory 사용률, 디스크 가용공간, 디스크 Inode 사용량, 디스크 사용률, 커넥션 사용량, 메모리 단편화율, Redis 메모리 사용률
2. 지금 당장의 장애는 아니지만, 장애 발생 시 무방비 상태(사후 분석 필수): Sentinel port 다운, Redis exporter 다운, Node exporter 다운
*exporter 다운: 프로메테우스가 해당 DB 상태(CPU, 메모리, 디스크 정보 등)를 가져가지 못함. 즉각적인 장애는 아니지만, 실제 장애 상황일 때 프로메테우스가 장애를 인지하지 못하는 상황이 벌어질 수 있음

장애는 아님
1. Sentinel Failover: 마스터에 장애가 발생. 그래도 failover가 잘 수행되면 괜찮음
2. 삭제된 keys: 데이터 삭제는 장애로 볼 수 없음. 다만 Redis가 꽉 차서 캐시가 지워지는 eviction 경우라면 문제가 될 수 있지만 이는 위의 Redis 메모리 사용률 등으로도 충분히 파악 가능

[MySQL]
시스템 다운, MySQL 재시작: 매우 중요
디스크 가용공간, 디스크 Inode 사용량, 디스크 error, 커넥션 사용량, DB 접속 불가: DB 서비스 먹통과 관련
복제 지연, 복제 중단: 마스터-슬레이브 간 장애
CPU 사용률, Memory 사용률, 디스크 사용률, 슬로우쿼리, InnoDB Buffer Pool hit ratio, Long 쿼리, Long 트랜잭션, 쿼리 지연: 성능 저하와 관련

MySQL exporter 다운, Node exporter 다운: 프로메테우스가 해당 DB 상태(CPU, 메모리, 디스크 정보 등)를 가져가지 못함. 즉각적인 장애는 아니지만, 실제 장애 상황일 때 프로메테우스가 장애를 인지하지 못하는 상황이 벌어질 수 있음
MMM 롤체인지: DB 서버의 역할이 바뀌었다 = 마스터가 죽었다는 뜻
MMM VIP ping check: MMM은 가상 IP를 DB 서버에 띄워서 애플리케이션이 접속하도록 하는데, 이 알람은 앱이 접속할 문(VIP)가 사라진 경우 발생(서비스 접속 불가)
MMM 모니터 Active: MySQL 서버들의 감시자인 MMM이 죽음. 즉각적인 장애는 아니지만, 마스터가 죽게 되면 failover(슬레이브 승격) 불가능

참고: MMM은 MySQL 서버들의 상태를 감시함. 마스터가 죽으면 슬레이브를 승격시키는 failover를 실행하고, 가상 IP(VIP)를 이동시켜 지속적인 서비스를 제공함

=> 담당자는 스크립트를 실행해야 할 주요 장애 상황 여부를 결정


[5]실행 테스트

Redis 실행 명령어

# 싱글 구성
  python3 collect_redis_incident.py                                      # 로컬 실행 (두레이 등록 생략)
  
  # 원격 실행
  python3 collect_redis_incident.py user@hostname --tunnel               # 두레이 SSH 터널 경유
  python3 collect_redis_incident.py user@hostname --no-dooray            # 두레이 등록 생략

  # Sentinel 구성
  python3 collect_redis_incident.py user@hostname --sentinel /path/to/sentinel.log --tunnel
  python3 collect_redis_incident.py user@hostname --sentinel --tunnel    # conf의 log_path 사용
  python3 collect_redis_incident.py user@hostname --sentinel --no-dooray

  # 복제 구성 (마스터 + 슬레이브)
  python3 collect_redis_incident.py user@master --slave user@slave --tunnel
  python3 collect_redis_incident.py user@master --slave --tunnel         # conf의 slave_host 사용
  python3 collect_redis_incident.py user@master --slave --no-dooray

옵션:
  --tunnel      두레이 API를 SSH 리버스 터널(localhost:19999)로 중계
                (사전 준비: 맥북에서 ssh -N -R 19999:api.dooray.com:443 user@실행서버)
  --no-dooray   두레이 태스크 등록 생략 (로컬 실행 시 자동 적용)
  --sentinel    Sentinel 로그 파일 기반 수집 모드
  --slave       복제 구성 수집 모드 (슬레이브 호스트 지정 또는 conf 참조)

 

MySQL 실행 명령어

# 싱글 구성 (두레이 등록 생략)
python3 collect_mysql_incident.py irteamsu@infa-testsrv-ca801 --no-dooray

# 싱글 구성 (두레이 SSH 터널 사용)
python3 collect_mysql_incident.py irteamsu@infa-testsrv-ca801 --tunnel

# 복제 구성 (두레이 등록 생략)
python3 collect_mysql_incident.py irteamsu@infa-testsrv-ca801 --replica --no-dooray

# 복제 구성 (두레이 SSH 터널 사용)
python3 collect_mysql_incident.py irteamsu@infa-testsrv-ca801 --replica --tunnel

 

--slave 옵션(redis), --replica(mysql) 사용 이유: 마스터에서 슬레이브(레플리카)의 uuid 38자리까진 알아낼 수 있어도, ip주소나 hostname은 알아내지 못함

--sentinel 옵션(redis) 사용 이유: Sentinel 구축 및 장애 연출 생략(서버 최소 3개 필요, ACL 등록 필요), redis_의도된_shutdown.txt 파일에 정적으로 기록된 로그 내용을 읽어 오는 형태로 구현

 

*두레이 태스크 등록 시, 로컬 맥북에서 실행

ssh -N -R 19999:api.dooray.com:443 bjpark00@hcon.nhnent.com

 

실행 결과

장애 복구 전 스크립트 실행

 

 

장애 복구 후 스크립트 실행(기존에 작성된 두레이 태스크 업데이트)


[6]실행 부가 설명

SSH 터널 (두레이 API 사용 시) 열어둔 이유: 두레이 API ACL에 로컬 맥북의 ip만 등록되어 있기 때문

 

context

  • 로컬 맥북에서 hcon으로 ssh 접속이 가능하고, hcon에서 ca801/cb801로 ssh 접속이 가능한 상황
  • 스크립트를 hcon에서 실행하면 코드 상에 localhost:19999로 요청하는 부분이 존재

 

요청이 두레이까지 흐르는 모습

hcon:19999 → (SSH 터널) → 맥북:임시포트 → api.dooray.com:443

SSH 터널이 수립될 때, 맥북이 내부적으로 소켓을 열어서 트래픽을 중계

 

터널 경유 방식 채택 이유

다른 아이디어: JSON 파일을 hcon 서버에서 output으로 만든 뒤, 이 JSON 파일을 로컬 맥북으로 옮겨 맥북에서 직접 두레이 태스크 등록

JSON 방식 단점: 관리해야 할 스크립트가 2개가 됨


[7]실행 가이드(환경별 config 변경사항)

슬레이브(레플리카) 서버 정보의 경우, 장애 난 마스터 DB 서버에 맞춰 conf 내용을 수정해야 함 -> --slave 옵션에 user@slave를 직접 입력하는 것으로 해결했지만 추후 고도화 필요

 

collect_redis_incident.conf 예시

[dooray]
# 두레이 개인 API 인증 토큰 (개인설정 > API > 개인 인증 토큰)
token = 생략

# 태스크를 등록할 프로젝트 ID
project_id = 생략

# 두레이 API base URL (민간: https://api.dooray.com / 공공: https://api.gov-dooray.com)
base_url = https://api.dooray.com

# SSH 리버스 터널 URL (맥북에서 ssh -N -R 19999:api.dooray.com:443 으로 터널 열기)
tunnel_url = https://localhost:19999

[redis]
# 모니터링할 systemd 서비스명
# - Redis 7.0 (systemd 등록):  service = redis-server
# - Valkey (systemd 등록):     service = valkey-{포트번호}  예) valkey-10379
# ※ 싱글/복제 구성의 journalctl 수집 및 [STARTED]/[STOPPED] 이벤트 감지에 사용됨
service = redis-server

# 정상화 판단 기준 (초): 마지막 재시작 후 이 시간 동안 kill 없으면 정상화로 판단
recovery_stable_secs = 30

[sentinel]
# Sentinel 로그 파일 경로 (--sentinel 옵션에서 경로 생략 시 사용)
log_path = /home1/irteam/redis_의도된_shutdown.log

[replication]
# 슬레이브 서버 접속 정보 (--slave 옵션에서 슬레이브 호스트 생략 시 사용)
slave_host = irteamsu@infa-testsrv-ca801

# 마스터 redis/valkey 로그 파일 경로
# - Redis 7.0:  /var/log/redis/redis-server.log
# - Valkey:     /home1/irteam/valkey_{포트번호}/valkey_{포트번호}.log  예) /home1/irteam/valkey_10379/valkey_10379.log
master_log_path = /var/log/redis/redis-server.log

# 슬레이브 redis/valkey 로그 파일 경로
# - Redis 7.0:  /var/log/redis/redis-server.log
# - Valkey:     /home1/irteam/valkey_{포트번호}/valkey_{포트번호}.log  예) /home1/irteam/valkey_10379/valkey_10379.log
slave_log_path = /var/log/redis/redis-server.log

[8]Input 선정 이유

Input(실행 명령어) 예시: python3 스크립트명 hostname(장애가 발생한 DB 서버)

*서버 내에서 직접 실행일 땐 hostname을 입력하지 않아도 동작하도록 구현

 

[이유]

  • 핵심: 실행 명령어가 사용자(장애 담당자) 입장에서 복잡하지 않아야 함
  • 아이디어1(알람 내용을 그대로 복사 붙여넣기) 반려: 실제론 알림을 복사 붙여넣기 하기 어려움, 복붙이 되더라도 오타 처리 및 알림 형식 파싱 복잡
  • 아이디어2(장애 결과(ex. MySQL Not Reachable, Redis Port Down)를 input에 넣기) 반려: 사용자의 편의 저하에 비해 코드 성능이 그렇게 좋아지는 것은 아님(사후 보고용 리포트 작성이 목적이므로 신속하게 처리되지 않아도 됨)
  • 아이디어3(input에 '로그를 수집하고 싶은 시간' 넣기) 반려: 장애가 중복 또는 자주 발생하지 않는다는 점을 고려하면 시간을 입력하는 건 사용자 입장에서도 번거로움, '시간'을 input으로 넣어서 과거 로그를 확인하는 기능 필요없음
  • => 대신, 하루에 여러 장애가 발생했을 때, '이 로그가 어느 장애와 관련된 로그인지' 장애 묶음을 구분하도록 함

[9]Output 선정 이유

장애 이력 보고에 들어갈 항목 23가지 중

  1. 반드시 넣을 부분
  2. 장애해결 이후에 작성될 부분
  3. 장애관리팀 작성 영역이지만 output에 넣으면 좋을 부분
  4. 굳이 들어가야 되나 싶은 부분
  5. 구현 가능할지 모르겠는 부분

으로 항목을 구분한 뒤

  • 4,5번 제외
  • 담당자가 로그 확인 없이도 쉽게 작성할 부분 제외
  • 핵심 항목만 남기기

최종 Output 형식 예시

장애 지속 시간   : 9분 48초   (발생 → 해결)

발생 시각        : 2025-03-15 13:19:12
해결 시각        : 2025-03-15 13:29:00

장애 발생 원인   : OOM Kill

장애 타임라인
  13:19:12  [OOM] Feb 27 13:19:12 kernel: Out of memory: Kill process 1234
  13:21:00  [OOM] Feb 27 13:21:00 kernel: oom-killer invoked
  13:21:45  [OOM] Feb 27 13:21:45 kernel: Killed process 5678 (mysqld)
  13:22:30  [OOM] Feb 27 13:22:30 kernel: Out of memory: Kill process 9012
  13:28:30  [정상화] 2024-02-27T13:28:30.123456Z mysqld: ready for connections
  13:29:00  [정상화] 2024-02-27T13:29:00.789012Z mysqld: restarted successfully

[10]장애 상황 연출

코드 구현을 위해, 장애 상황을 모두 연출 -> 로그 패턴 분석 -> 로그 수집 및 output 산출 스크립트 작성

 

DB의 장애 상황은 크게 5가지 상황 내에서 일어난다고 분류함(DB 다운 및 stop 등 장애에 영향을 미치는 요인 중 장애 연출이 가능한 요인 추림)

1. OOM killed(메모리 full)

2. 디스크 full 및 병목

3. CPU 병목

4. Too many connections

5. 내부 로직 segfault


1. Redis OOM 장애 시뮬레이션: https://ssuperjun.tistory.com/55

  • => maxmemory 아주 작게 설정, 메모리 폭탄 투하 -> journalctl 로그에서 OOM kill 발생 확인
  • 특이사항: systemd에 등록된 Redis는 oom-kill 시 자동 재시작 수행 - 메모리 폭탄 투하 코드가 계속 진행 중이면 ALIVE와 DEAD를 왔다갔다함(새 pid 생성)
  • 시행착오: Swap 메모리 비활성화(메모리 limit을 초과하면 OS가 데이터를 RAM 대신 Swap 메모리로 밀어내는 문제 해결)

 

2. Redis 디스크 full 시뮬레이션: https://ssuperjun.tistory.com/64결론: 디스크 full 장애 감지 스킵 - 회사에선 Redis를 데이터 저장 용도로 사용하지 않음

  • => Redis dir 경로를 용량이 작은 tmpfs(임시 데이터 저장 장소)로 바꾸고 dd 명령어로 실제 디스크를 모두 채우기
  • 결론: 디스크 full 장애 감지 스킵 - 회사에선 Redis를 데이터 저장 용도로 사용하지 않음

 

3. Redis Too many connections 시뮬레이션: https://ssuperjun.tistory.com/65

  • => maxclients를 낮게 설정(예: 5)한 뒤 연결을 폭주시키기
  • Too many connections 감지 스킵 이유: Redis가 살아있을 때만 내부에서 확인 가능한 기록. Redis 재시작 시 휘발됨
  • CPU 병목 장애 감지 스킵 이유: 이하 동일

 

4. Redis Segfault 장애 시뮬레이션: https://ssuperjun.tistory.com/66

=> SIGSEGV(kill -11) 직접 전송(gdb(프로세스에 붙어 상태 확인이나 강제 오류 발생 디버거)로 실제 segfault 유발 방식보다 간단, 로그 패턴은 동일하게 찍힐 것으로 예측)


5. Redis Sentinel 구조 로그 패턴 분석: https://ssuperjun.tistory.com/67

=> redis_의도된_shutdown.log 원본 로그 파일을 파싱하는 형태로 구현

 

[더 알아본 내용]

더보기

sdown과 odown에서 각각 s와 o의 의미

  • sdown = Subjectively Down. 특정 Sentinel 하나가 마스터에 응답이 없다고 주관적으로 판단한 상태. 한 명의 Sentinel만 이상하다고 느끼는 단계
  • odown = Objectively Down. quorum 수 이상의 Sentinel이 동의하여 객관적으로 다운으로 판단한 상태. 이 단계에서 페일오버가 트리거됨

[NEW_EPOCH]: Sentinel 클러스터 내 페일오버 시도가 시작될 때 epoch(판 번호)를 증가시키는 이벤트. 여러 Sentinel이 동시에 페일오버를 시도하는 것을 방지하기 위한 분산 합의 메커니즘으로, epoch가 높은 쪽이 우선권을 가짐

[CONFIG_UPDATE]: 다른 Sentinel이 페일오버를 완료하고 새 마스터 정보를 브로드캐스트했을 때, 이를 수신하여 자신의 설정을 업데이트했다는 이벤트. 즉 이 로그의 Sentinel은 페일오버를 직접 수행한 게 아니라 결과를 전달받은 것

 

[궁금증 해결]

"로그에서 failover보다 +sdown이 먼저 발생해야 하지 않나?"

로그에서 failover가 +sdown(센티널의 마스터 죽음 인지)보다 먼저 발생한 이유

  • 이 로그는 이 Sentinel 노드(.39)의 시각으로 기록된 것임
  • 페일오버는 다른 Sentinel(.38)이 수행했고, 이 Sentinel은 .38로부터 +config-update-from으로 결과를 전달받아 02:58:46에 마스터 전환을 인지
  • 이후 구 마스터(.38)와 Sentinel(.38)이 새 마스터(.37) 기준으로 응답이 없어서 03:01:13과 03:01:35에 +sdown이 찍힘

=> 즉 페일오버 완료 후 구 마스터가 슬레이브로 재편입되는 과정에서 일시적으로 +sdown이 발생한 정상적인 흐름임

 

failover 전 Sentinel 구성 상황 정리

("+switch-master tfw 10.x.x.38 10379 10.x.x.37 10379" 로그 기반 추론)

여기서 .38의 마스터 DB가 다운되자 .38의 Sentinel이 페일오버를 수행해서 .37의 replica를 새 마스터로 승격시킴

페일오버 후에는 구 마스터였던 .38:10379가 새 마스터 .37의 슬레이브로 재편입을 시도하는 과정에서 +sdown/-sdown/+reboot이 찍히게 됨

 

Sentinel 구조의 장점(Replication에 비해)

Replication의 경우, master 장애로 인한 failover 시 replica에서 직접 master 연결 끊기 필요, 애플리케이션 연결 재설정도 필요

Sentinel 구조의 경우, 연결 재설정을 수동으로 할 필요 없음


6. Redis 복제 구조 장애 시뮬레이션: https://ssuperjun.tistory.com/68

  • => cb801내 DB를 마스터로, ca801 내 DB를 슬레이브로 설정(마스터의 redis.conf를 수정)
  • => 마스터 OOM Kill(maxmemory 아주 작게 설정, 메모리 폭탄 투하), 네트워크 차단 장애(마스터에서 슬레이브의 연결을 강제로 끊기) 연출

[궁금증 해결]

더보기

복제 구성에서 bgsave가 발생하는 이유

  • save ""로 디스크 저장용 RDB는 비활성화했지만, 슬레이브 Full resync 시에는 마스터가 현재 데이터를 슬레이브에 전송하기 위해 별도로 BGSAVE를 수행함
  • 이 bgsave는 save 설정과 무관하게 동작함
  • 이번 로그에서 Starting BGSAVE for SYNC with target: replicas sockets라고 찍힌 것이 바로 이 경우에 해당함
  • replicas sockets는 디스크에 파일을 쓰지 않고 RDB 데이터를 소켓으로 직접 슬레이브에 스트리밍하는 diskless replication 방식임
  • 이 BGSAVE는 fork()로 자식 프로세스를 생성하는데, fork 순간 부모 프로세스의 메모리를 복사하므로 메모리 사용량이 일시적으로 급증해서 다시 OOM Kill이 발생함
  • diskless replication을 비활성화(repl-diskless-sync no)하면 디스크에 RDB 파일을 먼저 쓴 후 전송하는데, 이 경우 fork 시 메모리 부담은 줄어들지만 디스크 I/O가 발생함. 어느 쪽이든 MemoryLimit이 낮으면 OOM Kill 반복은 피하기 어려움

MySQL 장애 연출: https://ssuperjun.tistory.com/72

각 장애마다 연출 → 로그 확인 → 패턴 확정 → 코드 반영 사이클을 반복

싱글 구성 (ca801)

순서 장애 유형 연출 방법
1 재시작 (정상) mysqladmin shutdown 후 재시작 — 기준선 확보
2 OOM Kill stress-ng 또는 INSERT 루프로 메모리 고갈, vm.overcommit_memory 조정
3 Too Many Connections max_connections=5 설정 후 다수 연결
4 디스크 Full fallocate로 디스크 채우기
5 Segfault kill -SIGSEGV {mysqld_pid}

*Too many connections 장애만 스킵: error log에 흔적이 안남고, 이 장애는 DB 자체 문제보단 애플리케이션 레벨 문제라 판단

 

복제 구성 (ca801 마스터 → cb801 레플리카)

순서 장애 유형 연출 방법
6 복제 연결 단절 (마스터 재시작) ca801에서 mysqld 재시작
7 복제 연결 단절 (네트워크 단절) iptables로 ca801↔cb801 포트 차단/해제
8 마스터-레플리카 간 데이터 정합성 장애 유발 레플리카에서 직접 데이터를 수정한 뒤 마스터에서 동일 row를 건드려 충돌 유발
9 레플리카의 디스크 Full 장애 유발 마스터의 데이터를 레플리카가 동기화하는 과정에서 레플리카의 디스크 용량을 부족하게 설정

[11] Redis 구현 내용

11-1. 싱글 구성

수집할 DB: redis-server(7.0버전), valkey(7.2버전 이상)

 

수집할 로그: journalctl

 

journalctl 수집 이유

  • OOM kill 등 장애를 감지하는 건 error log보다 journalctl이 간편(직접적인 oom-kill 메시지는 journalctl에 남지만 error log에는 남지 않음)
  • dmesg(간단한 커널 로그)로 OOM kill 등의 장애를 감지할 수 있지만, journalctl보다 장애 발생 시각을 정확히 탐지하지 못함

 

주의사항

  • Valkey는 systemd에 자동으로 등록되지 않음
  • journalctl 수집 방식을 적용하기 위해, Valkey를 systemd에 등록함
  • 실제 환경의 DB가 systemd에 등록되지 않은 경우를 고려해, MySQL 구현 시엔 journalctl이 아닌 mysql.err 로그를 수집함

 

redis-server(7.0버전)과 valkey의 journalctl 로그 형태가 동일한가?

다른 점

  1. 1. 프로세스 이름: Redis 7.0은 redis-server로 찍히지만, Valkey는 valkey-{포트 번호}로 찍힘
  2. 2. 메시지 키워드 형태
  3. Redis 7.0: master
    Valkey: primary

그외 나머지 로그 형태는 Redis/Valkey 무관하게 동일

 

발생 시각 기준: 가장 최근 장애 묶음의 첫 번째 kill 또는 stop 이벤트 시각

  • 장애 묶음 경계: 직전 [STARTED] → 다음 kill 또는 stop 간 gap이 30초 이상이면 새 묶음으로 판단
원인 감지 키워드 태그
OOM Kill Failed with result 'oom-kill' [OOM_KILL]
Segfault Failed with result 'core-dump' [SEGFAULT]
Signal Kill Failed with result 'signal' [KILL_SIGNAL]
비정상 종료 - {result값} (원인 수동 확인 필요) Failed with result 'exit_code 또는 timeout 등' [FAILED_EXIT_CODE], [FAILED_TIMEOUT]
미확인 (로그 수동 확인 필요) Failed with result 자체가 없는 경우(db 내부에서 강제 stop하는 경우는 여기에 해당)  

기타 이벤트 태그

태그 감지 키워드
[STARTED] Started {redis-server 또는 valkey-10379}
[STOPPED] Stopped {redis-server 또는 valkey-10379}
[RESTART] Scheduled restart job
[RESTART_THROTTLE] Start request repeated too quickly

정상화 판단: 마지막 [STARTED] 이후 30초 동안 kill 이벤트 없음

 

30초 선정 이유

  • 로그에 완벽한 정상화를 판단할 수 있는 기록이 없음, 시간 gap으로 정상화를 감지하는 게 구현 난이도 측면에서 유리
  • 전제: systemd는 Redis가 죽으면 자동 재시작을 진행함
  • OOM Kill 직후 systemd가 redis를 재시작하는 데 걸리는 시간은 로그 상 1~3초
  • 재시작 직후 메모리 채우기 스크립트(Insert 작업)가 남아있으면 보통 2~5초 내에 다시 kill됨
  • => 장애 상황에서는 kill 이벤트 간격이 최대 10초 수준이므로, 넉넉잡아 30초 동안 kill이 없으면 정상화로 판단해도 무방

11-2. 복제 구성

수집 소스: 마스터 journalctl + 마스터/슬레이브 /var/log/redis/redis-server.log

11-2-1. 마스터 OOM Kill / Segfault / Signal Kill

발생 시각 기준: 가장 최근 장애 묶음의 첫 번째 kill 또는 stop 이벤트 시각

  • 장애 묶음 경계: 연속된 kill/conn_lost 이벤트 중 직전 이벤트와의 gap이 30초 이상이면 새 묶음으로 판단
원인 감지 키워드 태그
OOM Kill Failed with result 'oom-kill' (마스터 journalctl) [OOM_KILL]
Segfault Failed with result 'core-dump' (마스터 journalctl) [SEGFAULT]
Signal Kill Failed with result 'signal' (마스터 journalctl) [KILL_SIGNAL]

슬레이브 측 태그

태그 감지 키워드
[CONN_LOST] Connection with master lost
[FULL_RESYNC] Full resync from master
[SYNC_FAIL] I/O error trying to sync with MASTER
[SYNC_OK] MASTER <-> REPLICA sync: Finished with success
[PSYNC_OK] Successful partial resynchronization

마스터 측 태그

태그 감지 키워드
[REPLICA_CONN] Synchronization with replica .* succeeded

정상화 판단: 마지막 [REPLICA_CONN] 또는 [SYNC_OK]/[PSYNC_OK] 이후 30초 동안 [CONN_LOST] 없음


11-2-2. 네트워크 단절

발생 시각 기준: 가장 최근 장애 묶음의 첫 번째 [CONN_LOST] 이벤트 시각

  • 장애 묶음 경계: 연속된 [CONN_LOST] 이벤트 중 직전 이벤트와의 gap이 30초 이상이면 새 묶음으로 판단 (kill 이벤트 없음)
원인 감지 조건 태그
복제 연결 단절 kill 이벤트 없이 Connection with master lost 반복 (슬레이브 로그) [CONN_LOST]

정상화 판단: 마지막 [PSYNC_OK] 또는 [SYNC_OK] 이후 30초 동안 [CONN_LOST] 없음

 

gap이 30초인 이유: 싱글 구성과 동일(OOM Kill 후 재시작까지 수초, BGSAVE(슬레이브 동기화를 위한 rdb 구성 위해 필요) fork 후 동기화까지 수초 수준이므로)

 

11-3. Sentinel 구성

수집 소스: Sentinel 로그 파일

발생 시각 기준: 장애 유형에 따라 다름

  • Failover 발생 시: 직전 +new-epoch 또는 +odown 중 이른 시각 (없으면 직전 +sdown fallback)
  • Failover 없이 +odown만 있을 시: 가장 최근 +odown 시각
  • +sdown만 있을 시: 가장 최근 +sdown 시각
원인 감지 조건
Sentinel Failover +switch-master 이벤트 존재
Sentinel Failover 실패 +switch-master 없고 -failover-abort-* 존재
Sentinel 장애 위 둘 다 없고 +sdown/+odown 존재

태그 정리

태그 감지 키워드
[SDOWN] +sdown
[SDOWN_RECOVER] -sdown
[ODOWN] +odown
[ODOWN_RECOVER] -odown
[FAILOVER] +switch-master
[FAILOVER_ABORT] -failover-abort-*
[REBOOT] +reboot
[NEW_EPOCH] +new-epoch
[CONFIG_UPDATE] +config-update-from

정상화 판단: 마지막 복구 이벤트(-sdown/-odown)가 마지막 장애 이벤트 이후에 존재

=> -sdown/-odown은 센티널이 '마스터가 살아있음'을 보증해준다고 판단해 gap을 따로 설정하지 않음

 

공통 기능

기능 설명
두레이 태스크 자동 등록 장애 발생 시각 기준 중복 방지 (PUT 업데이트)
두레이 태스크 상태 자동 설정 정상화 판단 시 완료, 진행 중이면 할일
민감정보 분리 두레이 토큰, 슬레이브 호스트 등 conf 파일로 관리

그외 코드 구현 시 고려한 부분

1. run 함수(명령어를 로컬 또는 ssh원격으로 실행하고 stdout 반환) 예외처리 로직 개선: 명령 타임아웃(30초) 초과, 명령 권한 오류, 빈 결과 시 예외처리

2. 날짜 경계 문제(23시->01시, 12월31일->1월1일) 해결


[12] MySQL 구현 내용

12-1. 로그 수집 위치

로그 경로: /home1/irteam/mysql/log/mysql.err (ca801/cb801 공통)

 

이유

재시작 시 휘발되지 않는 기록만을 수집하고자 함(SHOW ENGINE INNODB STATUS 등 런타임 통계는 스킵)

MySQL도 Valkey처럼 systemd로 관리되지 않고 직접 실행되어 journalctl 로그가 없음


12-2. 로그 수집 항목

싱글 구성 (ca801 단독)

장애 유형 수집 여부 수집 로그 스킵 사유
OOM Kill / kill -9 ca801 error log  
Segfault ca801 error log  
비정상 종료 (Aborting) ca801 error log  
디스크 Full ca801 error log 단, 디스크가 완전 소진되어 로그가 잘린 경우는 수집 불가
Too Many Connections log_error_verbosity=2 기본값에서 error log 미기록

복제 구성 (ca801=마스터, cb801=레플리카)

장애 유형 수집 여부 수집 로그 스킵 사유
마스터 비정상 종료 (Aborting) ca801 + cb801 error log 통합  
마스터 OOM Kill / kill -9 ca801 + cb801 error log 통합  
마스터 디스크 Full ca801 + cb801 error log 통합  
복제 연결 단절 ca801 + cb801 error log 통합  
레플리카 디스크 Full ca801 + cb801 error log 통합  
데이터 정합성 충돌
(레플리카의 데이터를 마스터와 동기화하는 상황)
error log 미기록 확인 (실제 연출로 확인)

12-3. 장애 감지 키워드, 발생 시각 기준 정리

싱글 구성 (_SINGLE_EVENT_PATTERNS)

태그 감지 키워드 발생 시각 기준
[STARTING] mysqld_safe Starting mysqld daemon, [MY-010116]
[INNODB_READY] [MY-013577]
[XA_RECOVERY] [MY-010229]
[XA_RECOVERY_END] [MY-010232]
[READY] [MY-010931]
[SAFE_RESTARTED] mysqld_safe mysqld restarted
[SAFE_ENDED] mysqld_safe mysqld from pid file.*ended
[CRASH_DETECTED] mysqld_safe Number of processes running now: 0 fault_events 중 첫 번째 시각
[ABORTING] [MY-010119] fault_events 중 첫 번째 시각
[SEGFAULT] handle_fatal_signal fault_events 중 첫 번째 시각
[DISK_FULL] [MY-000035], [MY-011072] fault_events 중 첫 번째 시각

발생 시각: {segfault, aborting, oom_or_crash, disk_full} 중 첫 번째 이벤트 시각

복제 구성 _REPL_EVENT_PATTERNS

태그 감지 키워드 서버 발생 시각 기준
[CONN_LOST] [MY-010557], [MY-010558] replica fault_events 중 첫 번째 시각
[RECONNECTING] [MY-010584] (I/O 스레드) replica fault_events 중 첫 번째 시각
[CONNECTED] [MY-010592] replica
[REPL_START] [MY-014001] replica
[REPLICA_DISK_FULL] [MY-012144], [MY-012639], [MY-012640], [MY-013132] replica fault_events 중 첫 번째 시각
[STARTING] [MY-010116] master
[ABORTING] [MY-010119] master fault_events 중 첫 번째 시각
[CRASH_DETECTED] mysqld_safe Number of processes running now: 0 master fault_events 중 첫 번째 시각
[SAFE_RESTARTED] mysqld_safe mysqld restarted master
[READY] [MY-010931] master

발생 시각: {oom_or_crash, aborting, conn_lost, reconnecting, replica_disk_full} 중 첫 번째 이벤트 시각


12-4. 태그 전체 목록 정리

태그 구성 설명
[STARTING] 싱글/복제 공통 mysqld 기동 시작
[INNODB_READY] 싱글 InnoDB 초기화 완료
[XA_RECOVERY] 싱글 XA crash recovery 시작 (비정상 종료 간접 증거)
[XA_RECOVERY_END] 싱글 XA crash recovery 완료
[READY] 싱글/복제 공통 mysqld 완전 기동 완료
[SAFE_RESTARTED] 싱글/복제 공통 mysqld_safe가 mysqld 재시작
[SAFE_ENDED] 싱글 mysqld_safe 정상 종료
[CRASH_DETECTED] 싱글/복제 공통 mysqld 프로세스 소멸 감지
[ABORTING] 싱글/복제 공통 mysqld 스스로 기록한 비정상 종료
[SEGFAULT] 싱글 signal handler 스택 트레이스 감지
[DISK_FULL] 싱글 마스터 binlog 쓰기 실패
[CONN_LOST] 복제 레플리카 I/O 스레드 연결 끊김
[RECONNECTING] 복제 레플리카 I/O 스레드 재연결 시도
[CONNECTED] 복제 레플리카 I/O 스레드 재연결 성공
[REPL_START] 복제 복제 스레드 시작
[REPLICA_DISK_FULL] 복제 레플리카 InnoDB 파일 쓰기 실패
[정상화] 싱글/복제 공통 정상화 판단 기준 이벤트

12-5. 정상화 판단 기준 정리

싱글 구성

장애 유형 기준 이벤트 Gap 근거
OOM Kill, 
Segfault, 
Aborting
[MY-010931] ready for connections +60초 커넥션 풀 재접속(~30초) + 초기 부하 안정화
디스크 Full [MY-010931] ready for connections +120초 디스크 공간 미확보 시 재시작 후 즉시 동일 장애 재발 가능. InnoDB 이전 실패 쓰기 복구 시간 + 운영자 디스크 정리 후 안정화 구간까지 커버
  • ready for connections 이후 crash가 재발하면 정상화 판단 보류
  • gap 60초 미경과 시 '장애 진행 중' 출력 후 재실행 안내

 

crash류의 장애 정상화 기준을 60초로 정한 이유

  • ready for connections는 InnoDB 초기화, XA crash recovery, TLS 설정 완료 후 찍힘
  • 일반적으로 커넥션 풀의 reconnect timeout은 30초
  • 재접속 완료(30초) 후 쿼리(부하)가 실행되기까지 수 초가 더 필요
  • => 30초+a = 여유롭게 60초가 지나면 정상화됐다고 판단

디스크 Full의 장애 정상화 기준을 120초로 정한 이유

  • 디스크 Full이 해소되지 않으면 재시작 후 즉시 동일 장애 재발 가능
  • mysqld 재시작 후 InnoDB가 이전에 실패한 쓰기 작업을 복구하는 시간 필요
  • 운영자가 디스크 정리 후 재시작하는 수동 개입 시간 필요
  • 60초는 커넥션 풀 재접속 기준이지만, 디스크 Full은 공간 확보 확인까지 포함해야 함

복제 구성

장애 유형 기준 이벤트 Gap 근거
연결 단절 계열 [MY-010592] connected to source 또는 [MY-014001] repl_start +120초 replica_net_timeout(60초) 1사이클 + 여유 60초
레플리카 디스크 Full (InnoDB 파일 쓰기 실패) 마지막 [REPLICA_DISK_FULL] 이후 fault 없음 + gap 120초 경과 +120초 SQL 스레드만 중단되어 I/O 스레드 재연결이 정상화 증거가 되지 않음. 수동으로 START REPLICA SQL_THREAD 후 안정화 구간까지 커버
레플리카 디스크 Full (relay-bin 쓰기 실패) 마지막 [RELAY_DISK_FULL] 이후 fault 없음 + gap 720초 경과 +720초 [MY-000035]는 600초(10분)마다 반복 출력됨. 600초 초과 시 공간 확보로 자동 재개된 것으로 판단 + 여유 120초
  • 기준 이벤트 이후 120초 또는 720초 이내에 fault가 재발하면 정상화 판단 보류
  • gap 미경과 시 '장애 진행 중' 출력 후 재실행 안내

12-6. 장애 원인 판단 우선순위

싱글: [DISK_FULL] > [SEGFAULT] > [ABORTING] > [CRASH_DETECTED] > 미확인

복제: [REPLICA_DISK_FULL] > [ABORTING](master) > [CRASH_DETECTED](master) > [CONN_LOST]/[RECONNECTING] > 미확인

 

=> [DISK_FULL] > [SEGFAULT] > [ABORTING] > [CRASH_DETECTED] 태그들이 모두 존재하는 장애 묶음이라면 disk_full을 장애 원인으로 보겠다는 의미

 

  • 우선순위가 높을수록 장애 원인이 구체적임
  • 디스크 Full 시 signal 6(SIGABRT)으로 handle_fatal_signal도 함께 찍히므로 [DISK_FULL]을 [SEGFAULT]보다 우선 판단
  • [ABORTING], [CRASH_DETECTED] 등으로 장애를 판단했을 땐 mysql.err 로그만으론 장애의 원인이 구체적으로 oom-kill인지 알 수 없으므로 journalctl이나 dmesg 확인 필요

[13] 고도화 필요 부분

문제

레플리카의 디스크 Full 장애(MySQL 9번 장애)를 일으킨 뒤 8분 후에 복구를 시도하면, '레플리카의 디스크 Full'에 대한 타임라인이 정리되지 않고, 복구 이후의 '복제 연결 단절' 타임라인만 정리됨

 

원인

복구 시 로그에 [CONN_LOST], [REPL_START]가 찍힘. 현재 로직에선 [CONN_LOST]도 하나의 장애로 감지

 

해결 과정

디스크 full 에러 메시지([MY-000035])가 10분에 한번씩 출력되는 것을 확인 -> 넉넉잡아 12분 gap으로 디스크 full 정상화를 판단하는 것으로 로직을 수정했으나, 동일한 문제 지속

담당자가 '복제 연결 단절' 원인을 보고 로그를 수동으로 직접 확인해보면 금방 '디스크 Full'이 원인임을 알 수 있을 거라 판단하고 시간관계상 로직 수정을 보류

 

해결 아이디어

새로운 로그 패턴이 나올 때마다 스크립트의 로직을 일일이 수정해야 하는 문제 존재

LLM을 이용한 로그 패턴 학습으로 장애 묶음을 구분하는 건 어떨까? - 학습 데이터 확보 문제 존재


[14] 고도화 아이디어

1. 가장 최근에 발생한 장애 로그 묶음 '하나만' 타임라인을 정리하는 현재 방식 대신, 타임라인이 정리되지 않은 '모든 장애'를 타임라인으로 정리

  • 실무에서의 필요성 확인 필요

 

2. 장애 발생 직후 1차적으로 스크립트를 실행(원인 분석 및 휘발성 상태 정보 보존 목적) -> 1차 실행 때 수집한 정보를 사후 2차 실행에서 재활용하기

  • 디스크 full, cpu 병목, too many connections, 복제 지연 정보들도 수집할 수 있음
  • 1차 정보 저장을 어떻게 할지, 어떻게 재활용할지 더 고민 필요

 

3. 수집 대상 DB에 주기적 로그 수집 방법 => 폐기

현재 방식은 정상화가 끝난 이후 스크립트를 실행해 로그를 한번만 실행하는 걸 고려한 상황

  • 주기적인 로그 수집 방식의 장점: 1.장애 알람 기능 구현, 2.수집에 kafka 사용, 3.로그 패턴 분석에 AI 활용 등 프로젝트 사이즈가 커짐
  • 주기적인 로그 수집 방식의 단점: 장애 빈도 낮고, 주기적 폴링  명령은 장애  DB에 부하를 주므로 폐기