들어가며 — 왜 갑자기 다들 엔진을 갈아엎는가
데이터베이스 엔진을 바꾸는 일은 원래 흔치 않았습니다. 한 번 Oracle 위에 시스템을 올리면 십수 년을 그 위에서 사는 것이 보통이었고, 누구도 굳이 멀쩡히 도는 엔진을 들어내려 하지 않았습니다. 그런데 2020년대 들어 분위기가 달라졌습니다. 금융권, 통신사, 공공기관까지 "Oracle을 걷어내고 PostgreSQL로 간다"는 프로젝트가 줄을 이었고, 2026년 지금은 이기종 DB 전환이 더 이상 특이한 결정이 아니라 하나의 표준 과제처럼 자리 잡았습니다.
이유는 크게 셋입니다. 첫째는 라이선스 비용입니다. 코어 수 기반 라이선스와 매년 갱신되는 유지보수 요금은, 시스템이 커질수록 기하급수적으로 늘어납니다. 둘째는 클라우드입니다. 클라우드로 옮기면서 관리형 데이터베이스를 쓰려고 보니, 상용 엔진을 클라우드에서 그대로 돌리는 비용이 오히려 더 비싼 경우가 많았습니다. 셋째는 개방성입니다. 특정 벤더에 묶이는 락인에서 벗어나, 확장과 생태계가 풍부한 오픈소스로 이동하려는 흐름입니다.
이 글에서는 Oracle에서 PostgreSQL로 가는 길을 동기부터 호환성 격차, 도구, 데이터 이전, 애플리케이션 재작성, 검증, 단계적 전환, 실제 사례, 함정까지 실무 관점에서 정리합니다. "그 너머"라는 부제를 붙인 이유는, 같은 방법론이 결국 다른 모든 이기종 전환에도 그대로 통하기 때문입니다.
전환의 동기 — 라이선스와 클라우드
먼저 왜 옮기는지를 정확히 정리해야 합니다. 동기가 흐릿하면 프로젝트 중반에 "이걸 왜 하고 있나"라는 회의가 반드시 찾아오기 때문입니다.
- 라이선스 비용: 상용 엔진은 코어 단위 라이선스에 더해 매년 유지보수 비용을 청구합니다. 노드를 늘릴 때마다, 코어를 추가할 때마다 비용이 같이 뜁니다.
- 클라우드 적합성: 관리형 PostgreSQL 서비스는 클라우드 사업자마다 잘 다듬어져 있습니다. 자동 백업, 읽기 복제본, 자동 장애 조치를 버튼 몇 번으로 얻습니다.
- 생태계와 확장: PostgreSQL은 확장 기능이 풍부합니다. 공간 데이터, 시계열, 전문 검색, 벡터 검색까지 확장으로 흡수합니다.
- 인력과 채용: 오픈소스 경험을 가진 개발자 풀이 넓어, 채용과 유지가 상대적으로 쉽습니다.
반대로 옮기지 말아야 할 신호도 있습니다. 핵심 업무 로직이 수십만 줄의 PL/SQL에 박혀 있고, 그 로직이 곧 회사의 경쟁력이며, 전환에 따른 위험 대비 절감액이 작다면, 전환은 비용만 키우는 결정이 됩니다. 동기를 숫자로 환산해 보고 의사결정을 내려야 합니다.
큰 그림 — 전환은 다섯 갈래의 작업이다
이기종 전환을 한 덩어리로 보면 막막합니다. 실제로는 다섯 갈래로 나뉘는 별개의 작업입니다.
+---------------------------------------------------------------+
| 이기종 DB 엔진 전환의 다섯 갈래 |
| |
| 1. 스키마 변환 |
| - 테이블, 인덱스, 제약, 뷰, 시퀀스 |
| - 데이터 타입 매핑 (NUMBER -> numeric 등) |
| |
| 2. 객체(코드) 변환 |
| - 프로시저, 함수, 트리거, 패키지 |
| - PL/SQL -> PL/pgSQL |
| |
| 3. 데이터 이전 |
| - 초기 적재 + 변경분 동기화(CDC) |
| |
| 4. 애플리케이션 변경 |
| - SQL 방언 차이, 드라이버, 트랜잭션 처리 |
| |
| 5. 검증과 전환 |
| - 데이터 정합성, 성능 회귀, 컷오버 |
+---------------------------------------------------------------+
이 다섯 갈래는 난이도와 자동화 가능성이 제각각입니다. 스키마와 데이터 이전은 도구가 잘 받쳐 주어 자동화율이 높습니다. 반면 PL/SQL 변환과 애플리케이션 SQL 재작성은 사람의 손이 많이 갑니다. 프로젝트 일정의 대부분은 후자에서 소모됩니다.
호환성 격차 1 — 데이터 타입
가장 먼저 부딪히는 격차는 데이터 타입입니다. Oracle과 PostgreSQL은 비슷해 보이지만 미묘하게 다른 타입 체계를 가지고 있습니다.
| Oracle | PostgreSQL | 주의점 |
| --- | --- | --- |
| NUMBER | numeric 또는 정수형 | 정밀도 없는 NUMBER는 통째로 numeric이 되어 느려질 수 있음 |
| VARCHAR2 | varchar | 길이 의미가 바이트 대 문자로 다를 수 있음 |
| CHAR | char | 공백 패딩 동작 유사하나 비교 규칙 확인 필요 |
| DATE | timestamp | Oracle DATE는 시분초 포함, PG date는 날짜만 |
| CLOB | text | text는 길이 제한이 사실상 없음 |
| BLOB | bytea | 대용량은 별도 저장 전략 고려 |
| RAW | bytea | 길이 제한 차이 확인 |
| NUMBER(1) 불리언 대용 | boolean | 불리언으로 정규화할 좋은 기회 |
가장 흔한 함정은 NUMBER입니다. Oracle 개발자는 정수든 실수든 일단 NUMBER를 쓰는 습관이 있는데, 이를 그대로 numeric으로 옮기면 PostgreSQL에서 정수 연산보다 느려집니다. 명백한 정수 컬럼은 integer나 bigint로 좁혀 주는 것이 좋습니다.
-- Oracle 원본
CREATE TABLE orders (
order_id NUMBER(18) NOT NULL,
amount NUMBER(12,2) NOT NULL,
is_paid NUMBER(1) DEFAULT 0 NOT NULL,
created_at DATE DEFAULT SYSDATE
);
-- PostgreSQL로 다듬은 형태
CREATE TABLE orders (
order_id bigint NOT NULL,
amount numeric(12,2) NOT NULL,
is_paid boolean DEFAULT false NOT NULL,
created_at timestamptz DEFAULT now()
);
DATE도 주의해야 합니다. Oracle의 DATE는 날짜와 시각을 함께 담지만, PostgreSQL의 date는 날짜만 담습니다. 시각이 필요한 컬럼은 timestamp 또는 timestamptz로 매핑해야 데이터 손실이 없습니다. 타임존을 다루는 시스템이라면 처음부터 timestamptz로 통일하는 것이 안전합니다.
호환성 격차 2 — 시퀀스와 자동 증가
Oracle의 시퀀스는 PostgreSQL에도 거의 그대로 존재합니다. 다만 사용 문법과 관용구가 다릅니다.
-- Oracle 방식
CREATE SEQUENCE seq_order_id START WITH 1 INCREMENT BY 1;
INSERT INTO orders (order_id) VALUES (seq_order_id.NEXTVAL);
-- PostgreSQL 방식 1: 명시적 시퀀스
CREATE SEQUENCE seq_order_id START WITH 1 INCREMENT BY 1;
INSERT INTO orders (order_id) VALUES (nextval('seq_order_id'));
-- PostgreSQL 방식 2: 표준 IDENTITY 컬럼 (권장)
CREATE TABLE orders (
order_id bigint GENERATED ALWAYS AS IDENTITY,
amount numeric(12,2)
);
Oracle 11g 이전에서 흔히 쓰던 "트리거로 NEXTVAL을 채워 넣는" 패턴은 PostgreSQL에서는 IDENTITY 컬럼으로 깔끔하게 대체됩니다. 다만 데이터를 이전한 직후에는 시퀀스의 현재값을 데이터의 최대값에 맞춰 올려 주는 작업을 잊지 말아야 합니다. 이걸 빠뜨리면 전환 직후 첫 INSERT에서 기본키 충돌이 터집니다.
-- 이전 후 반드시: 시퀀스를 데이터 최대값으로 동기화
SELECT setval('seq_order_id', (SELECT max(order_id) FROM orders));
호환성 격차 3 — SQL 방언과 힌트
같은 SQL이라도 방언 차이가 적지 않습니다. 애플리케이션 코드를 훑으며 다음 패턴을 찾아 바꿔야 합니다.
| Oracle 관용구 | PostgreSQL 대응 | 비고 |
| --- | --- | --- |
| 더미 테이블 DUAL | 그냥 SELECT만 | FROM 절 자체가 불필요 |
| NVL(a, b) | COALESCE(a, b) | 다인자 지원은 COALESCE가 우월 |
| DECODE(...) | CASE WHEN ... | 가독성도 좋아짐 |
| 외부조인 (+) 표기 | 표준 LEFT JOIN | (+) 문법 미지원 |
| ROWNUM 기반 페이징 | LIMIT 와 OFFSET | 또는 키셋 페이징 |
| CONNECT BY 계층 쿼리 | 재귀 CTE | WITH RECURSIVE 사용 |
| 옵티마이저 힌트 주석 | 대부분 불필요 | 플래너 파라미터로 제어 |
| SYSDATE | now() 또는 current_timestamp | 반환 타입 차이 주의 |
가장 마음을 비워야 하는 부분은 옵티마이저 힌트입니다. Oracle에서는 주석 형태의 힌트로 실행 계획을 강제하는 문화가 강합니다. PostgreSQL은 기본적으로 힌트를 지원하지 않습니다. 대신 통계를 잘 모으고, 필요한 경우 세션 단위 플래너 파라미터로 동작을 유도합니다.
-- Oracle: 힌트로 인덱스 강제
SELECT /*+ INDEX(o idx_orders_user) */ *
FROM orders o WHERE user_id = 42;
-- PostgreSQL: 힌트 대신 통계와 플래너 설정
ANALYZE orders; -- 통계 갱신이 우선
SET enable_seqscan = off; -- 최후의 수단, 세션 한정
EXPLAIN ANALYZE
SELECT * FROM orders WHERE user_id = 42; -- 계획을 직접 확인
힌트가 정말 필요한 극소수 상황에는 pg_hint_plan 확장을 도입할 수 있지만, 대부분은 통계와 인덱스 설계로 해결됩니다. "힌트가 없으면 못 산다"는 불안은 전환 초기의 흔한 오해입니다.
호환성 격차 4 — PL/SQL에서 PL/pgSQL로
전환 프로젝트에서 가장 손이 많이 가는 영역입니다. PL/SQL과 PL/pgSQL은 모두 Ada 계열 문법을 공유해 겉모습은 비슷하지만, 핵심 구조가 다릅니다.
가장 큰 차이는 패키지입니다. Oracle의 PACKAGE는 관련 프로시저와 변수를 하나로 묶는 모듈 단위인데, PostgreSQL에는 패키지가 없습니다. 보통 스키마를 모듈처럼 쓰고, 패키지 변수는 별도 설정 테이블이나 세션 변수로 풀어냅니다.
-- Oracle PL/SQL 프로시저
CREATE OR REPLACE PROCEDURE add_order(
p_user_id IN NUMBER,
p_amount IN NUMBER
) AS
BEGIN
INSERT INTO orders (user_id, amount, created_at)
VALUES (p_user_id, p_amount, SYSDATE);
COMMIT;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
RAISE;
END;
이를 PL/pgSQL로 옮기면 다음과 같습니다. 함수 본문을 달러 인용으로 감싸는 점, 트랜잭션 제어를 함수 안에서 직접 다루기 어려운 점이 핵심 차이입니다.
-- PostgreSQL PL/pgSQL 프로시저
CREATE OR REPLACE PROCEDURE add_order(
p_user_id bigint,
p_amount numeric
)
LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO orders (user_id, amount, created_at)
VALUES (p_user_id, p_amount, now());
-- 프로시저는 내부에서 COMMIT 가능, 함수는 불가
COMMIT;
EXCEPTION
WHEN others THEN
RAISE;
END;
$$;
여기서 짚어야 할 차이가 여럿 있습니다. 첫째, PostgreSQL 11부터 PROCEDURE가 생겨 내부에서 COMMIT과 ROLLBACK이 가능해졌지만, FUNCTION에서는 여전히 불가능합니다. 둘째, Oracle의 묵시적 커밋 문화와 달리 PostgreSQL은 트랜잭션 경계를 호출 측에서 잡는 편이 자연스럽습니다. 셋째, 예외 처리 블록은 비용이 큽니다. 남발하면 성능이 떨어집니다.
커서를 많이 쓰는 PL/SQL은 특히 주의가 필요합니다. PL/pgSQL에서도 명시 커서를 지원하지만, 대부분의 행 단위 루프는 집합 기반 SQL로 다시 쓰는 편이 압도적으로 빠릅니다. 전환은 코드를 더 좋게 다시 쓸 좋은 기회이기도 합니다.
-- PL/SQL의 행 단위 루프 (느린 패턴)
-- FOR rec IN (SELECT id FROM staging) LOOP
-- UPDATE target SET flag = 1 WHERE id = rec.id;
-- END LOOP;
-- 집합 기반으로 다시 쓰기 (빠른 패턴)
UPDATE target t
SET flag = 1
FROM staging s
WHERE t.id = s.id;
스키마 변환 도구 — ora2pg와 AWS SCT
다행히 스키마와 코드 변환을 수작업으로만 하지는 않습니다. 두 가지 대표 도구가 있습니다.
ora2pg는 펄로 작성된 오픈소스 도구로, Oracle 스키마와 데이터를 PostgreSQL 형태로 추출하고 변환합니다. 가장 큰 장점은 전환 평가 보고서입니다. 변환 난이도를 자동 채점해, 어디에 사람 손이 많이 갈지 미리 알려 줍니다.
ora2pg 설정 파일 기반으로 평가 보고서 생성
ora2pg -t SHOW_REPORT --estimate_cost -c ora2pg.conf
스키마만 추출 (테이블, 제약, 인덱스)
ora2pg -t TABLE -o schema.sql -c ora2pg.conf
프로시저와 함수 추출 (수동 검토 필수)
ora2pg -t PROCEDURE -o procedures.sql -c ora2pg.conf
ora2pg -t FUNCTION -o functions.sql -c ora2pg.conf
AWS SCT는 AWS의 스키마 변환 도구로, 그래픽 인터페이스에서 변환 가능 항목과 수동 변환 필요 항목을 색으로 구분해 보여 줍니다. 변환 결과를 AWS DMS와 연계해 데이터 이전까지 한 흐름으로 이어 갈 수 있는 점이 강점입니다. 클라우드로 가는 전환이라면 이 조합이 매끄럽습니다.
두 도구 모두 "자동 변환된 것을 그대로 믿지 말 것"이라는 원칙은 같습니다. 도구는 90퍼센트를 처리하고, 나머지 10퍼센트의 까다로운 로직이 프로젝트 시간의 대부분을 잡아먹습니다. 도구의 평가 보고서는 그 10퍼센트의 위치를 알려 주는 지도 역할을 합니다.
데이터 마이그레이션 — 초기 적재와 변경 동기화
스키마가 준비되면 데이터를 옮깁니다. 데이터 이전은 두 단계로 나뉩니다. 초기 전량 적재와, 그 사이에 발생한 변경분을 따라잡는 동기화입니다.
+------------------------------------------------------------+
| 데이터 이전의 두 단계 |
| |
| [1] 초기 전량 적재 (full load) |
| Oracle ----(추출/적재)----> PostgreSQL |
| 대용량이라 시간이 오래 걸림 |
| |
| [2] 변경분 동기화 (CDC) |
| Oracle 의 redo log -> 변경 캡처 -> PostgreSQL |
| 초기 적재 중 발생한 변경을 따라잡음 |
| |
| [3] 컷오버 |
| 지연(lag)이 0에 수렴하면 짧은 정지 후 전환 |
+------------------------------------------------------------+
작은 시스템이라면 점검 시간을 잡고 한 번에 옮기는 빅뱅 방식이 단순합니다. 하지만 무중단을 요구하는 시스템에서는 변경 데이터 캡처가 필수입니다. AWS DMS는 초기 적재와 CDC를 모두 지원해, 두 데이터베이스를 한동안 병행 운영하다가 지연이 0에 가까워졌을 때 짧게 컷오버할 수 있게 해 줍니다.
ora2pg로 데이터까지 추출하는 단순 방식 (소규모)
ora2pg -t COPY -o data.sql -c ora2pg.conf
또는 직접 PostgreSQL로 적재 (병렬 처리)
ora2pg -t COPY -J 4 -c ora2pg.conf
대용량에서는 인덱스를 모두 떼고 적재한 뒤 마지막에 다시 만드는 것이 빠릅니다. 또한 적재 중에는 자동 VACUUM과 동기 커밋을 잠시 느슨하게 풀어 두면 처리량이 크게 올라갑니다. 다만 이 설정은 적재가 끝나면 반드시 원래대로 돌려놓아야 합니다.
애플리케이션 SQL 재작성
데이터베이스만 바꾼다고 끝이 아닙니다. 애플리케이션이 던지는 SQL도 손봐야 합니다. 이 작업의 규모는 코드베이스가 SQL을 어떻게 다루느냐에 크게 좌우됩니다.
ORM 위에 깔끔하게 추상화된 애플리케이션이라면, 방언 설정만 바꾸어도 상당 부분이 자동으로 해결됩니다. 반면 문자열로 직접 SQL을 조립하는 코드, 특히 Oracle 전용 함수가 곳곳에 박힌 코드는 일일이 찾아 고쳐야 합니다.
재작성 작업량의 스펙트럼
낮음 [ORM 추상화 잘 됨] ---- [부분적 네이티브 SQL] ---- [전부 동적 SQL] 높음
방언 설정 변경 혼합 대응 전수 검사 필요
실무에서 효과가 큰 접근은 그래프를 만들어 두는 것입니다. 코드베이스 전체에서 Oracle 고유 함수와 관용구를 정규식으로 긁어, 어디에 몇 번 등장하는지 목록을 만듭니다. NVL, DECODE, SYSDATE, ROWNUM, CONNECT BY, 더미 테이블 같은 패턴을 우선순위로 잡고 하나씩 정리해 나가면 됩니다. 가장 위험한 것은 "찾았다고 생각했는데 동적으로 생성되어 정적 검색에 안 잡히는" SQL이므로, 통합 테스트로 실제 실행 경로를 덮는 것이 안전합니다.
검증 — 데이터 정합성과 성능 회귀
옮긴 데이터가 맞는지, 옮긴 시스템이 충분히 빠른지를 증명하지 못하면 전환은 끝난 것이 아닙니다. 검증은 두 축으로 봅니다.
첫째는 데이터 정합성입니다. 양쪽 데이터베이스의 행 수, 집계값, 표본 행을 비교합니다. 가장 단순하면서 강력한 방법은 테이블별 행 수와 핵심 컬럼의 체크섬을 양쪽에서 뽑아 맞춰 보는 것입니다.
-- PostgreSQL 측: 테이블별 행 수와 금액 합계
SELECT 'orders' AS tbl, count(*) AS rows, sum(amount) AS sum_amount
FROM orders;
-- Oracle 측에서도 같은 값을 뽑아 비교
-- SELECT 'orders', COUNT(*), SUM(amount) FROM orders;
둘째는 성능 회귀입니다. 전환 후 느려진 쿼리를 찾아내야 합니다. PostgreSQL의 pg_stat_statements 확장은 어떤 쿼리가 총 시간을 많이 잡아먹는지 집계해 줍니다. 운영 부하를 재현한 환경에서 이 통계를 비교하면, 회귀가 발생한 쿼리를 정확히 짚을 수 있습니다.
-- 느린 쿼리 상위 목록 (총 실행 시간 기준)
SELECT
substr(query, 1, 60) AS q,
calls,
round(total_exec_time::numeric, 1) AS total_ms,
round(mean_exec_time::numeric, 2) AS mean_ms
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 20;
성능 회귀의 흔한 원인은 통계 부족, 인덱스 누락, NUMBER를 numeric으로 옮겨 생긴 느린 산술, 그리고 행 단위 루프를 그대로 옮긴 PL/pgSQL입니다. EXPLAIN ANALYZE로 계획을 직접 들여다보는 습관이 결국 가장 빠른 진단입니다.
단계적 전환 — 한 번에 다 옮기지 않는다
규모가 큰 시스템을 한 번의 빅뱅으로 옮기는 것은 위험합니다. 단계적 전환이 표준입니다. 흔히 쓰는 전략은 셋입니다.
첫째는 읽기 우선 전환입니다. 먼저 읽기 트래픽만 PostgreSQL 복제본으로 돌려 부하와 결과를 검증하고, 충분히 안정되면 쓰기까지 옮깁니다.
둘째는 도메인 단위 분할입니다. 시스템을 업무 도메인으로 쪼개, 결합도가 낮은 도메인부터 하나씩 옮깁니다. 한 도메인을 옮겨 운영하며 방법론을 다듬고, 그 경험을 다음 도메인에 적용합니다.
셋째는 병행 운영과 그림자 비교입니다. 한동안 두 데이터베이스에 같은 쓰기를 흘려 보내고, 읽기 결과를 양쪽에서 받아 비교합니다. 차이가 나면 경보를 울리되, 사용자에게는 기존 시스템의 결과를 돌려줍니다. 충분히 신뢰가 쌓이면 주력을 PostgreSQL로 바꿉니다.
단계적 전환의 타임라인 예시
1단계 스키마/코드 변환 + 테스트 환경 구축
2단계 데이터 초기 적재 + CDC 로 병행 운영 시작
3단계 읽기 트래픽 일부를 PostgreSQL 로 전환 (그림자 비교)
4단계 도메인 단위로 쓰기 트래픽 점진 전환
5단계 Oracle 을 읽기 전용 백업으로 강등, 일정 후 폐기
어느 전략이든 핵심은 되돌릴 수 있어야 한다는 점입니다. 각 단계마다 문제가 생기면 즉시 이전 상태로 돌아갈 길을 미리 만들어 두어야 합니다. 롤백 경로 없는 전환은 도박입니다.
사례 연구 — 결제 정산 시스템 전환
가상의 사례로 흐름을 묶어 봅니다. 어느 결제 정산 시스템이 Oracle 위에서 십 년 넘게 돌고 있었습니다. 테이블 약 400개, PL/SQL 패키지 약 6만 줄, 야간 배치가 정산의 핵심이었습니다. 라이선스 비용 절감과 클라우드 이전이 동기였습니다.
팀은 먼저 ora2pg로 평가 보고서를 뽑았습니다. 스키마는 자동 변환률이 높았지만, 패키지 6만 줄 중 정산 핵심 로직 약 1만 5천 줄이 수동 변환 대상으로 표시됐습니다. 여기서 일정의 대부분이 잡혔습니다.
다음으로 데이터 타입을 정리했습니다. 무분별하게 쓰인 NUMBER를 정수와 numeric으로 구분하고, DATE를 timestamptz로 통일했습니다. 정산 금액 컬럼은 numeric을 유지하되, 식별자와 카운트는 bigint로 좁혔습니다. 이 정리만으로도 야간 배치의 산술 연산이 눈에 띄게 빨라졌습니다.
PL/SQL 변환에서는 행 단위 커서 루프를 집합 기반 SQL로 다시 쓰는 데 집중했습니다. 원래 한 건씩 도는 루프로 두 시간 걸리던 정산 배치를, 집합 연산으로 다시 쓰자 이십 분으로 줄었습니다. 전환이 오히려 성능을 개선한 셈입니다.
데이터는 AWS DMS로 초기 적재 후 CDC로 두 달간 병행 운영했습니다. 매일 밤 양쪽 정산 결과를 그림자 비교해, 차이가 0이 유지되는 것을 확인한 뒤에야 컷오버했습니다. 컷오버 자체는 주말 새벽 한 시간 안에 끝났습니다. 가장 무서운 단계가 가장 짧고 조용하게 지나간 것은, 그 앞의 두 달이 충실했기 때문입니다.
흔한 함정들
전환 프로젝트에서 반복적으로 사람들을 넘어뜨리는 함정을 모았습니다.
첫째, 빈 문자열과 NULL의 차이입니다. Oracle은 빈 문자열을 NULL로 취급하지만, PostgreSQL은 빈 문자열과 NULL을 엄격히 구분합니다. 이 차이는 조건절과 유니크 제약에서 미묘한 버그를 만듭니다.
둘째, 대소문자 식별자입니다. Oracle은 따옴표 없는 식별자를 대문자로 폴딩하고, PostgreSQL은 소문자로 폴딩합니다. 따옴표로 감싼 식별자가 섞여 있으면 양쪽에서 다르게 해석됩니다.
셋째, 묵시적 형 변환입니다. Oracle은 문자와 숫자를 꽤 너그럽게 자동 변환하지만, PostgreSQL은 더 엄격합니다. 숫자 컬럼에 문자열을 비교하던 코드가 전환 후 에러를 냅니다.
넷째, 트랜잭션 격리와 잠금 동작의 차이입니다. 동작이 다르면 동시성 버그가 운영에서만 드물게 터집니다. 부하 테스트로 동시성 경로를 반드시 덮어야 합니다.
다섯째, 정렬 순서입니다. 로케일에 따라 문자열 정렬 결과가 달라집니다. 정렬에 의존하는 페이징이나 비교가 전환 후 다른 순서를 내놓을 수 있습니다.
여섯째, 도구의 자동 변환을 맹신하는 것입니다. 도구는 출발점이지 결승선이 아닙니다. 자동 변환된 코드는 반드시 사람이 읽고, 테스트로 검증해야 합니다.
전환 체크리스트
마지막으로 프로젝트 내내 곁에 두고 점검할 체크리스트를 정리합니다.
- 동기를 숫자로 환산해 의사결정 근거를 남겼는가
- ora2pg 또는 AWS SCT 평가 보고서로 난이도와 일정을 추정했는가
- NUMBER, DATE 등 데이터 타입을 의미에 맞게 정리했는가
- 시퀀스를 이전 후 데이터 최대값으로 동기화했는가
- 빈 문자열과 NULL, 식별자 대소문자, 묵시적 형 변환을 점검했는가
- 옵티마이저 힌트 의존을 통계와 인덱스 설계로 대체했는가
- 행 단위 커서 루프를 집합 기반 SQL로 다시 썼는가
- 애플리케이션의 동적 SQL까지 통합 테스트로 덮었는가
- 데이터 정합성을 행 수와 체크섬으로 양쪽 비교했는가
- pg_stat_statements로 성능 회귀를 추적했는가
- 각 단계마다 롤백 경로를 마련했는가
- 컷오버 전 충분한 기간 병행 운영과 그림자 비교를 했는가
마치며
이기종 DB 엔진 전환은 화려한 신기술 도입이 아니라, 지루하고 꼼꼼한 토목 공사에 가깝습니다. 그러나 그 토목 공사의 끝에는 라이선스 비용의 해방, 클라우드 적합성, 그리고 벤더 락인에서 벗어난 자유가 기다립니다. 핵심은 전환을 다섯 갈래의 별개 작업으로 쪼개고, 도구로 자동화할 수 있는 부분과 사람이 다시 써야 하는 부분을 구분하며, 되돌릴 수 있는 단계로 나누어 나아가는 것입니다.
그리고 한 가지 위안이 있습니다. Oracle에서 PostgreSQL로 가는 길에서 익힌 방법론은, MySQL에서 PostgreSQL로 가든, SQL Server에서 다른 곳으로 가든, 거의 그대로 통합니다. 데이터 타입 매핑, 방언 차이, 코드 변환, 데이터 동기화, 단계적 컷오버라는 골격은 모든 이기종 전환에서 동일하기 때문입니다. 한 번 제대로 해 두면, 그 너머의 전환은 훨씬 덜 두렵습니다.
참고 자료
- PostgreSQL 공식 문서: https://www.postgresql.org/docs/current/
- PL/pgSQL 문서: https://www.postgresql.org/docs/current/plpgsql.html
- ora2pg 공식 사이트: https://ora2pg.darold.net/
- ora2pg GitHub 저장소: https://github.com/darold/ora2pg
- AWS Schema Conversion Tool 사용 설명서: https://docs.aws.amazon.com/SchemaConversionTool/latest/userguide/CHAP_Welcome.html
- AWS Database Migration Service 사용 설명서: https://docs.aws.amazon.com/dms/latest/userguide/Welcome.html
- PostgreSQL CREATE PROCEDURE 문서: https://www.postgresql.org/docs/current/sql-createprocedure.html
- PostgreSQL pg_stat_statements 문서: https://www.postgresql.org/docs/current/pgstatstatements.html
- Oracle Database 공식 문서: https://docs.oracle.com/en/database/
- Flyway 공식 문서: https://flywaydb.org/documentation/
- Liquibase 공식 문서: https://docs.liquibase.com/
현재 단락 (1/239)
데이터베이스 엔진을 바꾸는 일은 원래 흔치 않았습니다. 한 번 Oracle 위에 시스템을 올리면 십수 년을 그 위에서 사는 것이 보통이었고, 누구도 굳이 멀쩡히 도는 엔진을 들어내...