본문 바로가기
나의 IT 기억

MySQL에서 PostgreSQL로의 실시간 데이터 동기화 시스템 구축기

by 浪畅 (Làng Chàng) 2025. 5. 24.

MySQL에서 PostgreSQL로의 실시간 데이터 동기화 시스템 구축기

프로젝트 배경과 필요성

최근 회사에서 레거시 MySQL 시스템을 PostgreSQL로 전환하는 대규모 마이그레이션 프로젝트를 진행하게 되었습니다. 단순히 한 번에 데이터를 옮기는 것이 아니라, 서비스 중단 없이 점진적으로 전환해야 하는 상황이었기 때문에 실시간 데이터 동기화가 필수적이었습니다.

처음에는 "그냥 배치로 주기적으로 복사하면 되지 않을까?"라고 생각했지만, 실제로는 그렇게 간단하지 않았습니다. 실시간성이 중요한 주문 시스템과 사용자 데이터를 다루고 있었기 때문에, 데이터 지연이나 불일치가 발생하면 비즈니스에 직접적인 영향을 미칠 수 있었거든요.

기술 선택의 고민

여러 가지 옵션을 검토해봤습니다:

1. 직접 구현한 ETL 스크립트

  • 장점: 완전한 커스터마이징 가능
  • 단점: 개발 시간이 오래 걸리고, 안정성 검증이 필요

2. AWS DMS (Database Migration Service)

  • 장점: 관리형 서비스로 안정적
  • 단점: 비용이 상당하고, 세밀한 제어가 어려움

3. Debezium + Kafka 조합

  • 장점: 오픈소스, 높은 안정성, 확장성
  • 단점: 초기 설정이 복잡

고민 끝에 Debezium을 선택했습니다. CDC(Change Data Capture) 방식으로 MySQL의 바이너리 로그를 실시간으로 읽어서 변경사항을 감지하는 방식이 가장 안정적이고 성능이 좋다고 판단했기 때문입니다.

시스템 아키텍처 설계

최종적으로 구성한 아키텍처는 다음과 같습니다:

MySQL (Source DB) → Debezium Connector → Apache Kafka → Python Sync Script → PostgreSQL (Target DB)

각 컴포넌트의 역할은 다음과 같습니다:

  • MySQL: 소스 데이터베이스, 바이너리 로그 활성화 필요
  • Debezium: MySQL의 변경사항을 감지하여 Kafka로 전송
  • Apache Kafka: 메시지 큐 역할, 높은 처리량과 내구성 보장
  • Python Sync Script: Kafka 메시지를 소비하여 PostgreSQL에 적용
  • PostgreSQL: 타겟 데이터베이스

구현 과정에서의 어려움들

1. 한글 인코딩 문제

첫 번째로 마주한 큰 문제는 한글 데이터 처리였습니다. MySQL에서는 utf8mb4 인코딩을 사용하고 있었는데, PostgreSQL로 동기화하는 과정에서 한글이 깨지는 현상이 발생했습니다.

해결 방법:

  • MySQL의 바이너리 로그 포맷을 ROW로 설정
  • PostgreSQL 데이터베이스를 UTF-8로 생성
  • Python 스크립트에서 명시적으로 UTF-8 인코딩 지정

2. 스키마 차이점 해결

MySQL과 PostgreSQL은 데이터 타입이 조금씩 다릅니다. 특히 다음과 같은 차이점들을 해결해야 했습니다:

  • MySQL의 TINYINT(1)는 PostgreSQL의 BOOLEAN으로 변환
  • MySQL의 DATETIME는 PostgreSQL의 TIMESTAMP로 변환
  • MySQL의 AUTO_INCREMENT는 PostgreSQL의 SERIAL로 변환

3. 초기 데이터 마이그레이션

