시작하기 전에: 왜 이 글을 쓰는가

우리 팀은 2023년 초에 "이 시스템 더 이상 못 버틴다"라는 결론에 도달했다. 2016년에 Spring Boot 1.x로 만들어진 모놀리스 시스템이 7년째 돌아가고 있었고, 빌드 한 번에 23분, 배포 후 롤백까지 45분이 걸리는 상황이었다. 금요일 오후에는 아무도 배포 버튼을 누르려 하지 않았다.

나는 이 전환 프로젝트를 처음부터 끝까지 리드했고, 솔직히 말하면 중간에 "그냥 모놀리스 리팩토링하는 게 나았나" 싶은 순간이 최소 열 번은 있었다. 이 글은 그 3년간의 기록이다. 성공 사례만 예쁘게 포장한 게 아니라, 우리가 실제로 삽질한 부분까지 다 담았다.

전환 전 상황: 모놀리스의 고통

당시 시스템 상황을 숫자로 정리하면 이렇다:

가장 힘들었던 건 "주문 모듈 수정했더니 정산 모듈이 터지는" 상황이 반복된 것이다. 코드 간 의존성이 스파게티처럼 얽혀 있어서, 신입 개발자가 기능 하나 추가하려면 최소 2주는 코드를 읽어야 했다.

Before & After: 아키텍처 변화

BEFORE: 모놀리스 monolith-app.jar (2.1GB) 주문 모듈 결제 모듈 회원 모듈 정산 모듈 배송 모듈 알림 모듈 PostgreSQL (312 tables) 3년의 여정 AFTER: 마이크로서비스 API Gateway (Kong) 주문 서비스 PostgreSQL 결제 서비스 PostgreSQL 회원 서비스 MongoDB 정산 서비스 PostgreSQL 배송 서비스 PostgreSQL 알림 서비스 Redis Apache Kafka (이벤트 버스) Prometheus+Grafana Jaeger (Tracing) Kubernetes Cluster (EKS)

왜 전환을 결심했나

사실 "MSA 하자"라고 처음 제안한 건 나였는데, 그 계기는 2022년 블랙 프라이데이 장애였다. 트래픽이 평소의 8배로 치솟았고, 알림 모듈에서 메모리 릭이 발생해서 JVM 전체가 뻗었다. 주문, 결제, 배송 모듈은 아무 문제 없었는데 알림 하나 때문에 전체 서비스가 40분간 다운됐다.

그날 새벽 3시에 장애 대응하면서 팀장님한테 슬랙으로 보낸 메시지가 아직도 기억난다: "알림 모듈 하나 때문에 매출 2억이 날아갔습니다. 서비스 분리 안 하면 이런 일 계속 반복됩니다." 다음 주 월요일에 전환 프로젝트가 승인됐다.

Strangler Fig 패턴으로 점진적 전환

빅뱅 마이그레이션은 처음부터 배제했다. 서비스를 멈출 수 없었고, 리스크가 너무 컸다. 대신 Strangler Fig 패턴을 선택했다. 무화과나무가 기존 나무를 감싸며 자라다가 결국 대체하듯이, 새로운 마이크로서비스가 기존 모놀리스를 점차 대체하는 방식이다.

핵심 아이디어는 간단했다: API Gateway를 앞에 두고, 새로 만든 서비스로 트래픽을 점진적으로 라우팅한다. 기존 모놀리스는 그대로 돌아가게 두면서, 하나씩 기능을 떼어내는 것이다.

단계별 마이그레이션 타임라인

