커스텀 메트릭 하나 추가한 게 전부였다. "사용자별 API 응답 시간 추적하면 좋겠다"는 PM의 요청에 @Timed 어노테이션에 userId 태그를 하나 끼워 넣었고, 스테이징에서 잘 돌았고, 배포했다.
쿠폰 발급 테이블에 동시 수정이 가능하다는 코드 리뷰가 달렸고, 다음 날 PR에 @Version 필드가 추가됐다. 낙관적 락을 걸면 충돌 시 예외가 터지니까 안전하다 — 이론적으로는 맞다.
새벽 4시 반, 슬랙에 "DB connection pool exhausted" 알림이 쏟아졌다. DB 서버의 CPU 사용률은 15%.
readiness probe 설정할 때 "/actuator/health 쓰면 되죠?"라고 대답하는 개발자가 많다.
동시에 두 명이 포인트를 사용하면 잔액이 마이너스가 되지 않는 게 당연하다고 생각했다. 그 생각이 틀렸다는 걸 화요일 오후 CS 인입량이 알려줬다.
캐시 정리의 최적화라고 생각했다. 서비스 전체에 흩어진 @Cacheable 설정을 보니 TTL이 3분, 7분, 15분, 30분 — 제각각이었다.
새벽 2시, 슬랙 알림이 울린다. 결제 완료 후 포인트 적립이 안 됐다는 CS가 3건 들어왔다.
지난달 팀에서 Spring Boot 4로 올리면서 "Jackson 3? 패키지명만 바뀌었겠지"라고 생각했다.
재시도 로직은 백엔드 개발자의 안전장치지만, 서비스 체인에 겹겹이 쌓이면 증폭기로 돌변한다. 지난달 결제 API 장애 때 우리가 겪은 일이 정확히 이것이었고, 새벽 2시에 PagerDuty가 울렸을 때 처음 본 지표는 외부 API 오류가 아니라 우리 서버의 스레드 풀 고갈이었다.
주문 API에서 결제 게이트웨이 연동 하나 추가한 게 전부였다. 배포하고 트래픽 올라가자마자 HikariCP 커넥션 대기 큐가 쌓이기 시작했고, 30초 만에 ConnectionTimeoutException이 터졌다.
배포할 때마다 502가 몇 건씩 찍힌다. Grafana 봐도 서버 과부하가 아니고, Rolling Update가 돌 때만 나타난다.
운영 서버에 Virtual Thread를 적용한 지 이틀 만에 Slack 알림이 울렸다. Connection is not available, request timed out after 30000ms.
3일 전에 findById 하고 save 했더니 데이터가 두 줄 생긴 이야기를 썼다. 그 글을 쓰면서도 속으로 생각했다 — "이걸 왜 아직도 애플리케이션 레벨에서 해결해야 하지?
작년 겨울, 트래픽 피크 때마다 HPA가 Pod를 늘리는데 Spring Boot 앱이 뜨는 데 12초가 걸렸다. 12초면 이미 늦다.
새벽 2시, 주문 서비스 마스터 DB CPU가 97%로 치솟았다는 알림이 왔다. Replica 세 대는 15%에서 조용히 돌고 있었다.
새벽 2시에 슬랙이 울렸는데, 같은 푸시 알림이 고객한테 3번 갔다는 제보였다. 서버 3대가 각자 @Scheduled 메서드를 실행한 거다.
"Redis 앞에 Caffeine 하나 놓으면 빨라지잖아요." 코드 리뷰에서 이 말이 나오면 반은 맞고 반은 틀리다.
운영 환경에서 repository.saveAll(list)를 호출하고 슬로우 쿼리 로그를 열었더니 INSERT 문이 10,000줄 찍혀 있었다.