1. 장애 대응
서비스를 운영하다 보면 장애를 완전히 피하기는 어렵다. 따라서 장애 자체를 막는 것뿐만 아니라 빠르게 발견하고 원인을 분석하여 복구한 뒤, 같은 장애가 반복되지 않도록 개선하는 과정이 중요하다.
전체적인 장애 대응 흐름은 다음과 같이 정리할 수 있다.
모니터링
↓
장애 식별
↓
장애 분석 및 진단
↓
장애 복구
↓
원인 분석 및 사후 평가
↓
재발 방지
1. 모니터링
가장 좋지 않은 장애 발견 방법은 사용자의 문의를 통해 장애를 처음 인지하는 것이다.
따라서 시스템의 상태를 지속적으로 관찰하고 이상 징후를 빠르게 발견할 수 있는 모니터링 환경이 필요하다.
1. 시스템 모니터링
- CPU 사용률
- 메모리 사용량
- Disk I/O
- 네트워크 대역폭
- 서버 가용성
- 응답 시간
리소스 사용량을 지속적으로 확인하면 성능 병목이나 장애 징후를 조기에 발견할 수 있다.
2. 애플리케이션 모니터링
- 로그 및 예외
- 응답 시간
- 처리량(Throughput)
- 오류율
- 서비스 Health Check
애플리케이션 로그와 메트릭을 함께 확인하면 단순히 장애 발생 여부뿐만 아니라 어느 부분에서 문제가 발생했는지 추적할 수 있다.
3. 사용자 경험 모니터링
서버가 정상이라고 해서 사용자가 반드시 서비스를 정상적으로 이용하고 있는 것은 아니다.
따라서 실제 사용자의 관점에서도 서비스를 모니터링할 필요가 있다.
- End-to-End 모니터링
- 사용자 피드백
- RUM(Real User Monitoring)
4. 알림 및 경고
특정 메트릭이 임계값을 초과하면 이메일, SMS, Slack 등의 채널을 통해 알림을 받을 수 있도록 구성할 수 있다.
단, 알림을 너무 많이 설정하면 중요한 장애를 구분하기 어려워질 수 있으므로 명확한 경고 기준을 설정하는 것이 중요하다.
2. 장애 분석 및 진단
장애가 발생하면 바로 코드를 수정하기보다 먼저 장애의 영향 범위와 원인을 파악해야 한다.
1. 초기 대응
- 장애 발생 인지
- 관련 팀에 장애 상황 공유
- 장애 심각도 및 영향 범위 평가
- 초기 대응 전략 수립
- 장애 상황 재현
- 로그와 메트릭을 이용한 원인 분석
장애가 발생했을 때 확인할 수 있는 대표적인 정보는 다음과 같다.
로그
애플리케이션에서 발생한 오류와 예외를 추적한다.
Error Log
Exception
Stack Trace
Request Log
메트릭
정상 상태와 장애 상태의 시스템 지표를 비교한다.
CPU
Memory
Disk I/O
Network
Response Time
Error Rate
시스템
- 실행 중인 프로세스 상태
- CPU / Memory 사용량
- Thread Dump
- Deadlock
- Thread 고갈
네트워크
- 패킷 손실
- 네트워크 지연
- 대역폭
- 외부 API 연결 상태
데이터베이스
- Slow Query
- DB Lock
- Deadlock
- Connection Pool
- 데이터 무결성
즉, 장애 분석은 하나의 로그만 확인하는 것이 아니라 로그 + 메트릭 + 시스템 + 네트워크 + DB 상태를 종합적으로 확인하는 과정이다.
3. 장애 복구
장애 원인을 찾는 것과 별개로 사용자에게 미치는 영향을 줄이기 위해 빠르게 서비스를 복구해야 한다.
1. 대표적인 복구 방법
데이터베이스 장애
- 백업 데이터 복원
- 데이터 무결성 검증
애플리케이션 장애
- 문제가 발생한 코드 수정
- 애플리케이션 재배포
- 캐시 초기화
- 정상 동작 여부 검증
시스템 장애
- 서버 재시작
- 디스크 공간 확보
- 리소스 상태 점검
네트워크 장애
- 네트워크 장비 상태 확인
- 네트워크 설정 변경
- 연결 상태 확인
2. Failover
주 시스템에 장애가 발생했을 때 미리 준비된 Standby 시스템으로 트래픽을 전환하는 방식이다.
정상 상태
Client
↓
Load Balancer
↓
Primary Server
Primary 장애 발생
Client
↓
Load Balancer
↓
Standby Server
이를 통해 하나의 시스템에 장애가 발생하더라도 서비스를 계속 제공할 수 있다.
4. 장애 발생 이후
서비스를 복구했다고 장애 대응이 끝나는 것은 아니다.
같은 문제가 다시 발생하지 않도록 근본 원인을 분석하고 대응 과정을 기록해야 한다.
1. RCA(Root Cause Analysis)
장애의 표면적인 현상이 아니라 근본적인 원인을 찾는 과정이다.
대표적인 방법으로 5 Whys가 있다.
문제: 배포 실패
왜?
→ 코드 충돌 발생
왜?
→ 충분한 테스트가 이루어지지 않음
왜?
→ 실제 배포 환경에서 테스트하지 않음
왜?
→ 배포 자동화에 문제가 있음
왜?
→ 자동화 스크립트가 최신 변경사항을 반영하지 못함
단순히 "코드 충돌을 해결한다"에서 끝나는 것이 아니라 왜 코드 충돌이 배포 실패까지 이어질 수 있었는지 계속 추적하는 것이다.
2. 장애 기록
장애 발생부터 해결까지의 과정을 시간 순서대로 기록한다.
장애 발생 시점
→ 장애 인지
→ 영향 범위 확인
→ 원인 분석
→ 임시 조치
→ 복구
→ 근본 원인 분석
→ 재발 방지 조치
또한 다음 내용을 함께 기록한다.
- 장애 원인
- 수행한 대응 조치
- 대응 결과
- 커뮤니케이션 내용
- 개선해야 할 부분
이를 바탕으로 SOP(Standard Operating Procedure)를 개선하고 향후 동일한 장애가 발생했을 때 더 빠르게 대응할 수 있다.
5. 장애 예방
장애 대응 경험을 바탕으로 시스템 자체를 장애에 강한 구조로 개선할 수 있다.
1. 고가용성 아키텍처
Clustering
여러 서버를 하나의 시스템처럼 구성하여 하나의 서버에 장애가 발생하더라도 다른 서버가 서비스를 계속 수행하도록 한다.
장점
- 고가용성
- 부하 분산
- 장애 발생 시 대체 서버 활용
Load Balancing
트래픽을 여러 서버에 분산시켜 특정 서버에 요청이 집중되는 것을 방지한다.
장점
- 확장성
- 가용성
- 서버 부하 분산
Geo-Redundancy
데이터와 시스템을 지리적으로 떨어진 여러 지역에 구성한다.
특정 지역이나 데이터센터에 장애가 발생해도 다른 지역을 통해 서비스를 지속할 수 있다.
2. 무중단 배포
배포 자체가 장애의 원인이 될 수 있기 때문에 배포 과정에서 문제가 발생하더라도 영향을 최소화하는 전략을 사용할 수 있다.
- Blue-Green Deployment
- Canary Release
6. DB Lock
여러 트랜잭션이 동시에 동일한 데이터를 읽고 수정하면 데이터 일관성이 깨질 수 있다.
DB Lock은 여러 트랜잭션이 동시에 동일한 데이터에 접근할 때 데이터의 무결성과 일관성을 유지하기 위한 동시성 제어 방법이다.
3. 동시성으로 발생할 수 있는 문제
Dirty Read
다른 트랜잭션이 아직 Commit하지 않은 데이터를 읽는 문제
Transaction A
5000 → 6000
아직 Commit X
Transaction B
6000 조회
Transaction A
Rollback → 5000
Transaction B는 실제로 존재하지 않게 된 6000이라는 값을 사용하게 된다.
Non-repeatable Read
하나의 트랜잭션에서 동일한 데이터를 두 번 조회했는데 중간에 다른 트랜잭션이 데이터를 수정하여 조회 결과가 달라지는 문제
Lost Update
두 트랜잭션이 같은 데이터를 동시에 수정하면서 한쪽의 변경 사항이 다른 변경 사항에 의해 덮어쓰이는 문제
7. DB Lock 종류
1. Shared Lock
데이터를 읽기 위한 락이다.
여러 트랜잭션이 동시에 데이터를 읽을 수 있지만 Shared Lock이 존재하는 동안 해당 데이터를 수정하는 작업은 제한된다.
Transaction A → READ
Transaction B → READ 가능
Transaction C → WRITE 대기
2. Exclusive Lock
데이터를 수정할 때 사용하는 락이다.
Exclusive Lock이 걸려 있는 동안 다른 트랜잭션의 접근이 제한된다.
Transaction A → WRITE + LOCK
Transaction B → 접근 대기
Transaction C → 접근 대기
Transaction A Commit
↓
Lock 해제
8. 비관적 락(Pessimistic Lock)
데이터 충돌이 발생할 가능성이 높다고 가정하고 데이터를 조회하는 시점부터 락을 획득하는 방식이다.
Spring Data JPA에서는 다음과 같이 사용할 수 있다.
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("select i from Item i where i.id = :id")
Item findByIdWithLock(Long id);
트랜잭션 A가 데이터를 조회하여 락을 획득하면 다른 트랜잭션은 해당 락이 해제될 때까지 기다리게 된다.
Transaction A
SELECT + LOCK
↓
데이터 수정
↓
COMMIT
↓
LOCK 해제
Transaction B
↓
대기
↓
작업 수행
장점
- 데이터 충돌을 확실하게 방지할 수 있음
- 충돌 가능성이 높은 환경에 적합
단점
- 다른 트랜잭션이 락 해제를 기다려야 함
- 동시 처리 성능이 떨어질 수 있음
따라서 충돌 가능성이 높은 데이터를 다룰 때 적합하다.
9. 낙관적 락(Optimistic Lock)
충돌이 자주 발생하지 않을 것이라고 가정하고 실제 DB Lock을 사용하기보다는 데이터를 수정하는 시점에 충돌 여부를 확인하는 방식이다.
일반적으로 version 값을 이용한다.
@Version
private Integer version;
두 트랜잭션이 동시에 다음 데이터를 조회했다고 가정한다.
price = 10000
version = 1
Transaction A가 먼저 수정한다.
price = 20000
version = 2
이후 Transaction B가 자신이 조회했던 version = 1을 이용해 수정하려고 한다.
하지만 현재 DB의 version은 2이므로 충돌이 발생한 것을 알 수 있다.
Transaction A ── version 1 ── UPDATE ──> version 2
Transaction B ── version 1 ── UPDATE ──> 충돌!
충돌이 발생하면 예외를 발생시키거나 비즈니스 로직에 따라 재시도할 수 있다.
장점
- 실제 DB Lock을 오래 점유하지 않음
- 병행 처리 성능이 좋음
단점
- 충돌 발생 시 재시도 또는 예외 처리 필요
- 데이터 변경이 빈번하면 충돌이 계속 발생할 수 있음
따라서 충돌 가능성이 낮고 동시에 많은 요청을 처리해야 하는 환경에서 유리하다.
10. 비관적 락 VS 낙관적 락
| 구분 | 비관적 락 | 낙관적 락 |
| 기본 가정 | 충돌이 자주 발생 | 충돌이 적게 발생 |
| 방식 | 먼저 Lock 획득 | 수정 시 충돌 검사 |
| 구현 | DB Lock | Version |
| 충돌 처리 | 다른 트랜잭션 대기 | 예외 / 재시도 |
| 장점 | 데이터 충돌 방지에 강함 | 동시 처리 성능이 좋음 |
| 단점 | Lock 대기로 성능 저하 가능 | 충돌 처리 로직 필요 |
| 적합한 상황 | 충돌이 빈번한 데이터 | 충돌이 드문 데이터 |
11. Deadlock
두 개 이상의 트랜잭션이 서로 상대방이 가지고 있는 Lock을 기다리면서 작업을 진행하지 못하는 상태이다.
Transaction A
Item 1 Lock
↓
Item 2 Lock 기다림
↑
│
│
Transaction B
Item 2 Lock
↓
Item 1 Lock 기다림
서로 상대방의 Lock이 해제되기를 기다리기 때문에 작업을 진행할 수 없다.
따라서 Lock을 사용하는 경우에는
- Lock 획득 순서를 일정하게 유지
- 트랜잭션 범위를 짧게 유지
- Timeout 설정
등을 고려해야 한다.
12. DB 복제 지연
읽기와 쓰기 DB를 분리하면 다음과 같이 구성할 수 있다.
Application
│
├── WRITE → Primary DB
│ │
│ Replication
│ ↓
└── READ ─→ Replica DB
문제는 Primary DB에 저장된 데이터가 Replica DB에 즉시 반영되지 않을 수 있다는 것이다.
Primary DB에 데이터 저장
↓
Replication 지연
↓
Replica DB 조회
↓
이전 데이터 반환
이를 Replication Lag(복제 지연)이라고 한다.
해결 방법
- 쓰기 직후 일정 시간 후 다시 조회
- 중요한 데이터는 쓰기 직후 Primary DB에서 조회(Read-after-Write)
- DB 복제 설정 최적화
- CQRS 등의 구조 활용
- 캐시 활용
13. Memory Leak
더 이상 필요하지 않은 메모리가 해제되지 않고 계속 점유되는 현상이다.
메모리 할당
↓
사용
↓
더 이상 필요 없음
↓
참조가 계속 존재
↓
GC가 제거하지 못함
↓
메모리 사용량 증가
메모리 릭이 지속되면 메모리가 점점 부족해지면서 성능 저하나 애플리케이션 장애가 발생할 수 있다.
대표적인 원인
- 이벤트 리스너 해제 누락
- 불필요한 전역 객체 참조
- 캐시 데이터 제거 실패
- Collection에 데이터를 계속 추가
ThreadLocal정리 누락
특히 Thread Pool 환경에서 ThreadLocal을 사용한다면 작업이 끝난 후 제거해야 한다.
try {
threadLocal.set(value);
// 로직 수행
} finally {
threadLocal.remove();
}
14. Cache Pressure
캐시 데이터가 계속 증가하면서 캐시에 사용할 수 있는 메모리가 부족해지는 현상이다.
예를 들어 사용자별 페이징 결과를 모두 캐싱하면 다음처럼 캐시 Key가 계속 증가할 수 있다.
user1:page:1
user1:page:2
user1:page:3
user2:page:1
user2:page:2
user3:page:1
...
이 경우 다음과 같은 문제가 발생할 수 있다.
- 캐시 메모리 부족
- 기존 캐시 데이터 제거 증가
- Cache Hit Rate 감소
- 사용자 간 캐시 공간 경쟁
- 요청 처리 지연
해결 방법
- TTL 설정
- 최대 캐시 크기 제한
- 자주 조회되는 데이터만 캐싱
- 캐싱 단위를 작게 설계
- DB 인덱스 최적화를 통해 불필요한 캐시 사용 감소
- Redis와 같은 분산 캐시 활용
- 필요에 따라 캐시 우회
또한 캐시 장애로 모든 요청이 DB로 한꺼번에 전달되면 DB에 큰 부하가 발생할 수 있기 때문에 주의해야 한다.
15. 설정 버전 관리
애플리케이션의 설정값을 단순히 덮어쓰는 것이 아니라 버전별로 저장하고 관리하는 방법이다.
Config v1
↓
Config v2
↓
Config v3 ← 현재 설정
v3 설정으로 장애가 발생한다면 이전 설정으로 빠르게 돌아갈 수 있다.
Config v3 장애 발생
↓
Rollback
↓
Config v2
장점
- 장애 발생 시 빠른 Rollback
- 설정 변경 이력 추적
- 테스트 후 운영 설정 적용 가능
- 설정 변경으로 발생한 장애 원인 추적
- 팀원 간 설정 상태 공유
단점
- 관리해야 하는 버전 증가
- 저장 공간 증가
- 잘못된 버전 선택 등의 인적 오류 가능성
📌 정리
장애 대응에서 중요한 것은 단순히 장애가 발생한 후 코드를 수정하는 것이 아니다.
모니터링
→ 장애를 빠르게 발견
로그 / 메트릭 분석
→ 원인을 진단
복구 / Failover
→ 사용자 영향을 최소화
RCA / Postmortem
→ 근본 원인을 분석
아키텍처 및 프로세스 개선
→ 같은 장애의 재발 방지
또한 실제 서비스에서는 DB Lock, Deadlock, DB 복제 지연, Memory Leak, Cache Pressure처럼 정상적인 기능 구현만으로는 쉽게 발견하기 어려운 운영상의 문제가 발생할 수 있다.
결국 안정적인 서비스를 만들기 위해서는 기능 구현뿐만 아니라 장애를 어떻게 발견하고, 복구하고, 다시 발생하지 않게 할 것인지까지 고려하는 것이 중요하다.
'개발 공부 > Java' 카테고리의 다른 글
| [SPRING] JPA와 MyBatis (0) | 2026.10.09 |
|---|---|
| [Spring AI] LLM을 백엔드 서비스에 연결하기 - ChatClient와 컨텍스트 (0) | 2026.08.24 |
| [TIL] 모니터링 시스템 및 시큐어 코딩 (0) | 2026.08.21 |
| [TIL] 대규모 시스템 설계 기초 (0) | 2026.07.28 |
| [TIL] Redis 자료구조와 캐싱(Cache) (0) | 2026.07.28 |
