레거시 시스템 마이그레이션, 어디서부터 시작하나

요약

8년 된 PHP·jQuery 어드민·코드 이해자 부재 — 자주 듣는 인수 케이스입니다. 완전 재작성은 "제2시스템 효과"로 실패하기 쉽습니다. Strangler Fig 패턴으로 기능 단위 점진 교체가 더 안전합니다.

외주 문의 중 상당수가 기존 시스템 교체 요청입니다. "8년 된 PHP 시스템인데 더 이상 유지보수가 안 됩니다", "jQuery로 만든 어드민인데 기능 추가할 때마다 사이드 이펙트가 터집니다", "개발자가 퇴사하고 나서 코드를 이해하는 사람이 아무도 없습니다", "내부 개발팀이 다 나갔는데 시스템 업데이트는 계속 필요합니다", "옛날에 외주로 만든 시스템인데 그때 개발 당시 문제가 많아서 다른 개발사를 찾아야 합니다" — 이런 이야기를 정말 자주 듣습니다.

레거시 마이그레이션은 단순히 기술 스택을 바꾸는 것이 아니라, 운영 중인 서비스를 멈추지 않으면서 내부를 완전히 뜯어고치는 작업이기 때문에 신중한 전략이 필요합니다.

완전 재작성 vs 점진적 마이그레이션

완전 재작성은 유혹적입니다. 처음부터 깔끔하게 다시 만들면 될 것 같지만, 소프트웨어 공학에서 말하는 '제2시스템 효과(Second System Effect)'가 거의 예외 없이 발생합니다. 기존 시스템에 축적된 수많은 예외 처리, 비즈니스 로직, 운영 과정에서 추가된 핫픽스 등을 새 시스템에서 빠뜨리기가 너무나 쉽습니다.

특히 결제·정산처럼 수년에 걸쳐 누적된 예외 케이스가 코드 곳곳에 박혀 있는 도메인에서 이 효과는 더 강하게 나타납니다. 새로 짜고 있는 시스템이 60~70% 진행됐는데도 기존 시스템의 기능 수준에 도달하지 못해, 결국 이미 들어간 비용 일부를 포기하고 점진 전환 전략으로 갈아타는 패턴을 외주 시장에서 흔하게 봅니다. 한 해 분량의 개발 예산이 들어간 뒤에 깨닫는 경우가 많기 때문에, 시작 단계에서의 전략 선택이 비용을 크게 좌우합니다.

점진적 마이그레이션은 느리지만 안전합니다. 스트랭글러 피그 패턴(Strangler Fig Pattern)이 대표적인 접근법입니다. 열대우림에서 교살 무화과나무가 숙주 나무를 서서히 감싸며 대체하는 것처럼, 새로운 기능은 새 시스템에서 구축하고 기존 기능을 하나씩 교체해 나갑니다.

