Case Studies
작성 원칙
- 회사 내부 정보는 제거한다.
- 시스템명, 테이블명, URL, 로그 원문은 쓰지 않는다.
- 문제 유형과 해결 패턴 중심으로 일반화한다.
- 이력서에는 결과 중심, 플레이북에는 원리 중심으로 정리한다.
사례 작성 형식
사례는 길게 쓰기보다 문제 해결 흐름이 보이게 쓴다.
| 구간 | 쓸 내용 |
|---|---|
| 문제 | 사용자가 겪은 증상 또는 운영 지표 변화 |
| 원인 가설 | 처음 의심한 지점과 확인한 근거 |
| 확인 방법 | 로그, 메트릭, 실행 계획, 테스트, 재현 조건 |
| 해결 | 적용한 변경과 선택 이유 |
| 한계 | 남은 위험, 비용, 대안 |
| 설명 요약 | 1분 안에 말할 수 있는 요약 |
좋은 사례 기준
- 숫자는 공개 가능한 범위에서만 사용한다.
- 특정 회사, 서비스, 테이블, 고객을 식별할 수 없어야 한다.
- “개선했다”보다 “어떻게 병목이나 원인을 좁혔는가”가 보여야 한다.
- 성공한 결과뿐 아니라 선택하지 않은 대안과 이유가 있어야 한다.
사례 목록
| 우선순위 | 사례 | 연결 개념 | 핵심 질문 | 상태 |
|---|---|---|---|---|
| 1 | 조회 API 성능 개선 | JPA, 실행 계획, 인덱스 | 병목을 쿼리 수, 실행 계획, 응답 시간 중 어디서 확인했는가? | Draft |
| 2 | Redis 캐싱 적용 | Cache Aside, TTL, 캐시 정합성 | 캐시 만료와 원본 데이터 변경 시점을 어떻게 맞췄는가? | Draft |
| 3 | 외부 API 중복 요청 처리 | 멱등성, Retry, Timeout | 재시도 때문에 중복 처리가 발생하지 않도록 무엇을 보장했는가? | Draft |
| 4 | 배치 안정화 | Scheduler, 재처리, 모니터링 | 실패한 작업을 어디서부터 안전하게 재시작했는가? | Draft |
| 5 | 배포 안정화 | CI/CD, 롤백, 설정 관리 | 배포 실패를 어떻게 감지하고 되돌렸는가? | Draft |
| 6 | 로그/알림 개선 | Observability, traceId, Alert | 장애 원인 추적 시간을 줄이기 위해 어떤 신호를 남겼는가? | Draft |
사례 1: 조회 API 성능 개선
문제
목록 조회 API의 응답 시간이 데이터 증가 후 길어졌다. 로컬이나 개발 환경에서는 재현되지 않았지만, 운영과 비슷한 데이터 크기에서는 특정 검색 조건과 정렬 조합에서 지연이 커졌다.
원인 가설
처음에는 애플리케이션 로직이 느리다고 의심할 수 있지만, 조회 API에서는 먼저 아래를 분리해서 확인한다.
- 쿼리 수가 많아진 것인지
- 단일 SQL 실행 시간이 긴 것인지
- 정렬, 페이징, count query가 병목인지
- 응답 데이터가 너무 큰 것인지
이 사례에서는 SQL 로그와 query count를 확인했을 때 N+1은 아니었고, 실행 계획에서 검색 조건과 정렬 조건에 맞는 인덱스를 제대로 사용하지 못하는 것이 핵심 원인으로 드러났다.
확인 방법
- 애플리케이션 로그에서 API elapsed time과 실행된 SQL 수를 확인한다.
- slow query 로그나 query profiler로 오래 걸리는 SQL을 찾는다.
EXPLAIN으로 접근 방식, 사용 인덱스, 예상 row 수, 정렬 방식을 확인한다.- 운영과 비슷한 데이터 분포에서 개선 전후 응답 시간을 비교한다.
- 목록 query와 count query를 분리해서 각각의 비용을 확인한다.
해결
검색 조건과 정렬 조건을 기준으로 복합 인덱스를 재검토했다. 목록 조회에서는 필요한 컬럼만 반환하도록 projection을 적용하고, 큰 offset이 문제가 되는 경로는 커서 기반 조회를 검토했다.
JPA 연관 조회가 필요한 화면에서는 fetch join을 무조건 추가하지 않고, row 폭증 가능성과 페이지네이션 제약을 함께 확인했다. 단순 목록 화면은 DTO 조회로 전환해 엔티티 그래프 로딩 비용을 줄였다.
한계
- 인덱스 추가는 조회를 빠르게 만들 수 있지만 쓰기 비용과 저장 공간을 증가시킨다.
- 특정 검색 조건에 맞춘 인덱스는 다른 조건에서는 효과가 없을 수 있다.
- 커서 기반 페이징은 큰 offset 문제를 줄이지만 임의 페이지 이동에는 불리하다.
- fetch join은 N+1을 줄일 수 있지만 다대다 형태에서는 중복 row와 메모리 사용량이 커질 수 있다.
설명 요약
조회 API가 느려졌을 때는 먼저 애플리케이션 로직, 쿼리 수, 단일 SQL 비용을 분리해서 봅니다. SQL 로그로 N+1 여부를 확인하고, 느린 SQL은 EXPLAIN으로 인덱스 사용 여부와 scan row, 정렬 방식을 확인합니다. 개선은 인덱스 추가만으로 끝내지 않고 projection, fetch 전략, 페이징 방식, count query 비용을 같이 봅니다. 개선 후에는 운영과 비슷한 데이터 크기에서 p95 응답 시간과 DB 지표를 비교해 효과와 부작용을 확인합니다.
관련 문서
사례 2: Redis 캐싱 적용
문제
반복 조회가 많은 API에서 DB 부하와 응답 시간이 함께 증가했다. 데이터는 자주 읽히지만 변경 빈도는 낮았고, 모든 요청이 DB를 직접 조회하면서 peak 시간대 connection 사용량이 높아졌다.
원인 가설
먼저 느린 원인이 DB query 자체인지, 같은 결과를 반복해서 조회하는 구조인지, 응답 생성 비용인지 분리한다. 이 사례에서는 query 튜닝만으로는 반복 조회 비용을 줄이기 어려웠고, 동일 조건 조회가 짧은 시간에 많이 반복되는 패턴이 확인됐다.
확인 방법
- API별 cache hit 후보를 access log와 query count로 확인한다.
- 데이터 변경 빈도와 허용 가능한 stale 시간을 정리한다.
- 캐시 적용 전후 DB QPS, latency, Redis hit ratio를 비교한다.
- 캐시 장애 시 DB와 애플리케이션이 버틸 수 있는지 fallback 기준을 확인한다.
해결
Cache Aside 패턴으로 먼저 캐시를 조회하고, 없으면 DB에서 조회한 뒤 TTL과 함께 저장했다. 원본 데이터가 바뀌는 경로에서는 관련 cache key를 삭제하거나 짧은 TTL을 사용해 stale window를 제한했다. 캐시 key는 조회 조건을 명확히 포함하고, null 결과도 짧게 캐싱해 반복 miss를 줄였다.
한계
- 캐시는 원본 데이터와 정합성 차이가 생길 수 있다.
- TTL이 길면 stale data가 오래 남고, TTL이 짧으면 hit ratio가 낮아진다.
- Redis 장애 시 DB로 트래픽이 몰릴 수 있으므로 fallback과 rate limit을 고려해야 한다.
- 캐시 key 설계가 부정확하면 잘못된 응답을 재사용할 수 있다.
설명 요약
캐시를 적용할 때는 먼저 반복 조회인지, 데이터 변경 빈도와 stale 허용 범위가 어느 정도인지 확인합니다. Cache Aside는 구현이 단순하지만 원본 변경과 캐시 무효화 사이에 정합성 차이가 생길 수 있습니다. 그래서 key 설계, TTL, 변경 시 삭제 정책, Redis 장애 시 fallback을 함께 정해야 합니다. 적용 후에는 hit ratio뿐 아니라 DB QPS, p95 latency, Redis 오류율을 같이 봅니다.
관련 문서
사례 3: 외부 API 중복 요청 처리
문제
외부 API 호출이 timeout으로 실패했지만 실제 외부 시스템에서는 처리가 완료되는 경우가 있었다. 클라이언트나 서버가 재시도하면 같은 업무 요청이 두 번 처리될 수 있었다.
원인 가설
timeout은 실패가 아니라 결과를 모르는 상태일 수 있다. 따라서 네트워크 실패, 외부 처리 성공, 응답 손실, 서버 재시도 중 어디에서 중복이 생기는지 나눠 확인해야 한다.
확인 방법
- 요청마다 business key 또는 idempotency key가 있는지 확인한다.
- timeout, retry, 외부 API 응답 상태를 trace id로 연결한다.
- 같은 key 요청이 동시에 들어올 때 DB unique 제약이나 상태 전이가 중복 처리를 막는지 테스트한다.
- 실패 후 재시도 시 같은 결과로 수렴하는지 확인한다.
해결
같은 의도의 요청에는 같은 idempotency key를 사용했다. 서버는 key, payload hash, 처리 상태를 저장하고, 이미 완료된 요청이면 저장된 결과를 반환했다. 외부 API가 idempotency key를 지원하면 같은 key를 전달하고, 지원하지 않으면 내부 상태 전이와 unique 제약으로 중복 처리를 막았다.
한계
- 같은 key에 다른 payload가 들어오면 충돌 정책이 필요하다.
- 외부 API가 멱등 키를 지원하지 않으면 내부 보장 범위가 제한된다.
- key 저장 기간이 짧으면 늦은 재시도를 막지 못한다.
- timeout 이후 결과 확인 API가 없으면 보상 처리나 수동 확인 절차가 필요할 수 있다.
설명 요약
외부 API timeout은 처리 실패가 아니라 결과 미확정 상태로 봐야 합니다. 재시도 때문에 중복 처리가 생기지 않도록 같은 의도의 요청에는 idempotency key를 붙이고, key와 payload hash, 처리 상태를 저장합니다. 완료된 key가 다시 들어오면 같은 결과를 반환하고, 다른 payload면 충돌로 처리합니다. 외부 시스템이 같은 보장을 제공하지 않으면 내부 상태 전이와 unique 제약으로 최소한의 중복 처리를 막습니다.
관련 문서
사례 4: 배치 안정화
문제
주기적으로 실행되는 배치가 중간에 실패하면 어떤 데이터까지 처리됐는지 확인하기 어려웠다. 재실행하면 일부 데이터가 중복 처리될 수 있고, 실행하지 않으면 누락 데이터가 남는 문제가 있었다.
원인 가설
배치 안정성 문제는 스케줄러보다 처리 단위와 재처리 기준에서 시작한다. 실패 시점, 처리 완료 표시, 중복 실행 가능성, 작업별 멱등성을 확인해야 한다.
확인 방법
- 작업 단위별 상태가
pending,processing,completed,failed처럼 남는지 확인한다. - 배치 실행 id, 처리 건수, 실패 건수, 마지막 처리 위치를 로그와 메트릭으로 남긴다.
- 같은 작업을 두 번 실행해도 결과가 깨지지 않는지 테스트한다.
- 이전 실행이 끝나기 전 다음 실행이 시작되는지 확인한다.
해결
큰 작업을 chunk 단위로 나누고 각 chunk 또는 대상 row의 처리 상태를 저장했다. 중복 실행이 위험한 작업은 lock 또는 scheduler의 concurrency policy로 겹침을 막았다. 실패한 작업은 실패 사유와 재시도 가능 여부를 남기고, 재처리는 failed 또는 특정 기준 이후 데이터만 대상으로 제한했다.
한계
- 상태 테이블과 재처리 로직이 추가되어 구현 복잡도가 늘어난다.
- 외부 API 호출이 포함되면 내부 상태만으로 정확한 결과를 알 수 없다.
- lock만으로 멱등성을 대체할 수 없다.
- 처리 단위를 너무 작게 나누면 관리 비용이 커지고, 너무 크게 나누면 재처리 비용이 커진다.
설명 요약
배치는 실패하지 않는 것보다 실패했을 때 어디서부터 다시 시작할 수 있는지가 중요합니다. 처리 단위를 chunk로 나누고 각 단위의 상태, 실행 id, 처리 건수, 실패 사유를 남깁니다. 재실행해도 결과가 깨지지 않도록 멱등성을 설계하고, 중복 실행이 위험하면 lock이나 CronJob concurrency policy를 사용합니다. 운영에서는 성공 여부뿐 아니라 처리 건수, 실패 건수, 실행 시간을 알림 기준으로 봅니다.
관련 문서
사례 5: 배포 안정화
문제
새 버전 배포 후 일부 API 오류율이 증가했지만, 배포 자체는 성공으로 표시됐다. 문제가 코드인지 설정인지, 새 Pod 준비 문제인지 구분하는 데 시간이 걸렸다.
원인 가설
배포 안정성은 pipeline 성공 여부만으로 판단할 수 없다. 새 버전 Pod의 readiness, 설정 주입, error rate, latency, rollback 가능성을 함께 확인해야 한다.
확인 방법
- rollout status와 Pod event를 확인한다.
- 배포 전후 error rate, p95 latency, restart count, readiness failure를 비교한다.
- ConfigMap, Secret, image tag가 의도한 값인지 확인한다.
- rollback 시 DB schema와 설정이 이전 버전과 호환되는지 검토한다.
해결
readiness probe를 기준으로 준비된 Pod만 트래픽을 받게 하고, rolling update 전략에서 가용 Pod 수가 유지되도록 설정했다. 배포 후에는 일정 시간 핵심 지표를 확인하고, 오류율이 기준을 넘으면 rollback하도록 운영 절차를 정리했다. 설정 변경과 코드 변경은 가능한 한 분리해 원인 추적을 쉽게 했다.
한계
- rollback은 Pod template을 되돌릴 수 있지만 DB migration이나 외부 상태 변경은 자동으로 되돌리지 못한다.
- readiness 기준이 부정확하면 배포 전략이 있어도 장애를 막지 못한다.
- 지표 기준이 없으면 자동화된 배포 성공 판단이 어렵다.
- replica가 너무 적으면 rolling update 중 가용성 확보가 어렵다.
설명 요약
배포 성공은 pipeline 종료가 아니라 새 버전이 정상 지표를 유지하는지까지 확인해야 합니다. Kubernetes에서는 rollout 상태, readiness, Pod event, error rate, latency를 함께 봅니다. 무중단에 가깝게 배포하려면 replica 수, readiness probe, rolling update 전략이 맞아야 합니다. rollback은 준비하되 DB 변경이나 외부 상태 변경이 있으면 이전 버전 호환성을 먼저 설계해야 합니다.
관련 문서
사례 6: 로그/알림 개선
문제
장애가 발생했을 때 사용자의 요청 흐름을 따라가기 어려웠다. 로그는 많았지만 어떤 요청에서 어떤 외부 호출과 DB 작업이 이어졌는지 연결되지 않았고, 알림은 늦거나 너무 많이 발생했다.
원인 가설
관측성 문제는 로그 양 부족이 아니라 질문에 답할 수 있는 신호 부족일 수 있다. 장애 분석에 필요한 trace id, 핵심 상태 변화 로그, 지표, 알림 기준이 있는지 확인해야 한다.
확인 방법
- 요청 시작부터 외부 API, DB, 비동기 처리까지 같은 trace id가 전달되는지 확인한다.
- 오류 로그에 사용자 식별 가능한 민감 정보 없이 원인 분류가 남는지 본다.
- latency, error rate, saturation 지표가 endpoint나 dependency 단위로 있는지 확인한다.
- 알림이 SLO나 사용자 영향과 연결되는지 확인한다.
해결
요청 단위 trace id와 correlation id를 표준화하고, 외부 API 호출과 비동기 메시지에도 전달했다. 로그는 모든 값을 남기기보다 상태 전이, 실패 사유, dependency, elapsed time 중심으로 정리했다. 알림은 단순 error count보다 오류율, p95 latency, 주요 batch 실패처럼 행동 가능한 조건으로 조정했다.
한계
- 로그를 늘리면 비용과 노이즈가 증가한다.
- trace id가 모든 경계에 전달되지 않으면 흐름이 끊긴다.
- 알림 기준이 낮으면 피로도가 높아지고, 높으면 감지가 늦어진다.
- 민감 정보 마스킹 없이 로그를 늘리면 보안 위험이 커진다.
설명 요약
장애 분석을 빠르게 하려면 많은 로그보다 연결 가능한 신호가 필요합니다. 요청에는 trace id를 부여하고, 외부 API와 비동기 처리까지 전달해 흐름을 이어야 합니다. 로그는 상태 변화, 실패 사유, dependency, elapsed time을 중심으로 남기고 민감 정보는 제외합니다. 알림은 단순 로그 개수가 아니라 사용자 영향과 연결되는 오류율, 지연 시간, 실패율 기준으로 잡습니다.