1 Phase 1 2023 Q1~Q2 기반 구축 • API Gateway 도입 • K8s 클러스터 셋업 • CI/CD 파이프라인 • 모니터링 스택 • 서비스 메시 검토 팀원: 3명 비용: 약 1.2억 ⚠ Istio 도입 실패 2 Phase 2 2023 Q3~Q4 첫 서비스 분리 • 알림 서비스 분리 • 회원 서비스 분리 • Kafka 이벤트 버스 • 카나리 배포 적용 • 분산 로깅 구축 팀원: 5명 비용: 약 0.8억 ⚠ 데이터 정합성 이슈 3 Phase 3 2024 Q1~Q3 핵심 서비스 전환 • 주문 서비스 분리 • 결제 서비스 분리 • DB 분리 (per-service) • SAGA 패턴 도입 • 성능 최적화 팀원: 8명 비용: 약 2.5억 ⚠ SAGA 보상 트랜잭션 버그 4 Phase 4 2024 Q4~2025 Q2 완성 및 안정화 • 나머지 서비스 전환 • 모놀리스 퇴역 • 분산 트레이싱 • 자동 스케일링 • SRE 프랙티스 도입 팀원: 12명 비용: 약 1.8억 ✓ 모놀리스 완전 제거

Phase 1: 기반 구축 - 가장 과소평가했던 단계

처음엔 "인프라 셋업이야 한 달이면 되겠지"라고 생각했다. 완전히 틀렸다. 6개월이 걸렸다.

Kubernetes 클러스터를 AWS EKS로 올리는 것 자체는 어렵지 않았다. 문제는 그 위에 올라가는 것들이었다. CI/CD 파이프라인(GitHub Actions + ArgoCD), 모니터링 스택(Prometheus + Grafana + Loki), 시크릿 관리(AWS Secrets Manager), 로그 수집... 하나하나 셋업하고 테스트하는 데 예상보다 3배는 더 걸렸다.

가장 큰 실패는 Istio 도입 시도였다. 서비스 메시가 좋다는 글을 여기저기서 읽고 바로 도입하려 했는데, 학습 곡선이 너무 가팔랐고 리소스 오버헤드도 심했다. 사이드카 프록시 때문에 Pod당 메모리가 150MB씩 더 먹었다. 결국 Istio는 포기하고 Spring Cloud Gateway + Resilience4j 조합으로 갈아탔다. 2개월을 날렸지만, 이때 "유행하는 기술 무작정 따라가지 말자"는 교훈을 얻었다.

Phase 2: 첫 서비스 분리 - 쉬운 것부터

첫 분리 대상으로 알림 서비스를 골랐다. 이유는 단순했다:

알림 서비스 분리는 비교적 순조로웠다. Kafka를 도입해서 이벤트 기반으로 바꿨고, 기존 모놀리스에서는 Kafka 프로듀서만 추가하면 됐다. 2주 만에 끝났다.

그런데 회원 서비스 분리에서 첫 번째 큰 벽을 만났다. 데이터 정합성 문제였다. 모놀리스에서는 단일 트랜잭션으로 처리되던 "회원 가입 + 포인트 적립 + 웰컴 쿠폰 발급"이 서비스가 나뉘면서 부분 실패가 발생했다. 회원은 만들어졌는데 포인트가 안 들어간다거나, 쿠폰이 안 발급되는 경우가 생겼다.

이때 Eventual Consistency 개념을 팀에 교육하는 데 꽤 시간이 걸렸다. "결과적 일관성"이라는 게 말은 쉬운데, 실제로 "회원 포인트가 5분 후에 반영됩니다"를 고객에게 설명하려니 CS팀에서 항의가 왔다. 결국 포인트 적립만은 동기 처리로 남겨두고, 나머지는 비동기로 처리하는 타협안을 찾았다.

Conway's Law: 팀 구조가 아키텍처를 결정한다

이건 정말 뼈저리게 느꼈다. 처음에는 기존 팀 구조(프론트엔드팀, 백엔드팀, DBA팀) 그대로 MSA를 진행하려 했는데, 완전히 실패했다. 주문 서비스 하나 수정하는데 백엔드팀이 API를 고치고, 프론트엔드팀이 UI를 바꾸고, DBA팀이 스키마를 변경하고... 커뮤니케이션 비용이 어마어마했다.

