Skip to Content
Software Architecture레거시 주문 이력 서비스 현대화
🏗️ Software Architecture2025년 12월 15일

레거시 주문 이력 서비스 현대화

#e-commerce#modernization#software-architecture

이 글에서 다루는 서비스는 내부적으로 BLOHA(Breeze Legacy Order History API)라고 불렀던 레거시 주문 이력 서비스다. 한국 전자상거래법이 요구하는 5년 주문 이력 보관 의무를 이행하기 위해 운영되던 이 서비스를 2024년 7월부터 2025년 11월까지 단계적으로 현대화한 과정을 기록한다. NewRelic에서 SignalFx로의 모니터링 전환, Spring Boot 2.7에서 3.5.6으로의 대규모 업그레이드(Java 11→21, CVE 수십 건 해결), 그리고 최신 패치 적용까지, 레거시 서비스 유지보수의 현실을 보여준다.

BLOHA는 기능 개발보다 유지보수와 현대화에 더 많은 시간이 투입되는 서비스다. 비즈니스 로직은 거의 변하지 않지만, 인프라, 보안, 의존성은 끊임없이 업데이트해야 한다. 겉으로 보면 이미 끝난 주문을 조회하는 단순 API처럼 보이지만, 실제 운영에서는 법적 보관 의무, 보안 스캔 정책, 모니터링 체계 전환, 런타임 교체가 모두 연결되어 있었다. 이 글은 그러한 유지보수 작업이 실제로 어떤 범위와 복잡도를 갖는지를 기록한다.

BLOHA는 Nike의 주문 이력 조회 API로, 한국 전자상거래법(전자상거래 등에서의 소비자보호에 관한 법률)에 따라 최소 5년간의 주문 이력을 보관하고 조회할 수 있어야 한다. 이 법적 요구사항 때문에 서비스를 단순히 폐기할 수 없고, 지속적으로 운영 가능한 상태를 유지해야 한다.

그러나 이 서비스는 핵심 비즈니스 기능이 변하지 않기 때문에, 기능 개발 리소스가 거의 투입되지 않는다. 반면 보안 패치, 프레임워크 업그레이드, 모니터링 전환 등 기술적 유지보수는 주기적으로 필요하다. 문제는 이런 종류의 서비스가 대개 “비즈니스 변화가 없으니 우선순위가 낮다”는 판단을 반복해서 받기 쉽다는 점이다. 그렇게 미뤄진 유지보수는 어느 순간 Java, Spring Boot, 보안 스캔, 모니터링 전환이 한꺼번에 얽힌 큰 작업으로 돌아온다.

이번 현대화도 처음부터 거대한 재개발 프로젝트로 시작한 것은 아니었다. 운영상 꼭 필요한 변화부터 순서를 나눠 적용했다. 먼저 모니터링 전환으로 관측성 체계를 바꾸고, 그 다음 메이저 런타임과 프레임워크 업그레이드를 수행한 뒤, 마지막으로 후속 패치와 캐시 설정을 안정화하는 순서를 택했다. 레거시 서비스를 다룰 때는 “무엇을 바꾸는가”만큼이나 “어떤 순서로 바꾸는가”가 중요했다.

왜 이 서비스를 계속 현대화해야 했나

이 서비스의 아이러니는 제품 관점에서는 거의 변하지 않지만, 운영 관점에서는 멈출 수 없다는 점이다. 주문 이력 조회 API라는 성격상 새로운 기능 요구는 많지 않았다. 하지만 법적 보관 의무가 있는 이상 서비스를 종료할 수 없었고, 종료할 수 없는 서비스는 결국 계속 패치 가능한 상태를 유지해야 했다.

여기에는 몇 가지 현실적인 압력이 있었다.

  • 보안 취약점이 누적되면 내부 보안 검사에서 빌드와 배포가 차단될 수 있다.
  • 모니터링 체계가 바뀌면 기존 장애 탐지와 대시보드도 같이 이전해야 한다.
  • Java와 Spring Boot가 오래 묵을수록 한번에 건드려야 하는 변경 범위가 커진다.
  • 법적 보관 서비스는 장애가 났을 때 “나중에 고치자”가 잘 통하지 않는다.

