본문 바로가기
카테고리 없음

레거시 DB 마이그레이션 (이중 쓰기, 무중단 전환, 정합성 검증)

by 테크 마스터1 2026. 2. 9.

레거시 DB 마이그레이션
레거시 DB 마이그레이션

 

10년 이상 운영된 스마트스토어 회원 파트의 Oracle DBMS는 여러 부서가 공동으로 사용하면서 리소스 경합이 심화되었고, 이는 서비스 성능 불안정으로 이어졌습니다. Oracle 인프라 확장은 막대한 라이선스 비용 증가를 의미했기 때문에, 오픈소스인 MySQL로의 전환이 결정되었습니다. 이 글에서는 서비스 중단 없이 Oracle 공동 환경에서 MySQL로 전환하며 채택한 이중 쓰기 전략과 기술적 해결 과정, 그리고 실무자 관점에서의 비평과 보완점을 함께 살펴봅니다.

이중 쓰기 전략과 무중단 전환 설계

스마트스토어 회원 파트의 모듈은 타 부서 시스템과 광범위하게 연계되어 있어 서비스 중단이 불가능했습니다. 따라서 무중단 배포와 신속한 롤백 능력 확보가 필수였습니다. 이를 위해 선택한 전략이 바로 이중 쓰기(dual write) 기법입니다. 이중 쓰기란 모든 쓰기 트랜잭션을 기존 데이터베이스와 새 데이터베이스에 동시에 반영하는 기법으로, 두 DB가 완벽하게 동기화된 상태를 유지하여 데이터 손실이나 서비스 중단 없이 안전하게 신규 환경으로 전환할 수 있는 안전장치가 됩니다.

전환 과정은 다음과 같이 진행되었습니다. 전환 전 단계에서는 구버전 애플리케이션이 모든 Read/Write 트래픽을 Oracle에서 처리하되, CUD(Create, Update, Delete) 작업 시에만 백그라운드에서 MySQL에도 이중 쓰기를 수행했습니다. 데이터 마이그레이션 단계에서는 신버전 애플리케이션 배포 전에 Oracle의 전체 데이터를 MySQL로 마이그레이션해 정합성을 맞췄습니다. 전환 후 단계에서는 신버전 애플리케이션이 모든 Read/Write 트래픽을 MySQL에서 처리하며, CUD 작업 시 백그라운드에서 Oracle에도 이중 쓰기를 수행했습니다. 이 구조를 통해 데이터 정합성을 유지하면서 무중단 배포가 가능해지며, Oracle 방향으로 이중 쓰기가 지속되므로 롤백이 필요한 경우에도 별도의 데이터 복구 과정 없이 즉시 롤백할 수 있습니다.

전환 단계 Read 처리 Write 처리 이중 쓰기 방향
전환 전 Oracle Oracle Oracle → MySQL
전환 후 MySQL MySQL MySQL → Oracle

JPA 이중 쓰기 구현에서는 datasource-proxy 라이브러리를 활용해 Oracle에서 수행되는 쿼리를 가져온 뒤 별도의 MySQL DataSource로 해당 쿼리를 실행하도록 Proxy DataSource를 구성했습니다. LocalContainerEntityManagerFactoryBean 빈 등록 시점에 Proxy DataSource 구현체를 설정하여, EntityManager에서 Oracle DB로 쿼리가 flush될 때 MySQL DB에도 쿼리가 수행되도록 했습니다. 트랜잭션 처리에서는 TransactionSynchronizationManager를 활용해 트랜잭션 도중 수행되는 쿼리를 모아두었다가 커밋 후 한꺼번에 MySQL에서 수행하는 방식을 채택했습니다. 이를 통해 MySQL 쿼리 실패가 Oracle 트랜잭션에 영향을 주지 않도록 하면서도, 주기적인 마이그레이션과 검증을 반복하며 불일치 원인을 제거해 나갔습니다.

실무자 관점에서 보면, 이 전략은 레거시 현실을 잘 반영한 현실적인 선택이었습니다. 하지만 이중 쓰기는 본질적으로 일관성(consistency)과 운영 복잡도를 크게 올리는 전략이기 때문에, CDC(Change Data Capture)나 논리복제 같은 대안과의 비교 근거가 더 명확히 제시되었다면 더욱 설득력이 높았을 것입니다. 또한 커밋 후 MySQL 실행이 실패했을 때의 재처리 큐, 데드레터, 멱등성 보장, 실패율 허용치 같은 운영 안전장치에 대한 상세한 설명이 추가되면 독자가 실제로 적용할 때 더욱 유용할 것입니다.