결국 2024년 초에 도메인 기반 팀으로 재편성했다:

팀명담당 서비스인원구성
주문팀주문 서비스, 장바구니4명BE 2 + FE 1 + QA 1
결제팀결제 서비스, 정산4명BE 2 + FE 1 + QA 1
회원팀회원, 인증, 포인트3명BE 2 + FE 1
플랫폼팀인프라, 공통 라이브러리3명SRE 2 + BE 1

팀 구조를 바꾸고 나서 배포 속도가 눈에 띄게 빨라졌다. "다른 팀 PR 리뷰 대기" 같은 병목이 사라졌기 때문이다.

DB 분리: 가장 어려웠던 부분

솔직히 말하면, DB 분리가 이 프로젝트에서 가장 고통스러운 부분이었다. 312개 테이블이 서로 JOIN으로 촘촘히 엮여 있었다. orders 테이블이 users, products, payments, shipping 테이블과 모두 FK로 연결되어 있었다.

우리가 적용한 DB 분리 전략은 이랬다:

  1. 데이터 복제 기간 운영: Change Data Capture(Debezium)로 모놀리스 DB의 변경을 실시간으로 새 서비스 DB에 동기화
  2. 이중 쓰기 기간: 새 서비스와 모놀리스 둘 다에 쓰면서 데이터 검증
  3. 읽기 전환: 읽기 트래픽을 먼저 새 서비스로 전환 (shadow read로 결과 비교)
  4. 쓰기 전환: 문제 없으면 쓰기도 전환
  5. 모놀리스 DB 참조 제거: FK 제거, 필요한 데이터는 API 호출이나 이벤트로 대체

이 과정에서 Debezium 커넥터가 하루에 한 번씩 죽는 문제를 3주간 디버깅한 적도 있다. 원인은 단일 트랜잭션에서 LOB 데이터를 대량으로 변경하는 배치가 WAL 세그먼트를 폭발시킨 것이었다. max_wal_size를 늘리고 배치를 청크로 나누는 것으로 해결했지만, 그 3주는 정말 길었다.

전환 결과: 숫자로 보는 변화

성능 지표 비교: Before vs After Before (모놀리스) After (MSA) 평균 응답시간 빌드 시간 배포 주기 MTTR 850ms 120ms 23분 3분 14일/회 매일 135분 12분 86% 개선 87% 단축 14x 향상 91% 단축

실패에서 배운 교훈 Top 5

성공 사례만 나열하면 거짓말이다. 우리가 겪은 주요 실패들을 정리해본다.

1. "마이크로서비스 = 작은 서비스"라는 착각

처음에 서비스를 너무 잘게 나눴다. 상품 서비스, 카테고리 서비스, 리뷰 서비스, 검색 서비스... 서비스 간 호출이 폭발적으로 늘면서 레이턴시가 모놀리스보다 더 나빠졌다. 한 번의 상품 상세 페이지 조회에 내부 API 호출이 12번 발생하는 지경이었다. 결국 관련 서비스를 다시 합쳐서 "상품 도메인 서비스"로 만들었다. DDD의 Bounded Context를 제대로 정의하지 않은 대가였다.

2. 분산 트랜잭션의 복잡성 과소평가

SAGA 패턴의 보상 트랜잭션에서 버그가 터졌을 때가 가장 무서웠다. 주문 취소 시 결제 환불은 됐는데 재고가 안 돌아오는 경우가 발생했다. 프로덕션에서 3일간 약 200건의 재고 불일치가 생겼고, 수동으로 복구해야 했다. 이후 SAGA 오케스트레이터에 감사 로그와 수동 보상 UI를 추가했다.

3. 공유 라이브러리의 함정

서비스 간 공통 로직을 shared-lib으로 뽑았는데, 이게 결국 "분산 모놀리스"를 만들었다. shared-lib 버전 업데이트하면 모든 서비스를 재배포해야 했다. 현재는 공유 라이브러리를 최소화하고, 코드 중복을 일부 허용하는 방향으로 갔다.

