# Oracle에서 PostgreSQL로 다운타임 없이 마이그레이션: 기술적 분석
Oracle에서 PostgreSQL로 서비스 중단 없이 마이그레이션하는 것은 도전적이지만 달성 가능한 작업입니다. 이 글에서는 Spring Boot 마이크로서비스를 사용한 실제 프로젝트를 기반으로 사용자에게 전혀 영향을 주지 않는다는 핵심 요구사항을 충족하는 단계별 마이그레이션 전략을 자세히 분석합니다. Debezium을 통한 CDC를 활용해 실시간 데이터 동기화를 구현함으로써 다운타임을 피하고 위험을 최소화했습니다.
준비 단계: 구조와 데이터 분석
첫 번째 단계는 현재 데이터베이스를 철저히 감사하는 것입니다. 데이터 용량, 필드 타입 분포, 오래된 레코드 존재 여부, PostgreSQL에 직접 대응되는 등가물이 없는 Oracle 고유 구조 등을 파악해야 합니다. 불필요한 데이터를 정리하면 마이그레이션 용량을 줄이고 프로세스를 가속화할 수 있습니다.
주요 요소는 데이터 타입 매핑입니다. 예를 들어:
NUMBER(1,0)→BOOLEANVARCHAR2→TEXT또는VARCHARDATE→TIMESTAMP WITHOUT TIME ZONECLOB→TEXT
분석을 위해 다음 SQL 쿼리를 사용했습니다:
select distinct DATA_TYPE
from ALL_TAB_COLUMNS
where owner = 'schemat'
order by DATA_TYPE
이 결과를 바탕으로 모든 후속 변환의 기반이 되는 데이터 타입 매핑 테이블을 만들었습니다. 또한 NULL과 빈 문자열 처리 차이를 고려해야 합니다. Oracle에서는 빈 문자열이 NULL로 취급되지만 PostgreSQL에서는 그렇지 않습니다. 이로 인해 비즈니스 로직과 쿼리를 조정해야 합니다.
스키마 적응과 Liquibase
데이터베이스 드라이버만 교체하면 오류가 발생합니다. 마이그레이션 스크립트를 PostgreSQL에 맞게 적응시켜야 합니다. 이를 위해:
- Liquibase 컨텍스트를 통해 DDL과 DML 작업을 분리 — DML은 일시적으로 비활성화.
- 스크립트를 반복적으로 테스트: 변경 후 changelog 실행, 정리, 스모크 테스트.
- 전제 조건의 정확성을 확인하고 Oracle과의 스키마 일관성을 보장.
시퀀스에 특별한 주의를 기울여야 합니다. 동기화되지 않으면 새로운 ID가 기존 데이터와 충돌할 수 있습니다. 해결책:
- 애플리케이션 시작 시 PostgreSQL의 현재 시퀀스 값을 확인.
- Oracle보다 낮으면 버퍼를 두고 앞으로 이동.
- 로직은 멱등성을 보장: 반복 실행 시 실패하지 않음.
결과적으로 대상 PostgreSQL 스키마가 단일 풀 리퀘스트로 구성되어 데이터 채우기에 바로 사용할 수 있게 되었습니다.
애플리케이션 코드 조정
빈 PostgreSQL에 애플리케이션을 성공적으로 실행한 후 Java 레벨 코드 오류가 드러납니다. 주요 변경 사항:
@Lob어노테이션을@Column(columnDefinition = "text")로 교체.- 네이티브 SQL 쿼리에서 NULL 처리, 특히
IN (...)구조. - 숫자 플래그(0/1)를
boolean으로 전환. - Oracle/PostgreSQL 차이를 반영한 문자열 비교 로직 조정.
- PostgreSQL 방언에 맞게 네이티브 쿼리 재작성.
유닛 테스트와 통합 시나리오 수준에서 테스트를 수행했습니다. 빈 문자열, NULL 값, 대용량 텍스트 필드 등 엣지 케이스에 특별히 집중했습니다.
Debezium과 Kafka Connect를 통한 CDC 구현
클래식 스택인 Debezium + Kafka Connect를 선택했습니다. 아키텍처:
- 소스 커넥터(Debezium Oracle)가 redo log를 읽고 CDC 이벤트를 생성.
- 이벤트를 Kafka로 전송(테이블당 하나의 토픽).
- 싱크 커넥터(JDBC Sink)가 동일 순서로 PostgreSQL에 변경 사항 적용.
설정 단계:
- 초기 스냅샷 — 시작 시점의 전체 데이터 복사.
- 스트리밍 변경 — redo log로부터 INSERT/UPDATE/DELETE 지속 복제.
- 순서 보존 — LogMiner를 통한 변경 순서 보장.
사용된 이미지:
quay.io/debezium/kafka:3.0quay.io/debezium/zookeeper:3.0quay.io/debezium/connect:3.0provectuslabs/kafka-ui:0.7.2(모니터링용)
설정은 curl을 사용한 REST API로 수행했습니다. 마이그레이션 중 Debezium UI가 불안정했으므로 JSON 형식으로 수동 전달했습니다.
소스 커넥터 구성 예시:
{
"name": "oracle-connector",
"config": {
"connector.class": "io.debezium.connector.oracle.OracleConnector",
"database.hostname": "oracle-host",
"database.port": "1521",
"database.user": "user",
"database.password": "password",
"database.dbname": "ORCLCDB",
"database.server.name": "server1",
"table.include.list": "schema.table1,schema.table2",
"database.history.kafka.bootstrap.servers": "kafka:9092",
"database.history.kafka.topic": "schema-changes"
}
}
싱크 커넥터 구성 예시:
{
"name": "postgres-sink",
"config": {
"connector.class": "io.confluent.connect.jdbc.JdbcSinkConnector",
"connection.url": "jdbc:postgresql://postgres:5432/db",
"connection.user": "user",
"connection.password": "password",
"topics": "server1.schema.table1,server1.schema.table2",
"auto.create": "false",
"auto.evolve": "false",
"insert.mode": "upsert",
"pk.mode": "record_key",
"pk.fields": "id"
}
}
복합 키를 가진 테이블(예: 일부 테이블)은 별도 구성이 필요합니다. 각 커넥터를 독립적으로 테스트했습니다.
최종 전환과 품질 관리
초기 스냅샷 완료와 스트리밍 안정화 후:
- PostgreSQL에 연결된 서비스 테스트 인스턴스 실행.
- 엔드투엔드 테스트 수행: API, UI, 백그라운드 프로세스.
- 프로덕션과 유사한 데이터로 부하 테스트.
- 최소 부하 시점에 DNS 또는 인그레스 라우트를 새 인스턴스로 전환.
- 24–48시간 동안 로그, 메트릭, 알림 모니터링.
롤백은 전환 시점까지만 가능합니다. 그 후 시스템은 PostgreSQL만 사용합니다. Oracle 백업은 긴급 데이터 복구를 위해 보관합니다.
주요 교훈
- 만능 솔루션은 없다 — 모든 프로젝트에 맞춤 적응이 필요.
- 더블 라이트 없는 CDC — 몇 초 지연과 타협.
- 시퀀스 동기화 — ID 충돌 방지를 위한 필수 요소.
- 모든 단계에서의 테스트 — 안정적 마이그레이션의 핵심.
- Debezium + Kafka Connect — 온라인 마이그레이션을 위한 유연하고 안정적인 스택.
마이그레이션은 준비를 포함해 18시간 소요되었으나 실제 사용자 다운타임은 0초였습니다. 프로젝트는 PostgreSQL에서 1년 이상 성공적으로 운영 중이며 퇴보가 없습니다.
— Editorial Team
아직 댓글이 없습니다.