Nginx 같은 리버스 프록시를 두고 URL 경로 단위로 트래픽을 새 시스템과 기존 시스템에 분배합니다. 예를 들어 /api/v2/* 요청은 새 백엔드 서버로, 나머지는 기존 시스템으로 보내는 식입니다. 사용자 입장에서는 변화를 거의 느끼지 못합니다.

가장 어려운 부분: 데이터 마이그레이션

코드는 다시 쓸 수 있지만 데이터는 대체할 수 없습니다. 8년 된 시스템의 데이터베이스에는 스키마 변경 이력이 뒤엉켜 있고, 정규화되지 않은 컬럼, 중복 레코드, NULL과 빈 문자열이 혼용된 필드, 더 이상 사용하지 않는 테이블 등 온갖 문제가 있습니다.

예를 들어 phone 컬럼 한 곳에 010-1234-5678, 01012345678, +82-10-1234-5678 같은 표기가 섞여 들어가 있는 경우는 흔합니다. 정규화 스크립트만으로는 안 되고 어느 시점에 어느 표기가 들어왔는지 운영진과 함께 추적해야 정확한 변환 규칙이 나오는 일도 적지 않습니다.

프로덕트 메이커는 마이그레이션 스크립트를 먼저 작성하고, 스테이징 환경에서 최소 3회 이상 리허설을 진행합니다. 관계형 데이터베이스의 외래 키 제약조건과 CHECK 제약을 적극 활용해 마이그레이션 과정에서 데이터 정합성을 자동 검증합니다.

UI·UX는 그대로 두는 것이 안전합니다

코드와 데이터 못지않게 신중해야 하는 영역이 사용자 화면입니다. 새 스택으로 바꾸는 김에 디자인도 같이 새로 짜자는 유혹이 매번 들어오는데, 이게 가장 흔한 마이그레이션 실패 원인 중 하나입니다.

이 유혹은 외주사 쪽에서만 오는 게 아니라 클라이언트 쪽에서 더 강하게 옵니다. "이왕 비용·시간 들여 시스템을 바꾸는 김에 옛날부터 답답했던 디자인이랑 흐름도 같이 정리해야 하지 않나?" 라는 질문이 자연스럽게 나오고, 한 번 손볼 때 다 손보자는 효율 직관과 묵은 불만을 한 번에 풀고 싶은 심리가 합쳐집니다. 양쪽 입장이 같은 방향으로 끌어당기기 때문에 결정 자체는 어렵지 않게 내려지지만, 그 결정의 비용은 사용자 쪽에서 나옵니다.

기존 사용자는 이미 기존 화면 동선에 익숙해진 상태입니다. 버튼이 평소 위치에서 사라지거나, 한 번에 끝나던 작업이 두세 단계로 늘어나거나, 단축키·즐겨찾기 흐름이 깨지면 사용자 입장에서는 "내가 쓰던 그 서비스가 아니다" 가 됩니다. 특히 매일 같은 화면을 반복해서 다루는 운영 도구, B2B 백오피스, 시니어 사용자 비중이 높은 서비스에서는 사소한 UI 변경 하나가 장기 고객 이탈로 이어집니다.

그래서 기술 마이그레이션은 가능한 한 UI·UX를 그대로 옮기고, 디자인 개편은 마이그레이션이 안정화된 다음에 별도 사이클로 진행하는 것이 안전합니다. 두 변화를 한 번에 가져가면 문제가 생겼을 때 — 사용자가 빠져나가기 시작했을 때 — 원인이 기술인지 디자인인지 분리할 수 없게 됩니다. 마이그레이션 단계에서 사용자가 "어, 좀 빨라졌네" 정도만 느낀다면 가장 잘 된 마이그레이션입니다.

한 가지 예외가 있습니다. 사용자 입장에서 대체 서비스가 사실상 없는 강한 락인이 걸려 있는 환경이라면 이야기가 달라집니다. 사내 운영 시스템, 정부·공공 서비스, 업계 표준이 되어 다른 곳으로 옮기는 비용이 너무 큰 SaaS, 네트워크 효과로 그 서비스 안에 머물 수밖에 없는 플랫폼 같은 경우엔 사용자가 UI 개편을 불편해해도 빠져나갈 곳이 없습니다. 이런 환경이라면 마이그레이션과 디자인 개편을 한 번에 묶어 적응 비용을 한 번만 받아내는 쪽이 오히려 효율적입니다. 두 번에 나누면 적응 피로만 두 배로 누적되기 때문입니다. 다만 이 판단을 내리기 전에 "정말로 사용자가 빠져나갈 수 없는가" 를 정직하게 점검해야 합니다. 락인이 약한데 강하다고 착각하는 게 가장 비싼 실수입니다.

프로덕트 메이커의 실전 접근법

먼저 기존 코드를 철저히 감사합니다. 접근 로그와 데이터베이스 쿼리 로그를 분석해서 실제로 쓰이는 기능과 죽은 코드를 구분하고, 사용 빈도와 비즈니스 영향도를 기준으로 마이그레이션 우선순위를 정합니다. 결제·예약처럼 비즈니스 핵심에 가까운 모듈을 가장 먼저 전환하고, 관리자 페이지·리포트·알림 같은 주변 기능을 순서대로 옮기는 흐름이 일반적입니다.

전환 단계에서는 컨테이너로 새 시스템과 기존 시스템을 동시에 운영하면서 리버스 프록시(Nginx·HAProxy·클라우드 로드밸런서 등)로 트래픽을 URL 경로 단위로 점진적으로 옮깁니다. 새 시스템의 백엔드 언어·프레임워크·데이터베이스는 기존 시스템 구조, 클라이언트 인프라, 운영 인력의 익숙한 기술에 맞춰 선택합니다 — 한 가지 스택을 강요하지 않습니다. 운영 중인 시스템 위에서 진행되기 때문에 단계별로 롤백할 수 있는 토글과 이중 기록(dual write) 구간을 일정 기간 두는 것이 안전합니다.

가장 큰 위험은 기존 기능을 1:1로 완벽하게 복제하려는 욕심입니다. 마이그레이션은 불필요한 기능을 정리하고 단순화할 절호의 기회이기도 합니다. 실제 사용률이 2% 미만인 기능은 과감하게 제거하거나 단순화하는 것이 장기적으로 올바른 판단이고, 기능이 적을수록 유지보수 비용은 줄고 안정성은 높아집니다.

마이그레이션 체크리스트

레거시 마이그레이션을 검토하고 있다면 몇 가지 핵심 질문을 먼저 답해보세요. 현재 시스템의 실제 사용 기능 목록이 정리되어 있는가? 데이터베이스 스키마 문서가 최신 상태인가? 마이그레이션 기간 동안 기존 시스템의 유지보수 인력을 확보할 수 있는가? 마이그레이션 완료 후 기존 시스템을 완전히 종료할 시점이 명확한가? 이 질문에 대한 답이 불확실하다면, 코드를 한 줄이라도 작성하기 전에 현황 분석부터 하는 것이 맞습니다.

프로덕트 메이커는 마이그레이션 프로젝트의 첫 2주를 반드시 현황 분석과 전략 수립에 투자합니다. 이 2주가 이후 수개월의 개발 방향을 결정합니다.

다른 포스팅

레거시 시스템 마이그레이션, 어디서부터 시작하나

외주 문의 중 상당수가 기존 시스템 교체 요청입니다. "8년 된 PHP 시스템인데 더 이상 유지보수가 안 됩니다", "jQuery로 만든 어드민인데 기능 추가할 때마다 사이드 이펙트가 터집니다", "개발자가 퇴사하고 나서 코드를 이해하는 사람이 아무도 없습니다", "내부 개발팀이 다 나갔는데 시스템 업데이트는 계속 필요합니다", "옛날에 외주로 만든 시스템인데 그때 개발 당시 문제가 많아서 다른 개발사를 찾아야 합니다" — 이런 이야기를 정말 자주 듣습니다.

레거시 마이그레이션은 단순히 기술 스택을 바꾸는 것이 아니라, 운영 중인 서비스를 멈추지 않으면서 내부를 완전히 뜯어고치는 작업이기 때문에 신중한 전략이 필요합니다.

완전 재작성 vs 점진적 마이그레이션

완전 재작성은 유혹적입니다. 처음부터 깔끔하게 다시 만들면 될 것 같지만, 소프트웨어 공학에서 말하는 '제2시스템 효과(Second System Effect)'가 거의 예외 없이 발생합니다. 기존 시스템에 축적된 수많은 예외 처리, 비즈니스 로직, 운영 과정에서 추가된 핫픽스 등을 새 시스템에서 빠뜨리기가 너무나 쉽습니다.

특히 결제·정산처럼 수년에 걸쳐 누적된 예외 케이스가 코드 곳곳에 박혀 있는 도메인에서 이 효과는 더 강하게 나타납니다. 새로 짜고 있는 시스템이 60~70% 진행됐는데도 기존 시스템의 기능 수준에 도달하지 못해, 결국 이미 들어간 비용 일부를 포기하고 점진 전환 전략으로 갈아타는 패턴을 외주 시장에서 흔하게 봅니다. 한 해 분량의 개발 예산이 들어간 뒤에 깨닫는 경우가 많기 때문에, 시작 단계에서의 전략 선택이 비용을 크게 좌우합니다.

점진적 마이그레이션은 느리지만 안전합니다. 스트랭글러 피그 패턴(Strangler Fig Pattern)이 대표적인 접근법입니다. 열대우림에서 교살 무화과나무가 숙주 나무를 서서히 감싸며 대체하는 것처럼, 새로운 기능은 새 시스템에서 구축하고 기존 기능을 하나씩 교체해 나갑니다.

Nginx 같은 리버스 프록시를 두고 URL 경로 단위로 트래픽을 새 시스템과 기존 시스템에 분배합니다. 예를 들어 /api/v2/* 요청은 새 백엔드 서버로, 나머지는 기존 시스템으로 보내는 식입니다. 사용자 입장에서는 변화를 거의 느끼지 못합니다.

가장 어려운 부분: 데이터 마이그레이션

코드는 다시 쓸 수 있지만 데이터는 대체할 수 없습니다. 8년 된 시스템의 데이터베이스에는 스키마 변경 이력이 뒤엉켜 있고, 정규화되지 않은 컬럼, 중복 레코드, NULL과 빈 문자열이 혼용된 필드, 더 이상 사용하지 않는 테이블 등 온갖 문제가 있습니다.

예를 들어 phone 컬럼 한 곳에 010-1234-5678, 01012345678, +82-10-1234-5678 같은 표기가 섞여 들어가 있는 경우는 흔합니다. 정규화 스크립트만으로는 안 되고 어느 시점에 어느 표기가 들어왔는지 운영진과 함께 추적해야 정확한 변환 규칙이 나오는 일도 적지 않습니다.

프로덕트 메이커는 마이그레이션 스크립트를 먼저 작성하고, 스테이징 환경에서 최소 3회 이상 리허설을 진행합니다. 관계형 데이터베이스의 외래 키 제약조건과 CHECK 제약을 적극 활용해 마이그레이션 과정에서 데이터 정합성을 자동 검증합니다.

UI·UX는 그대로 두는 것이 안전합니다

코드와 데이터 못지않게 신중해야 하는 영역이 사용자 화면입니다. 새 스택으로 바꾸는 김에 디자인도 같이 새로 짜자는 유혹이 매번 들어오는데, 이게 가장 흔한 마이그레이션 실패 원인 중 하나입니다.

이 유혹은 외주사 쪽에서만 오는 게 아니라 클라이언트 쪽에서 더 강하게 옵니다. "이왕 비용·시간 들여 시스템을 바꾸는 김에 옛날부터 답답했던 디자인이랑 흐름도 같이 정리해야 하지 않나?" 라는 질문이 자연스럽게 나오고, 한 번 손볼 때 다 손보자는 효율 직관과 묵은 불만을 한 번에 풀고 싶은 심리가 합쳐집니다. 양쪽 입장이 같은 방향으로 끌어당기기 때문에 결정 자체는 어렵지 않게 내려지지만, 그 결정의 비용은 사용자 쪽에서 나옵니다.

기존 사용자는 이미 기존 화면 동선에 익숙해진 상태입니다. 버튼이 평소 위치에서 사라지거나, 한 번에 끝나던 작업이 두세 단계로 늘어나거나, 단축키·즐겨찾기 흐름이 깨지면 사용자 입장에서는 "내가 쓰던 그 서비스가 아니다" 가 됩니다. 특히 매일 같은 화면을 반복해서 다루는 운영 도구, B2B 백오피스, 시니어 사용자 비중이 높은 서비스에서는 사소한 UI 변경 하나가 장기 고객 이탈로 이어집니다.

그래서 기술 마이그레이션은 가능한 한 UI·UX를 그대로 옮기고, 디자인 개편은 마이그레이션이 안정화된 다음에 별도 사이클로 진행하는 것이 안전합니다. 두 변화를 한 번에 가져가면 문제가 생겼을 때 — 사용자가 빠져나가기 시작했을 때 — 원인이 기술인지 디자인인지 분리할 수 없게 됩니다. 마이그레이션 단계에서 사용자가 "어, 좀 빨라졌네" 정도만 느낀다면 가장 잘 된 마이그레이션입니다.

한 가지 예외가 있습니다. 사용자 입장에서 대체 서비스가 사실상 없는 강한 락인이 걸려 있는 환경이라면 이야기가 달라집니다. 사내 운영 시스템, 정부·공공 서비스, 업계 표준이 되어 다른 곳으로 옮기는 비용이 너무 큰 SaaS, 네트워크 효과로 그 서비스 안에 머물 수밖에 없는 플랫폼 같은 경우엔 사용자가 UI 개편을 불편해해도 빠져나갈 곳이 없습니다. 이런 환경이라면 마이그레이션과 디자인 개편을 한 번에 묶어 적응 비용을 한 번만 받아내는 쪽이 오히려 효율적입니다. 두 번에 나누면 적응 피로만 두 배로 누적되기 때문입니다. 다만 이 판단을 내리기 전에 "정말로 사용자가 빠져나갈 수 없는가" 를 정직하게 점검해야 합니다. 락인이 약한데 강하다고 착각하는 게 가장 비싼 실수입니다.

프로덕트 메이커의 실전 접근법

먼저 기존 코드를 철저히 감사합니다. 접근 로그와 데이터베이스 쿼리 로그를 분석해서 실제로 쓰이는 기능과 죽은 코드를 구분하고, 사용 빈도와 비즈니스 영향도를 기준으로 마이그레이션 우선순위를 정합니다. 결제·예약처럼 비즈니스 핵심에 가까운 모듈을 가장 먼저 전환하고, 관리자 페이지·리포트·알림 같은 주변 기능을 순서대로 옮기는 흐름이 일반적입니다.

전환 단계에서는 컨테이너로 새 시스템과 기존 시스템을 동시에 운영하면서 리버스 프록시(Nginx·HAProxy·클라우드 로드밸런서 등)로 트래픽을 URL 경로 단위로 점진적으로 옮깁니다. 새 시스템의 백엔드 언어·프레임워크·데이터베이스는 기존 시스템 구조, 클라이언트 인프라, 운영 인력의 익숙한 기술에 맞춰 선택합니다 — 한 가지 스택을 강요하지 않습니다. 운영 중인 시스템 위에서 진행되기 때문에 단계별로 롤백할 수 있는 토글과 이중 기록(dual write) 구간을 일정 기간 두는 것이 안전합니다.

가장 큰 위험은 기존 기능을 1:1로 완벽하게 복제하려는 욕심입니다. 마이그레이션은 불필요한 기능을 정리하고 단순화할 절호의 기회이기도 합니다. 실제 사용률이 2% 미만인 기능은 과감하게 제거하거나 단순화하는 것이 장기적으로 올바른 판단이고, 기능이 적을수록 유지보수 비용은 줄고 안정성은 높아집니다.

마이그레이션 체크리스트

레거시 마이그레이션을 검토하고 있다면 몇 가지 핵심 질문을 먼저 답해보세요. 현재 시스템의 실제 사용 기능 목록이 정리되어 있는가? 데이터베이스 스키마 문서가 최신 상태인가? 마이그레이션 기간 동안 기존 시스템의 유지보수 인력을 확보할 수 있는가? 마이그레이션 완료 후 기존 시스템을 완전히 종료할 시점이 명확한가? 이 질문에 대한 답이 불확실하다면, 코드를 한 줄이라도 작성하기 전에 현황 분석부터 하는 것이 맞습니다.

프로덕트 메이커는 마이그레이션 프로젝트의 첫 2주를 반드시 현황 분석과 전략 수립에 투자합니다. 이 2주가 이후 수개월의 개발 방향을 결정합니다.

다른 포스팅