4. 테스트 전략 부재

마이크로서비스 환경에서의 통합 테스트를 어떻게 할지 계획이 없었다. 개별 서비스 단위 테스트는 통과하는데 서비스 간 연동에서 문제가 터지는 일이 빈번했다. Contract Testing(Pact)을 도입하고 나서야 안정화됐다.

5. 운영 복잡도 폭증

모놀리스 1개 운영하던 게 서비스 8개 + Kafka + Redis + 각 서비스별 DB 운영으로 바뀌었다. 처음 6개월은 운영 부담이 3배 이상 늘었다. GitOps와 자동화에 투자하고 나서야 관리할 만해졌다.

비용 변화: 솔직한 이야기

MSA 전환의 비용은 아무도 솔직하게 말 안 하는 부분인데, 나는 공유하려 한다.

항목Before (월)After (월)비고
AWS 인프라450만원680만원+51% (K8s, Kafka, 개별DB 등)
인건비 (운영)1,200만원800만원-33% (자동화 효과)
장애 손실평균 300만원평균 50만원-83% (격리 효과)
개발 속도기능당 3주기능당 1주3x 향상

인프라 비용은 확실히 올랐다. 하지만 운영 비용 절감과 장애 감소, 개발 속도 향상을 종합하면 ROI는 충분히 나왔다. 다만 이 ROI가 눈에 보이기까지 전환 시작 후 18개월은 걸렸다는 점은 기억해야 한다.

지금 다시 한다면 다르게 할 것들

  1. 처음부터 이벤트 스토밍을 했을 것이다. DDD 기반으로 Bounded Context를 먼저 정의하고 서비스를 나눴어야 했다. 우리는 기존 모듈 구조를 그대로 서비스로 나눠서 나중에 합치는 작업을 해야 했다.
  2. 플랫폼 팀을 먼저 만들었을 것이다. Phase 1에서 인프라 구축에 고생한 이유가 전담 팀이 없었기 때문이다.
  3. 모놀리스 내부 모듈화를 먼저 했을 것이다. 서비스 분리 전에 모놀리스 내에서 패키지 의존성을 정리했어야 했다. 스파게티 코드를 그대로 분리하면 분산 스파게티가 된다.
  4. Observability를 Day 1부터 구축했을 것이다. 분산 시스템에서 문제를 추적하려면 트레이싱, 메트릭, 로깅이 필수인데, 우리는 Phase 3에서야 본격적으로 도입했다.

마무리: 전환할 만한 가치가 있었나

솔직히? 그렇다. 하지만 조건부로 그렇다.

우리 시스템은 7년 된 레거시에 빠르게 성장하는 트래픽, 다양한 도메인 요구사항이 있었기에 MSA가 맞았다. 단순한 서비스라면 잘 설계된 모놀리스가 훨씬 낫다. "MSA를 하면 멋있으니까"가 아니라 "이 문제를 해결하려면 MSA가 필요한가?"를 먼저 물어야 한다.

3년간의 여정에서 얻은 가장 큰 교훈은 이것이다: 기술 전환의 70%는 기술이 아니라 사람과 프로세스의 문제다. 팀 구조를 바꾸고, 팀원들의 마인드셋을 바꾸고, 조직의 의사결정 방식을 바꾸는 것이 코드를 바꾸는 것보다 훨씬 어렵고 중요했다.

혹시 레거시 시스템 전환을 고민하고 있다면, 이 글이 조금이나마 현실적인 참고가 되었으면 좋겠다. 궁금한 점이 있으면 언제든 연락 주시길.

← 목록으로 다음 글 →
Jaeseong
Jaeseong

10년차 풀스택 개발자. Spring Boot, Flutter, AI 등 실무 경험을 기록합니다.

GitHub →

💬 댓글