결국 BLOHA 현대화는 기능 확장이 아니라, 서비스를 계속 운영 가능한 상태로 되돌려 놓는 작업에 가까웠다.

모니터링 전환: NewRelic → SignalFx

Nike는 모니터링 플랫폼을 NewRelic에서 SignalFx(현재 Splunk Observability)로 전환하는 과정에 있었다. BLOHA에서도 이 전환 작업을 수행했다. 기존 NewRelic Java Agent 중심 자동 계측을 제거하고, OpenTelemetry Java Agent와 Micrometer 기반 계측으로 관측성 구조를 다시 정리해 SignalFx로 전송하도록 설정했다.

이 전환에서 가장 까다로웠던 부분은 기존 NewRelic 대시보드와 알림을 SignalFx에서 동등하게 재구성하는 것이었다. 이전에는 NewRelic Agent가 암묵적으로 잡아주던 메트릭(HTTP 요청 수, 응답 시간, 에러율 등)을, 전환 이후에는 OpenTelemetry Java Agent가 자동으로 수집하는 범위와 Micrometer로 직접 정의해야 하는 범위를 나눠 다시 설계해야 했다.

둘의 차이를 운영 관점에서 요약하면 이 정도였다.

항목NewRelic 시기SignalFx 전환 후
계측 방식NewRelic Java Agent 중심 자동 계측OpenTelemetry Java Agent + Micrometer
메트릭 정의에이전트가 기본 제공서비스가 직접 이름과 태그를 설계
대시보드 이행기존 대시보드에 의존새 플랫폼에 맞게 다시 구성 필요
알림 재구성기존 조건 유지동등 지표를 다시 정의해야 함
운영 체감시작은 빠르지만 내부 정의가 덜 보임초기 작업은 늘지만 메트릭 의도가 더 선명해짐

이 전환은 단순히 벤더를 바꾸는 작업이 아니었다. 자동 계측에 기대던 상태에서, 어떤 지표를 어떤 이름으로 남길지 서비스가 다시 책임지는 쪽으로 이동하는 작업에 가까웠다.

예를 들어 이전에는 에이전트가 암묵적으로 잡아주던 요청 수나 오류 지표를, 이후에는 이런 식으로 직접 수집해야 했다.

java
sample/micrometer-counter.java
private final Counter orderLookupCounter; public OrderHistoryService(MeterRegistry registry) { this.orderLookupCounter = registry.counter("bloha.order.lookup.count"); } public OrderHistory getOrder(String orderId) { orderLookupCounter.increment(); return repository.findByOrderId(orderId); }

프레임워크/런타임 대규모 업그레이드

BLOHA의 가장 큰 현대화 작업이었다. Spring Boot 2.7에서 3.5.6으로의 업그레이드는 메이저 버전 전환이므로 javax → jakarta 패키지 마이그레이션, Spring Security 설정 변경, Hibernate 5 → 6 전환 같은 과제가 함께 발생했다.

동시에 Java 11에서 21로 업그레이드하면서 수십 건의 CVE(Common Vulnerabilities and Exposures)를 해결했다. 이 버전 업그레이드는 단순히 최신 버전을 따라가기 위한 작업이 아니라, 누적된 취약점 이슈를 정리하고 이후 패치 사이클을 정상화하기 위한 대응에 가까웠다. 보안 취약점 해결은 내부 보안 정책상 필수였고, Critical과 High 수준의 CVE가 남아 있으면 빌드와 배포가 차단될 수 있었다.

이번 업그레이드를 한 표로 요약하면 이렇다.

항목이전이후의미
Java1121장기 지원 버전 갱신
Spring Boot2.73.5.6메이저 업그레이드
Servlet / JPA APIjavax.*jakarta.*코드와 의존성 수정 필요
Hibernate5.x6.xORM 동작과 캐시 설정 재점검 필요
모니터링NewRelic Java AgentOpenTelemetry Java Agent + Micrometer + SignalFx관측성 구조 전환