실시간 동기화를 시작하기 전에 기존 데이터를 모두 PostgreSQL로 옮겨야 했습니다. 수백만 건의 데이터를 효율적으로 이관하기 위해 다음과 같은 전략을 사용했습니다:

  • 배치 크기 최적화 (1,000건씩 처리)
  • 인덱스 생성을 마지막에 수행
  • 트랜잭션 단위 조정으로 메모리 사용량 최적화

핵심 구현 내용

Docker 기반 인프라 구성

전체 시스템을 Docker Compose로 구성했습니다. 이렇게 하면 개발 환경과 프로덕션 환경을 일관되게 유지할 수 있고, 팀원들이 쉽게 로컬에서 테스트해볼 수 있습니다.

# docker-compose.yml의 핵심 구성
services:
  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: rootpass
    ports:
      - "3306:3306"

  postgres:
    image: postgres:15
    environment:
      POSTGRES_PASSWORD: targetpass
    ports:
      - "5432:5432"

  kafka:
    image: confluentinc/cp-kafka:latest
    ports:
      - "9092:9092"

  kafka-connect:
    image: debezium/connect:2.4
    ports:
      - "8083:8083"

자동 동기화 스크립트

가장 핵심이 되는 부분은 Python으로 작성한 자동 동기화 스크립트입니다. 이 스크립트는 다음과 같은 기능을 제공합니다:

  • MySQL과 PostgreSQL의 데이터를 주기적으로 비교
  • 누락된 데이터를 자동으로 감지하고 동기화
  • 오류 발생 시 자동 재시도 및 로깅
  • 프로세스 재시작 시 중단점부터 재개

특히 데이터 일관성을 보장하기 위해 다음과 같은 로직을 구현했습니다:

def sync_missing_records():
    """MySQL과 PostgreSQL 간 누락된 레코드를 동기화"""
    for table in TABLES:
        mysql_max_id = get_max_id('mysql', table)
        postgres_max_id = get_max_id('postgres', table)

        if mysql_max_id > postgres_max_id:
            missing_records = get_missing_records(table, postgres_max_id, mysql_max_id)
            insert_records_to_postgres(table, missing_records)

모니터링 시스템

실시간 동기화가 제대로 작동하는지 확인하기 위해 모니터링 스크립트도 개발했습니다. 이 스크립트는 다음을 확인합니다:

  • Docker 컨테이너 상태
  • Kafka 커넥터 연결 상태
  • 데이터베이스 연결 상태
  • 동기화 프로세스 실행 상태
  • 데이터 동기화 현황 (레코드 수 비교)

성능 최적화 경험

1. 배치 크기 튜닝

처음에는 한 번에 100건씩 처리했는데, 처리 속도가 너무 느렸습니다. 여러 번의 테스트를 통해 1,000건이 최적의 배치 크기임을 발견했습니다. 더 크게 하면 메모리 사용량이 급증하고, 더 작게 하면 네트워크 오버헤드가 커집니다.

2. 인덱스 전략

PostgreSQL에서 데이터 조회 성능을 높이기 위해 적절한 인덱스를 생성했습니다. 특히 동기화 과정에서 자주 사용되는 id 컬럼과 created_at 컬럼에 인덱스를 생성했습니다.

3. 연결 풀링

데이터베이스 연결을 매번 새로 생성하는 것은 비효율적이므로, 연결 풀을 사용해서 성능을 개선했습니다.

운영 중 발생한 이슈들

1. Kafka 디스크 부족

운영 중에 Kafka가 저장하는 로그 파일이 계속 쌓여서 디스크 공간이 부족해진 적이 있습니다. 이를 해결하기 위해 로그 보존 정책을 설정했습니다:

log.retention.hours=24
log.segment.bytes=104857600

2. 네트워크 단절 시 복구

네트워크 문제로 동기화가 중단되었을 때, 자동으로 재연결하고 중단된 지점부터 동기화를 재개하는 로직을 추가했습니다.

3. 대용량 데이터 처리

