레거시 코드 다운타임 0 마이그레이션
운영 중인 서비스의 레거시 코드베이스를 전수 업데이트(일괄 배포) 없이 다운타임 0으로 구조적으로 개선한 경험이다.
배경
오너클랜 재직 중 수년간 누적된 레거시 PHP 코드베이스를 관리했다. 기능 추가는 쉽지 않고, 변경 시 예기치 않은 부작용이 발생하는 전형적인 레거시 문제를 안고 있었다. 하지만 24시간 운영 중인 이커머스 서비스였기 때문에 서비스를 멈추고 대규모 리팩토링을 진행하는 방식은 불가능했다.
핵심 제약
- 다운타임 없음: 운영 서비스는 중단 불가
- 일괄 배포 없음: 전수 업데이트 방식의 대규모 배포 불가
- 데이터 무결성: 마이그레이션 중에도 데이터 정합성 유지
- 점진적 진행: 비즈니스 기능 개발과 병행
전략
1. Strangler Fig 패턴
새로운 구조의 코드를 기존 코드 옆에 점진적으로 심어나가다가, 충분히 안정화되면 기존 코드를 제거하는 방식이다.
- 기존 코드를 수정하지 않고 새 코드를 추가
- 트래픽을 점진적으로 새 코드로 이동
- 기존 코드가 완전히 대체되면 제거
2. 브랜치 by 추상화
공통 인터페이스(추상화 레이어)를 두고, 구현체를 구버전과 신버전 두 가지로 만들어 전환 시점을 제어했다.
Interface (공통)
├── LegacyImpl (기존 코드)
└── NewImpl (신규 코드)
Feature flag로 어느 구현체를 사용할지 런타임에 전환할 수 있다.
3. 데이터 듀얼 라이트
데이터 스키마가 바뀌는 경우, 일정 기간 구버전과 신버전 스키마 모두에 동시에 쓰는 구간을 운영했다. 이를 통해 롤백 시에도 데이터 손실이 없도록 했다.
수행 과정
Phase 1 — 분석 및 경계 설정
리팩토링 범위를 정의하고, 변경이 영향을 미치는 범위(경계)를 문서화했다.
Phase 2 — 추상화 레이어 도입
기존 코드를 즉시 변경하지 않고, 인터페이스를 정의하고 기존 코드를 해당 인터페이스를 구현하는 형태로 감쌌다.
Phase 3 — 신규 구현체 개발 및 검증
신규 구현체를 개발하고 staging 환경에서 충분히 검증한 뒤, feature flag를 통해 일부 트래픽에만 적용하여 실 환경에서 검증했다.
Phase 4 — 트래픽 전환 및 기존 코드 제거
신규 구현체가 안정적으로 동작함을 확인한 후 트래픽 전환을 완료하고, 기존 코드를 제거했다.
핵심 교훈
- 한 번에 크게 바꾸려 하지 말 것: 작은 단위로 나눠 각 단계를 검증하며 진행
- 기존 동작을 먼저 명세화하라: 무엇을 보존해야 하는지 알아야 안전하게 변경할 수 있다
- Feature flag는 필수다: 롤백 없이도 기능을 켜고 끌 수 있는 구조가 배포 리스크를 크게 줄인다
- 모니터링 없이 전환하지 말라: 전환 전후 에러율, 응답시간을 실시간으로 볼 수 있어야 한다