표로 보면 단순한 버전 변경처럼 보이지만, 실제로는 import 경로, 설정 키, 의존 라이브러리 조합이 한꺼번에 바뀌는 작업이었다.

가장 눈에 띄는 변화는 역시 javax에서 jakarta로의 이동이었다.

java
sample/jakarta-migration.java
-import javax.persistence.Entity; -import javax.persistence.Id; +import jakarta.persistence.Entity; +import jakarta.persistence.Id;

혹은 validation 쪽도 비슷했다.

java
sample/validation-migration.java
-import javax.validation.constraints.NotNull; +import jakarta.validation.constraints.NotNull;

이 작업에서 주목할 점은 Hibernate L2(Second Level) Cache를 Caffeine으로 설정한 것이다. 이 서비스가 다루는 주문 이력 데이터는 과거 주문을 마이그레이션해 보관한 데이터라, 저장된 뒤에는 사실상 수정이 없다. 캐시가 꽉 차서 비워지거나(eviction) 명시적으로 만료시키지 않는 이상 갱신이 필요 없었기 때문에 캐시 효율이 매우 좋았다. Caffeine은 높은 히트율과 낮은 메모리 오버헤드를 제공하는 Java 캐시 라이브러리로, EhCache에 비해 성능이 우수하다.

업그레이드 과정에서 어려웠던 것은 단순히 컴파일 오류를 없애는 일이 아니었다. javax import를 jakarta로 바꾸는 작업은 눈에 잘 보이지만, 더 많은 시간은 어디까지를 한 번에 바꿔도 되는지를 판단하는 데 들었다. 주문 상세 조회, 고객센터 조회, 배치성 조회 API처럼 실제로 많이 호출되는 핵심 경로를 우선 회귀 테스트 대상으로 삼고, 나머지는 로그와 메트릭으로 이상 징후를 확인하는 식으로 위험을 나눴다.

메이저 업그레이드에서는 “이전에는 암묵적으로 되던 것”이 많이 사라진다. Spring Boot 2.x 시절의 설정이나 라이브러리 조합이 3.x에서는 더 이상 그대로 동작하지 않았고, Hibernate 6으로 올라가면서 캐시 관련 속성과 동작도 다시 점검해야 했다. 이런 종류의 작업은 코드를 고치는 시간보다, 예전의 가정이 어디서 깨졌는지를 찾아내는 시간이 더 길었다.

후속 안정화 패치 적용

3.5.6 업그레이드 한 달 후, 3.5.8 패치를 적용했다. 이 패치에는 3.5.6~3.5.7에서 발견된 버그 수정과 추가 보안 패치가 포함되어 있다. 메이저 업그레이드와 달리 패치 적용은 비교적 간단하지만, 회귀 테스트(regression test)를 통해 기존 기능이 영향받지 않음을 확인해야 한다.

이렇게 짧은 주기로 패치를 적용하는 것은 BLOHA가 법적 요구사항을 충족하는 서비스이기 때문이다. 보안 취약점이 발견되면 빠르게 패치해야 하고, 패치가 지연되면 감사(audit)에서 지적받을 수 있다.

Hibernate L2 Cache Caffeine 설정

BLOHA의 데이터 접근 패턴은 읽기 위주(read-heavy)다. 이 서비스가 다루는 것은 과거에 마이그레이션된 주문 이력이라, 저장된 뒤 다시 수정되는 일이 없다. 결국 조회만 반복되는 데이터였기 때문에 캐시와의 조합이 좋았다. 이 특성을 활용하여 Hibernate Second Level Cache를 Caffeine으로 설정했고, 엔티티 캐싱과 쿼리 캐싱을 모두 활성화하여 동일한 주문 조회 요청에 대한 DB 접근을 최소화했다.

