장애 분류와 SLO: 접속 장애, 지연, 데이터 손상, 복제 지연
모든 DB 장애는 같지 않다
"DB가 이상해요"라는 알림이 들어왔을 때, 가장 먼저 해야 할 일은 그 이상함이 어느 범주인지 구분하는 것이다. 접속 자체가 안 되는 것, 접속은 되는데 응답이 느린 것, 응답은 오는데 데이터가 이상한 것, 복제본에서 읽은 데이터가 주서버보다 과거를 보여주는 것은 각각 원인도 다르고, 긴급도도 다르고, 완화 방법도 다르다.
장애 범주를 미리 정의해두지 않으면 세 가지 문제가 생긴다.
- 처음 장애를 보는 사람이 무엇부터 확인해야 할지 모른다.
- SLO를 어디에 걸어야 하는지, 알림 임계값이 맞는지 판단이 안 된다.
- 사후 분석에서 장애 유형이 통일되지 않아 추세를 볼 수 없다.
이 장에서는 DB 장애를 네 범주로 정리하고, 각 범주에 맞는 SLI·SLO, 대표 원인, 완화 방법을 다룬다.
DB 장애 분류 체계
Pool exhaustion
Auth failure
DNS/LB 이상 SLI: 연결 성공률
SLO: ≥ 99.9%
Lock contention
IOPS 병목
Connection storm SLI: p99 쿼리 지연
SLO: ≤ 100ms
Torn page
WAL/binlog 손상
사람 실수(DROP) SLI: 무결성 이벤트
SLO: 0건/월
Apply 병목
Split-brain
replica 탈락 SLI: replica lag (초)
SLO: ≤ 30초 99%
범주 1: 접속 장애
접속 장애는 애플리케이션이 DB에 새 연결을 맺지 못하거나, 기존 연결이 끊어지는 상황이다. 사용자 관점에서는 로그인 실패, API 500 오류, "DB 연결 실패" 메시지로 나타나는 경우가 많다.
주요 원인
| 원인 | 구체 증상 | 확인 포인트 |
|---|---|---|
| Connection pool 고갈 | 앱에서 "pool timeout" | DB 활성 연결 수, 앱 pool 설정 |
| max_connections 초과 | DB에서 "too many connections" | SHOW STATUS LIKE 'Threads_connected' |
| 네트워크/DNS 장애 | 타임아웃, 패킷 손실 | ping, traceroute, DNS TTL |
| 인증 실패 | "Access denied for user" | 계정·비밀번호·호스트 허용 설정 |
| TLS 인증서 만료 | SSL handshake 오류 | 인증서 만료일 |
| primary 장애, LB 미전환 | failover 후에도 연결 안 됨 | LB/HA 구성, DNS 전파 시간 |
SLI와 SLO 설정 가이드
접속 장애의 SLI는 연결 성공률이다.
연결 성공률 = (성공한 연결 시도 수) / (전체 연결 시도 수)SLO 예:
- Tier-1 서비스 DB: 연결 성공률 ≥ 99.9% (4.38분/월 허용)
- Tier-2 서비스 DB: 연결 성공률 ≥ 99.5%
접속 장애는 보통 완전한 서비스 중단으로 이어지므로 알림 임계값을 낮게 유지하는 것이 좋다. 연결 성공률이 5분 이상 99% 아래로 떨어지면 P1 알림으로 분류한다.
Connection storm 패턴
Connection storm은 접속 장애의 전형적 양상이다. 다음 순서로 진행된다.
- DB 재시작 또는 failover 발생
- 앱 서버들이 동시에 재연결 시도
max_connections또는 OS의accept queue를 순간 초과- 연결 성공도 실패도 아닌
ETIMEDOUT폭증 - 앱이 에러를 내면서 재시도 → 연결 폭증 반복
완화 방법: connection pool의 reconnect 로직에 지수 백오프와 jitter를 추가해 동시 재연결을 분산시킨다. DB 앞에 ProxySQL·PgBouncer 같은 connection proxy를 두면 앱이 DB를 직접 보지 않아 storm 충격을 흡수할 수 있다.
범주 2: 지연 장애
지연 장애는 연결은 성공하지만 쿼리 또는 트랜잭션이 기대보다 오래 걸리는 상태다. 서비스가 완전히 죽지는 않지만 사용자 경험이 나빠지고, 악화되면 timeout으로 인한 오류로 발전한다.
주요 원인
쿼리 레이어:
- 인덱스 미사용, full table scan
- 통계 정보 부정확으로 인한 잘못된 실행계획 선택
- N+1 쿼리, 대용량 JOIN
트랜잭션/잠금 레이어:
- 긴 트랜잭션이 잠금을 오래 점유
- 데드락 빈발
SELECT ... FOR UPDATE남용
리소스 레이어:
- 디스크 IOPS 포화 (쓰기 피크, checkpoint, compaction 동시 발생)
- 메모리 부족으로 인한 swap IO
- CPU 포화 (analytics 쿼리가 OLTP와 경합)
커넥션 레이어:
- 과도한 연결 수로 스케줄링 overhead
- connection churn (연결 맺고 곧바로 끊기 반복)
SLI와 SLO 설정 가이드
지연 장애의 SLI는 p99 쿼리 응답시간이다. 평균 latency는 outlier를 숨기기 때문에 SLO 기준으로 적합하지 않다.
| 대상 | SLI | SLO 예시 |
|---|---|---|
| OLTP 읽기 쿼리 | p99 read latency | ≤ 50ms |
| OLTP 쓰기/커밋 | p99 commit latency | ≤ 100ms |
| 트랜잭션 에러율 | lock wait timeout 비율 | ≤ 0.01% |
| 슬로우 쿼리 | 1초 초과 쿼리 수 | 일별 top 20 검토 기준 |
p99 vs p95: p99가 더 엄격하지만 측정 샘플이 충분해야 의미 있다. 분당 100건 이하의 저트래픽 DB라면 p95가 더 안정적인 SLI다.
지연 장애 진단 흐름
pt-query-digest / pg_stat_statements
Full Scan → 인덱스 추가
잘못된 Join → 통계 갱신
pg_locks / pg_blocking_pids
긴 트랜잭션 kill
IOPS 한도 초과?
swap 사용량?
범주 3: 데이터 손상
데이터 손상은 발생 빈도는 낮지만 가장 심각한 결과를 가져온다. 복구가 불완전하거나 손상을 뒤늦게 발견하면 실질적 데이터 손실로 이어진다.
손상의 두 유형
물리적 손상 (Physical corruption):
- 스토리지 하드웨어 결함, 배드 섹터
- RAID 실패, 비정상 셧다운으로 인한 partial write (torn page)
- InnoDB:
innodb_checksum_algorithm으로 페이지 체크섬 검증 - PostgreSQL:
data_checksums활성화 시 block-level 체크섬 감지
논리적 손상 (Logical corruption):
- 버그 있는 배포로 인한 잘못된 데이터 기록
- 실수로 실행한
DROP TABLE,DELETE WITHOUT WHERE - 외래키 제약 없이 관계를 깨는 배치 작업
- 충돌 없이 실행됐으나 비즈니스 규칙을 위반한 데이터
물리적 손상은 DB 엔진이 감지하지만, 논리적 손상은 애플리케이션 레이어 또는 사람이 발견하기 전까지 모른다.
SLI와 SLO
데이터 손상은 연속 지표가 아니다. 이벤트 카운트로 관리한다.
SLI: 물리적 체크섬 실패 이벤트 수 / 월
SLO: 0건 (감지 즉시 P0 알림)
SLI: 복구 불가능한 데이터 손실 바이트 수
SLO: 0 bytes (RTO/RPO에서 파생)물리적 손상 감지 후 대응 순서:
- 해당 인스턴스를 즉시 읽기 전용으로 전환하여 손상 확산 방지
- 복제본에 동일 문제가 전파됐는지 확인
- 마지막 정상 백업 시점 식별
- PITR 또는 백업 복구로 최소 손실 복구 시도
- 복구 후 checksum/row count/애플리케이션 smoke test 수행
예방 체크리스트
- [ ] InnoDB
innodb_checksum_algorithm = crc32(MySQL 기본값 확인) - [ ] PostgreSQL
initdb --data-checksums또는 pg_checksums 활성화 - [ ] 중요 테이블에 대한
DROP/TRUNCATEDDL에 명시적 승인 프로세스 - [ ] 대량 DML 전
pt-table-checksum또는 row count 사전 스냅샷 - [ ] 주 1회 이상 backup restore 검증 (전체 혹은 샘플)
범주 4: 복제 지연
복제 지연은 replica(secondary)가 primary의 변경 사항을 따라잡지 못하는 상태다. 읽기를 replica에서 분산하는 구조에서는 사용자에게 과거 데이터를 보여주는 현상으로 나타난다. 장바구니에 담은 상품이 안 보이거나, 결제 후 잔액이 갱신 안 된 것처럼 보이는 것이 대표적이다.
복제 지연의 주요 원인
| 원인 | MySQL | PostgreSQL |
|---|---|---|
| 단일 스레드 apply | 멀티스레드 복제 미설정 (slave_parallel_workers=0) | max_wal_senders 부족 |
| 대형 트랜잭션 | 수백만 row UPDATE 한 트랜잭션 | 대형 COPY/VACUUM |
| 쓰기 폭증 | DDL/배치 실행 중 복제 밀림 | 대량 INSERT 후 replica 따라잡기 |
| Replica 리소스 부족 | Primary보다 낮은 IOPS/CPU | 동일한 문제 |
| Replication slot 방치 | WAL이 쌓여 디스크 포화 | slot lag 미모니터링 |
복제 지연 확인 명령
-- MySQL: replica에서 실행
SHOW REPLICA STATUS\G
-- Seconds_Behind_Source: 현재 지연(초)
-- PostgreSQL: primary에서 실행
SELECT client_addr, state, sent_lsn, write_lsn, flush_lsn, replay_lsn,
(sent_lsn - replay_lsn) AS replication_lag_bytes
FROM pg_stat_replication;
-- PostgreSQL: replica에서 실행
SELECT now() - pg_last_xact_replay_timestamp() AS replication_delay;Split-brain: 복제 장애의 최악 시나리오
Split-brain은 primary 장애 시 자동 failover가 잘못 실행되어 두 노드가 동시에 쓰기를 받는 상태다. 두 primary에 기록된 데이터는 서로 충돌하며, 이후 합치는 과정에서 일부 데이터는 항상 손실된다.
Split-brain 발생 조건:
- 네트워크 분리로 primary가 살아있지만 replica에서 보이지 않을 때
- 자동 failover가 quorum 없이 동작할 때
- STONITH(Shoot the Other Node in the Head) 없이 HA 구성할 때
방지 수단:
- quorum 기반 failover (예: Orchestrator, Patroni, MHA)
- fencing 메커니즘: old primary를 강제 격리하거나 STONITH 설정
- failover 전 최소 N개 replica가 동의해야 승격 허용
- semi-synchronous replication으로 primary가 최소 1개 replica에 commit 확인 후 진행
복제 지연 SLO
SLI: replica lag (초) — pg_stat_replication 또는 Seconds_Behind_Source
SLO: 99%의 시간 동안 30초 이하 유지 (서비스 특성에 따라 조정)
경보 1단계: 30초 초과 → 원인 파악 시작
경보 2단계: 5분 초과 → 해당 replica로의 읽기 분산 중단 고려
경보 3단계: 30분 초과 → replica 상태 이상 가능성, P1 대응SLO 설계의 실용적 원칙
DB SLO를 처음 만들 때 흔히 하는 실수는 "DB 가용성 99.99%"처럼 단 하나의 지표로 전부를 설명하려는 것이다. DB 장애는 범주가 다양하기 때문에, 범주별로 별도의 SLI와 SLO를 정의해야 의미 있는 운영이 가능하다.
DB SLO 정의 템플릿
| 범주 | SLI 이름 | 측정 방식 | SLO 값 | Error Budget |
|---|---|---|---|---|
| 접속 가용성 | connection_success_rate | 성공 연결 / 전체 시도 | ≥ 99.9% | 분/월 43.8분 |
| 쓰기 지연 | p99_commit_latency_ms | p99 커밋 응답시간 | ≤ 100ms | 시간의 1% 허용 |
| 읽기 지연 | p99_read_latency_ms | p99 읽기 응답시간 | ≤ 50ms | 시간의 1% 허용 |
| 복제 신선도 | replica_lag_seconds | replica lag (초) | ≤ 30초 99% | - |
| 데이터 무결성 | corruption_events | checksum 실패 건수 | 0건/월 | 허용 없음 |
Error Budget 활용
SLO를 정의하면 자동으로 error budget이 생긴다. 예를 들어 p99 커밋 지연 SLO가 99%이면, 월 44분의 허용 예산이 생긴다. 이 예산이 소진되면:
- 기능 배포를 중단하고 안정화 작업을 우선시한다.
- 배포 전 load test, replay test를 의무화한다.
- 인덱스 추가, 파라미터 변경처럼 리스크 있는 작업을 뒤로 미룬다.
error budget은 DBA 혼자 관리하는 지표가 아니라, 개발팀·운영팀·비즈니스와 공유하는 협상 기준이다.
장애 수신 시 첫 60초 체크리스트
1. 애플리케이션 에러 타입 확인
- Connection refused / timeout → 접속 장애 범주
- Slow response, lock wait → 지연 장애 범주
- Data mismatch, checksum → 손상 범주
- Stale read, replica diverged → 복제 지연 범주
2. primary가 쓰기를 받고 있는가?
→ 못 받고 있다면: failover 상태, LB 전환 여부 확인
3. connection 사용률 확인
→ 80% 이상: pool/max_connections 증설 또는 긴 트랜잭션 종료
4. 최근 30분 이내 변경사항 있었는가?
→ 배포, DDL, 파라미터 변경, 배치 실행, 트래픽 급증
5. 완화 조치가 부작용을 만드는가?
→ 예: long transaction kill은 일부 작업 롤백 발생
→ 예: replica 승격은 기존 replica 재동기화 필요기억할 문장
"DB가 이상하다"는 말은 진단의 시작이 아니라 끝이다. 어느 범주의 장애인지를 30초 안에 구분할 수 있을 때, 비로소 의미 있는 완화와 회복이 시작된다. 범주 없이 SLO를 만들면 숫자는 있어도 행동이 나오지 않는다.
다음 장에서는 내구성을 보장하는 백업 전략 — full, incremental, logical, physical, snapshot — 과 보존 정책을 다룬다.
References
- Google SRE Book, Chapter 4: Service Level Objectives — https://sre.google/sre-book/service-level-objectives/
- Google SRE Workbook, Chapter 2: Implementing SLOs — https://sre.google/workbook/implementing-slos/
- Google SRE Workbook, Error Budget Policy — https://sre.google/workbook/error-budget-policy/
- Percona Blog, It's All About Replication Lag in PostgreSQL — https://www.percona.com/blog/its-all-about-replication-lag-in-postgresql/
- MySQL 8.4 Reference: Replication Lag — https://dev.mysql.com/doc/refman/8.4/en/replication-administration-status.html
- PostgreSQL Documentation: pg_stat_replication — https://www.postgresql.org/docs/current/monitoring-stats.html#MONITORING-PG-STAT-REPLICATION-VIEW
- Datadog Guide: Alert on anomalous p99 latency of a database service — https://docs.datadoghq.com/tracing/guide/alert_anomalies_p99_database/