무중단 성능 검증과 Read 트래픽 복제

새로운 MySQL 데이터베이스로의 전환을 추진할 때, 기존 Oracle 환경에서 안정적으로 처리되던 대규모 트랜잭션과 복잡한 쿼리가 MySQL 환경에서도 동일한 성능을 보장하는지 검증하는 것이 핵심 과제였습니다. Oracle과 MySQL 간의 쿼리 실행 계획 및 최적화 엔진 차이로 인해, 특정 고부하 쿼리가 신규 MySQL의 CPU, 메모리, I/O 등 핵심 DB 자원에 예상치 못한 과부하를 초래해 서비스 안정성을 저해할 위험이 있었습니다.

HTTP 트래픽 복사나 Kafka 토픽 복제 같은 방식은 비즈니스 로직 호출 시 타 부서 시스템에 중복 호출 부하를 야기하거나, 예상치 못한 부작용으로 운영에 영향을 줄 위험이 매우 높았습니다. 따라서 타 부서와 연관된 시스템에 영향을 주지 않고 성능 검증이라는 목표를 달성하기 위해, 이미 수행 중인 이중 쓰기 로직을 유지하면서 오직 내부 서비스 로직에서 발생하는 Read 트래픽만 신규 MySQL DB로 복제해 호출하는 전략을 채택했습니다.

MyBatis나 JPA 같은 영속성 프레임워크의 특성상, 대부분의 Read 메서드는 매개변수가 쿼리로 변환되기 전에는 원시 타입이나 직렬화가 가능한 객체를 매개변수로 사용합니다. 이 특성을 활용해 기존 Oracle로 향하는 Repository 계층의 Read 메서드 호출이 발생할 때 해당 메서드의 실행 시간과 함께 이름, 매개변수 값을 캡처하고 JSON으로 직렬화했습니다. 직렬화된 데이터를 Kafka로 전송하고, 메시지를 수신한 별도의 성능 측정 Consumer 모듈은 수신된 데이터를 역직렬화해 원본 메서드 이름과 매개변수 리스트를 복원했습니다. 이후 Java Reflection API를 사용해 신규 MySQL을 바라보는 Repository 인터페이스의 동일한 메서드를 찾아 매개변수를 주입하고 호출함으로써, 마치 실제 서비스가 호출한 것처럼 읽기 작업을 MySQL에서 수행할 수 있었습니다.

각 토픽 메시지마다 실행 시간이 있으므로, 이를 시각화해 각 쿼리의 성능도 확인할 수 있었습니다. 이를 통해 성능이 좋지 않은 쿼리를 수정하고 인덱스 추가 등을 수행해 안정적인 성능을 확보할 수 있었습니다. MySQL의 Index Merge Optimization 문제를 해결하기 위해 OR 조건을 UNION으로 변경하거나, Spring Batch의 JdbcPagingItemReader 페이징 쿼리를 커스텀 QueryProvider로 최적화하는 등의 작업도 진행했습니다.

이 접근법은 실무적으로 매우 영리한 방법이었습니다. 하지만 성능 개선 결과가 정량 지표(p50/p95/p99, QPS, CPU/IO, 주요 쿼리 Top N 전후 비교)로 더 명확히 제시되었다면 독자 설득력이 크게 올라갔을 것입니다. 또한 MySQL Index Merge나 OR→UNION 대응은 좋은 팁이지만, 이런 변경이 늘어나면 쿼리 가독성과 유지보수성이 떨어질 수 있어 대체 인덱스 설계나 힌트/옵티마이저 설정 검토까지 함께 비교해주면 균형이 더 좋았을 것입니다.

정합성 검증과 데이터 동기화 전략

프로젝트의 중요한 목표는 데이터 정합성의 완벽한 확보였습니다. 이중 쓰기로 실시간 쓰기 동기화는 이루었지만, 이중 쓰기 로직에 문제가 있어 데이터가 다르게 쓰이고 있지는 않은지, 최종 전환 전에 두 DBMS 간의 데이터 일치 여부를 정량적으로 검증하는 과정이 필수였습니다. 워크플로 관리 도구인 Airflow로 정합성 검증 프로세스를 자동화했습니다. Airflow 파이프라인으로 주기적으로 기존 Oracle DB와 신규 MySQL DB의 주요 테이블 데이터를 추출해 Hive 데이터 웨어하우스로 통합했습니다.

