나도 겪었던 Spring Boot 실수들 - 한 개발자의 회고에 공감하며..
🖇️ 출처: Don’t Repeat These 5 Costly Spring Boot Mistakes I Made for Years😞
최근 읽은 글 “Don’t Repeat These 5 Costly Spring Boot Mistakes I Made for Years” 에서는 Spring Boot 개발자가 겪기 쉬운 다섯 가지 실수가 잘 정리되어 있었습니다.
그 중, “계획대로 되지 않을 때 예를 들어 새벽 3시에 버그를 고칠 때 비로소 진짜 경험을 얻는다.” 이 문장을 읽는 순간, 정말 많은 상황들이 떠올랐습니다.
개발 당시에는 아무 문제 없어 보였던 코드가, 막상 운영에서 장애를 일으켰고, 그 문제를 추적하면서 왜 그 선택이 잘못됐는지 깨달았던 경험들이 떠올랐기 때문입니다.
이 글에서는 해당 블로그의 내용을 바탕으로, 제가 실제로 경험한 사례를 함께 정리해 보았습니다.
트랜잭션 안에 외부 API 호출? 운영 들어가면 반드시 터진다
저도 예전에 이런 식으로 코드를 작성한 적이 있었습니다.
데이터를 저장하는 메서드에 @Transactional을 붙이고, 그 안에서 외부 API까지 함께 호출했던 것이죠.
1
2
3
4
5
@Transactional
public void order(OrderCommand orderCommand) {
orderRepository.save(...);
orderNotifier.notify(...); // 외부 API 호출
}
테스트 환경에서는 아무 문제가 없었습니다.
알림도 잘 갔고, DB에도 정상적으로 데이터가 저장됐습니다. 하지만 외부 API가 간헐적으로 실패했고, 예외가 발생하면서 트랜잭션은 롤백됐지만 이미 알림은 발송되어 데이터 불일치가 발생한 것입니다. 이후부터는 외부 시스템과의 통신은 특별한 상황이 아니라면 트랜잭션 경계 밖으로 분리하도록 하고 있습니다.
트랜잭션은 데이터베이스의 일관성을 지키기 위한 도구이지, 외부 시스템의 상태까지 책임져주지는 않습니다. 연동이 필요한 경우라면, 그만큼 설계에 더 많은 고민이 필요하다는 걸 배웠습니다.
성능 문제? 결국 캐시가 포인트
API 응답 속도가 느려졌을 때 처음에는 늘 그렇듯 DB 성능이나 네트워크 문제부터 의심했습니다.
실제로 인덱스를 추가하거나 쿼리를 최적화하면서 어느 정도 개선되기도 했고, 당장은 문제가 해결된 것처럼 보였습니다.
하지만 사용량이 늘어나고 호출 빈도가 많아지자 다시 병목이 생겼고, DB 튜닝만으로는 한계가 있다는 걸 체감하게 됐습니다. 결국 같은 데이터를 반복해서 조회하고 있다는 사실에 주목하게 되었고, 그때부터 캐시를 함께 고려하기 시작했습니다.
지금은 읽기 빈도가 높은 데이터나 자주 변경되지 않는 정보에 대해 캐시를 적용하고 있습니다.
단순한 인메모리 캐시부터 시작해서, Redis 같은 분산 캐시로 확장도 가능하게 구조를 열어두었습니다.
성능 병목을 해결하려면 DB를 튜닝하는 것도 중요하지만, 읽기 자체를 줄일 수 있는 구조를 고민하는 게 훨씬 효과적이라는 걸 알게 됐습니다.
로그는 남기는 것보다 “찾을 수 있게” 남기는 게 중요합니다
처음에는 로그만 잘 찍으면 문제를 추적할 수 있다고 생각했습니다.
하지만 서비스가 분산되고 인스턴스가 늘어나면서, 어느 서버의 로그를 봐야 할지조차 알기 어려워졌고, 하나하나 뒤지는 일은 더 이상 효과적인 방법이 아니었습니다.
이후 ELK 스택(Elasticsearch, Logstash, Kibana)을 구성하고 나서부터는 상황이 많이 달라졌습니다.
중앙에서 로그를 수집하고 검색할 수 있게 되면서, 필요한 정보를 훨씬 빠르게 찾을 수 있었고, 장애 대응 속도도 확실히 빨라졌습니다.
분산 환경에서는 로그를 남기는 것보다, 어디서든 쉽게 찾을 수 있도록 만드는 것이 훨씬 더 중요합니다.
마무리 — 기술은 습관에서 시작된다
이 글을 읽고, 지난 몇 년간의 실수와 마주하게 됐습니다.
기술적인 실수라기보다는, 문제가 보일 때까지 방치했던 태도가 더 큰 원인이었습니다.
다시 같은 실수를 반복하지 않도록, 이제는 다르게 선택해보려 합니다.