Tan Kim

legacy-refactor

레거시 코드 다운타임 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는 필수다: 롤백 없이도 기능을 켜고 끌 수 있는 구조가 배포 리스크를 크게 줄인다
  • 모니터링 없이 전환하지 말라: 전환 전후 에러율, 응답시간을 실시간으로 볼 수 있어야 한다