Hive 환경에서 고성능의 분산 쿼리를 실행해 두 데이터셋을 비교했으며, 레코드 수 비교, 핵심 칼럼 값의 해시 비교, 주요 비즈니스 통계 값의 차이를 검출했습니다. 이 과정에서 발견된 불일치 건은 상세 분석 및 수정을 거쳐 최종적으로 두 데이터베이스 간의 정합성이 100% 보장되도록 조치했습니다. 이 엄격한 검증 단계는 신규 MySQL 환경으로의 최종 컷오버를 위한 결정적인 안전장치가 되었습니다. 테이블별 row의 추출 시간 차이 때문에 발생하는 불일치 건은 수동으로 검증하는 과정을 거쳤습니다. 약 6개월간 테이블별 불일치 데이터 건수와 그 내용을 분석해 로직을 수정한 끝에 이중 쓰기 환경에서 데이터 정합성을 확보했습니다.

검증 항목 검증 방법 도구
레코드 수 테이블별 총 건수 비교 Hive 분산 쿼리
칼럼 값 핵심 칼럼 해시 비교 Hive 분산 쿼리
비즈니스 통계 주요 지표 값 차이 검출 Hive 분산 쿼리
전체 프로세스 주기적 자동 검증 Airflow 파이프라인

데이터베이스를 Oracle에서 MySQL로 전환하는 과제에서 중요한 것은 사용자가 이용하는 기능을 이전과 완전히 동일하게 유지하는 것이었습니다. 단순히 데이터만 옮기는 것이 아니라, DBMS 변화가 기존 비즈니스 로직에 예기치 못한 영향을 주지 않도록 약 3개월간의 QA 기간을 거쳤습니다. 이 과정에서 쿼리 실행 결과의 미세한 차이부터 트랜잭션 처리 방식까지 철저히 검증함으로써, 내부 인프라의 변화에도 불구하고 서비스의 모든 기능이 기존과 변함없이 안정적으로 작동함을 확인했습니다. 전환이 제대로 되었다는 것은 회원 부서가 발행하는 쿼리가 더 이상 Oracle로 유입되지 않고 MySQL로 유입된다는 의미입니다. 이를 검증하기 위해 모든 기존의 전환 대상 쿼리에 사전에 회원개발쿼리와 같은 임의의 주석을 추가했습니다. 전환 후 해당 주석이 포함된 쿼리가 Oracle 데이터베이스에 더 이상 인입되지 않는지 모니터링했습니다.

정합성 검증 방법론은 매우 체계적이고 재현 가능한 패턴입니다. 다만 "MySQL 실패는 무시" 전략에서 무시된 쓰기를 어떻게 추적하고 어떤 SLO로 복구했는지(예: 누락 건수 상한, 복구 평균시간, 자동 재실행 여부)가 더 상세히 나왔다면, 독자가 실제로 적용할 때 운영 안전장치를 더 명확히 설계할 수 있었을 것입니다. 또한 6개월간의 불일치 분석 과정에서 발견된 주요 패턴이나 자주 발생한 이슈 유형이 공유되면 다른 팀에게 더욱 실용적인 가이드가 될 것입니다.

이 프로젝트는 레거시·무중단·롤백·정합성·성능이라는 어려운 제약을 현실적으로 풀어낸 훌륭한 실전 사례입니다. 성공적인 이관 작업의 결과, Oracle 데이터베이스의 세션 수가 감소하면서 시스템의 PGA 메모리 사용량이 줄어들었고, PagingSpace Used(swap)가 감소하면서 메모리 자원이 추가로 확보되었습니다. 이 리소스 여유분은 공용 장비를 사용하는 타 부서에 자원적 안정성을 제공하는 동시에, 서비스 측면에서는 확보된 별도 장비 환경에서 기존 공용 장비 제약 없이 파드 개수를 늘려 안정적인 서비스 확장 및 운영의 토대를 마련하게 되었습니다. 다만 더 완성도 높은 레퍼런스가 되려면, dual write 선택의 대안 비교(특히 CDC), 커밋 후 실행의 실패/재처리/멱등성 설계, SQL 2벌 유지보수 전략(전환 후 정리 계획), 전후 성과를 수치로 제시하는 부분이 보강되면 독자가 그대로 적용할 수 있는 아키텍처 수준으로 승격될 것입니다.

자주 묻는 질문 (FAQ)