한 번에 수십만 건의 데이터가 변경되는 경우가 있었는데, 이때 메모리 사용량이 급증하는 문제가 있었습니다. 이를 해결하기 위해 스트리밍 방식으로 데이터를 처리하도록 개선했습니다.

결과 및 성과

정량적 성과

  • 동기화 지연시간: 평균 5-10초 이내
  • 데이터 정확성: 100% 일치 (24시간 모니터링 결과)
  • 시스템 안정성: 99.9% 가동률 (한 달간 모니터링)
  • 장애 복구 시간: 평균 30초 이내 자동 복구

정성적 성과

  1. 개발 생산성 향상: 한 번 설정하면 자동으로 동작하므로 개발자가 신경 쓸 일이 줄어들었습니다.
  2. 서비스 안정성 확보: 실시간 동기화 덕분에 점진적 마이그레이션이 가능해졌고, 서비스 중단 없이 시스템 전환을 진행할 수 있었습니다.
  3. 운영 효율성: 모니터링 시스템 덕분에 문제를 조기에 발견하고 대응할 수 있게 되었습니다.

교훈과 개선 사항

1. 초기 설계의 중요성

처음에 제대로 설계하지 않으면 나중에 수정하기가 매우 어렵습니다. 특히 데이터 타입 매핑이나 스키마 변환 규칙은 초기에 명확히 정의해두는 것이 중요합니다.

2. 모니터링의 필수성

실시간 시스템에서는 모니터링이 필수입니다. 문제가 발생했을 때 빠르게 인지하고 대응할 수 있는 체계를 갖춰야 합니다.

3. 오류 처리의 중요성

네트워크 오류, 데이터베이스 연결 오류 등 다양한 예외 상황을 고려한 견고한 오류 처리 로직이 필요합니다.

4. 테스트 환경의 중요성

프로덕션 환경과 동일한 테스트 환경에서 충분히 검증한 후 배포하는 것이 중요합니다. Docker 덕분에 이런 환경을 쉽게 구성할 수 있었습니다.

향후 개선 계획

1. 스키마 변경 자동 감지

현재는 테이블 구조가 변경되면 수동으로 설정을 수정해야 하는데, 이를 자동으로 감지하고 적용하는 기능을 추가할 예정입니다.

2. 다중 데이터베이스 지원

현재는 MySQL → PostgreSQL만 지원하지만, Oracle, SQL Server 등 다른 데이터베이스도 지원하도록 확장할 계획입니다.

3. 웹 UI 개발

현재는 명령줄 인터페이스로만 모니터링할 수 있는데, 더 직관적인 웹 기반 모니터링 대시보드를 개발할 예정입니다.

4. 메트릭 수집 및 알림

Prometheus, Grafana와 연동하여 더 상세한 메트릭을 수집하고, 문제 발생 시 즉시 알림을 받을 수 있는 시스템을 구축할 계획입니다.

마무리

이번 프로젝트를 통해 실시간 데이터 동기화 시스템을 구축하는 전체 과정을 경험할 수 있었습니다. 기술적으로는 Debezium, Kafka, Docker 등 최신 기술 스택을 활용해보는 좋은 기회였고, 실무적으로는 대규모 데이터 마이그레이션 프로젝트를 성공적으로 완수할 수 있었습니다.

특히 인상 깊었던 점은 오픈소스 생태계의 성숙도입니다. Debezium과 Kafka의 조합은 정말 강력했고, Docker 덕분에 복잡한 인프라를 쉽게 관리할 수 있었습니다.

혹시 비슷한 프로젝트를 진행하고 계신 분들에게 도움이 되었으면 좋겠습니다. 질문이나 의견이 있으시면 언제든 댓글로 남겨주세요!


전체 소스코드는 GitHub에서 확인할 수 있습니다: https://github.com/onesound71/debezium-sync

이 글이 도움이 되셨다면 ❤️를 눌러주세요!