캐시 설정의 핵심은 TTL(Time To Live)과 최대 크기 결정이다. 주문 데이터는 불변(immutable)이므로 TTL을 비교적 길게(1시간~24시간) 설정할 수 있었고, 최근 조회된 주문 데이터를 우선 캐싱하도록 최대 크기를 설정했다. 캐시 히트율을 모니터링하여 설정값을 튜닝했다.

개념적으로는 이런 설정에 가까웠다.

yaml
sample/application.yml
spring: jpa: properties: hibernate: cache: use_second_level_cache: true use_query_cache: true cache: caffeine: spec: maximumSize=10000,expireAfterWrite=6h

중요했던 것은 “캐시를 넣는다”보다 “이 데이터는 실제로 얼마나 오래 불변이라고 가정할 수 있는가”를 먼저 판단하는 일이었다. 주문 이력 조회처럼 읽기 비중이 높고, eviction이 일어나지 않는 한 갱신이 거의 필요 없는 도메인에서는 캐시가 잘 맞지만, 그렇지 않은 도메인에서는 오히려 정합성 비용이 더 커질 수 있다.

현대화는 버전 업그레이드 목록이 아니라 서비스 수명과 운영 가능성을 다시 확보하는 작업이었다.

기술 선택의 장단점

현대화의 장점은 보안 취약점 대응 속도와 운영 관측 가능성을 크게 높인다는 점이다. 최신 런타임과 모니터링 체계를 적용하면 장애 감지와 원인 분석 시간이 줄고, 이후 패치 사이클도 짧아진다.

단점은 단기적으로 마이그레이션 비용이 크고, 회귀 테스트 범위가 넓어진다는 점이다. 특히 장기 운영 서비스는 기능 변화가 작아도 의존성 변화에 민감하므로, 기술 부채를 한 번에 청산하기보다 단계적으로 나눠 적용하는 전략이 현실적이었다.

현대화 실행 체크포인트

  • 레거시 의존성 교체는 런타임/DB/빌드 체인을 분리해 단계 적용했다.
  • 구버전 API 호환 계층을 두어 운영 기능 중단 없이 전환했다.
  • 회귀 테스트는 주문 조회, CS 조회, 배치 동작의 핵심 경로를 우선 검증했다.

현대화 접근 비교

접근장점한계
일괄 교체(Big Rewrite)단기 구조 정리 효과실패 시 영향 범위 큼
단계적 현대화운영 리스크 분산이행 기간 동안 이중 관리 필요

레거시 서비스를 다룰수록 큰 교체보다 단계적 현대화가 더 현실적이었다. BLOHA처럼 법적 보관 의무가 있고 조회 API가 계속 살아 있어야 하는 서비스는, “한 번에 새로 만들고 갈아끼우자”는 접근보다 기존 운영을 유지한 채 조금씩 기술 기반을 바꾸는 쪽이 실패 비용이 훨씬 작았다.

현대화 이후에 남은 것

이번 작업으로 당장 체감한 변화는 세 가지였다.

  • 취약점 대응이 훨씬 단순해졌다.
  • 관측성 구조가 벤더 의존에서 벗어나 더 명시적으로 정리됐다.
  • 읽기 위주 도메인에 맞는 캐시 전략을 적용할 수 있게 됐다.

물론 이런 작업이 레거시 서비스를 곧바로 최신 아키텍처로 바꿔주지는 않는다. 다만 더 중요한 것은, 다음 패치와 다음 업그레이드를 감당할 수 있는 상태로 돌려놓았다는 점이다. 기능 변화가 거의 없는 서비스일수록 오히려 이 상태가 중요하다. 제품팀의 관심이 적더라도, 법적 책임과 운영 책임은 그대로 남기 때문이다.

이번 현대화를 지나고 나서 더 분명해진 것은 하나였다. 레거시 서비스의 현대화는 화려한 재설계보다, 종료할 수 없는 서비스를 계속 패치 가능한 상태로 유지하는 운영 기술에 더 가깝다.

Last updated on