Q. 이중 쓰기 방식 대신 CDC(Change Data Capture)를 사용하지 않은 이유는 무엇인가요?
A. 이중 쓰기는 애플리케이션 레벨에서 직접 제어할 수 있어 레거시 환경에서 구현이 상대적으로 단순하고, 롤백 시나리오를 양방향으로 대칭 구조로 설계할 수 있다는 장점이 있습니다. CDC는 데이터베이스 로그 기반으로 동작하여 애플리케이션 코드 변경이 적지만, Oracle의 LogMiner나 GoldenGate 같은 도구는 라이선스 비용이 발생하거나 MySQL과의 호환성 문제가 있을 수 있습니다. 또한 10년 이상의 레거시 환경에서 CDC 파이프라인의 안정성을 검증하고 운영하는 것보다, 기존 애플리케이션 로직 내에서 이중 쓰기를 직접 구현하는 것이 더 빠르고 예측 가능한 선택이었을 것으로 보입니다.

Q. MyBatis XML을 Oracle용과 MySQL용으로 두 벌 만드는 것이 장기적으로 유지보수에 부담이 되지 않나요?
A. 맞습니다. SQL XML을 두 벌로 관리하는 것은 장기적으로 유지보수 비용이 증가합니다. Oracle과 MySQL 간의 문법 차이(날짜 함수, NULL 처리, 페이징 등)를 계속 동기화해야 하므로, 전환 완료 후에는 이중 쓰기 코드와 Oracle용 XML을 제거하는 것이 필수입니다. 또한 가능하다면 SQL 표준화 가이드나 쿼리 빌더, DB별 함수 래퍼 같은 장치를 마련해 향후 DBMS 변경 시 유연하게 대응할 수 있도록 하는 것이 좋습니다. 이 프로젝트는 무중단 전환을 위한 일시적 전략이었으며, 전환 후 정리 계획이 함께 수립되어야 완전한 성공이라 할 수 있습니다.

Q. JPA에서 SEQUENCE에서 IDENTITY로 변경할 때 발생하는 연관관계 문제는 어떻게 해결하나요?
A. Oracle의 SEQUENCE는 INSERT 이전에 식별자가 생성되므로 persist() 호출 직후에도 ID를 활용할 수 있지만, MySQL의 IDENTITY는 INSERT 이후에야 식별자가 생성됩니다. 이로 인해 연관관계 설정 시점에 ID가 존재하지 않아 문제가 발생할 수 있습니다. 해결 방법으로는 flush() 호출 시점을 조정하거나 저장 순서를 변경하는 방법이 있으며, 식별자에 의존하던 로직을 정리하고 연관관계의 주인 설정을 명확히 하는 것이 근본적인 해결책입니다. 로직 수정이 불가능한 경우에는 채번용 MySQL 테이블을 별도로 두어 테이블에서 ID를 채번하도록 수정할 수도 있습니다.

Q. 이중 쓰기 기간 동안 MySQL 쿼리가 실패하면 어떻게 처리하나요?
A. 이중 쓰기 초기에는 MySQL과 Oracle 간 데이터 정합성이 맞지 않고, 쿼리가 MySQL에서 오류 없이 동작하는지 검증되지 않았으므로 MySQL 쿼리 실패를 용인하는 전략을 채택했습니다. MySQL 쿼리는 트랜잭션에 따라 원자적으로 수행하되, 실패하더라도 Oracle 트랜잭션이 롤백되지 않고 무시되도록 했습니다. 실패한 쿼리는 로깅해서 분석하고, 주기적으로 마이그레이션과 정합성 검증을 반복하며 불일치 원인을 찾아 제거해 나갔습니다. 장기적으로는 재처리 큐나 데드레터, 멱등성 보장 같은 안전장치를 추가하여 실패율을 점진적으로 낮추는 것이 이상적입니다.

Q. Read 트래픽 복제를 통한 성능 검증이 실제 운영 환경과 동일한 부하를 재현할 수 있나요?
A. Read 트래픽 복제는 실제 운영 환경의 Read 부하를 그대로 MySQL에 재현할 수 있어, 쿼리 성능과 DB 자원 사용량을 검증하는 데 매우 효과적입니다. 다만 Write 부하는 이미 이중 쓰기를 통해 MySQL에 반영되고 있으므로, Read와 Write를 모두 고려한 종합적인 부하 테스트가 가능합니다. 이 방식의 장점은 타 부서 시스템에 영향을 주지 않으면서도 실제 트래픽 패턴을 재현할 수 있다는 점이며, Kafka를 통해 비동기로 처리하므로 운영 서비스에 레이턴시를 추가하지 않습니다. 다만 캐시 워밍이나 커넥션 풀 상태 같은 세부 환경 차이는 추가로 고려해야 합니다.

 

[출처]
NAVER D2 - 스마트스토어 회원 파트의 Oracle에서 MySQL로의 DB 마이그레이션: https://d2.naver.com/helloworld/6512234


Disclaimer · Privacy Policy · About · Contact

© 2026 테크마스터