About VATOS
home
Company
home

techlog_14

← Tech Insights

마치며: 데이터가 아니라 의미를 옮기는 일

캐릭터셋부터 통계까지, 이기종 마이그레이션 시리즈 전체를 관통하는 핵심을 정리합니다.

13편을 마쳤습니다. 캐릭터셋부터 통계 정보까지, 데이터베이스 안에 살고 있는 모든 종류의 메타데이터를 살펴봤습니다. 이 글은 13편을 가로지르는 통합 정리이자, 시리즈를 처음 시작할 때 던졌던 질문 — "데이터만 잘 옮기면 되는 것 아닌가요?" — 에 대한 마지막 응답입니다.

13편을 관통하는 한 가지 사실이 있다면, 모든 편이 같은 말을 다른 각도에서 반복했다는 점입니다. DDL은 복사되지만, 그 DDL이 데이터를 통제하던 방식은 복사되지 않습니다. 같은 컬럼 정의가 다른 의미를 만들고, 같은 객체 이름이 다른 모델로 동작하며, 같은 SQL이 다른 계획으로 실행됩니다. 이 사실을 어디서 어떻게 마주치는지가 13편의 내용이었습니다.


13편이 채운 메타데이터의 지도

서론(00)에서 메타데이터를 네 축으로 정의했습니다. 각 편이 그 네 축에 어떻게 매핑되는지, 그리고 4축 분류 바깥에 추가된 한 축(옵티마이저 입력)이 어디에 위치하는지를 정리하면 다음과 같습니다.

메타데이터 축다룬 편핵심 메시지
저장 형태01 캐릭터셋 · 02 VARCHAR · 03 CHAR/NVARCHAR · 04 LOB · 05 날짜형 · 06 숫자형 · 08 저장구조같은 값이 같은 모양으로 저장되지 않는다. 도구의 기본값이 결정을 대신하지 못한다
흐름의 규칙09 제약조건·DEFAULT · 10 시퀀스·식별자 채번규칙은 정의가 아니라 실행 상태로 옮겨진다. 이관 직후 동기화가 마지막 매듭
접근성07 사용자·스키마·권한 · 11 인덱스같은 단어가 다른 구조를 가리키고, 같은 인덱스가 다르게 해석된다
내부 로직12 프로시저·트리거·뷰·MV·Scheduler가장 깊이 박힌 코드가 가장 늦게 옮겨지고 가장 자주 누락된다
(확장) 옵티마이저 입력13 통계 정보같은 SQL이 다르게 해석되는 이유는 통계 격차에 있다

13편(통계)은 4축에 정확히 들어가지 않습니다. 통계는 객체 자체가 아니라 옵티마이저의 입력이라는 별도 위상을 가지기 때문입니다. 인덱스 편(11)에서 옵티마이저가 다르게 생각하는 이유를 비용 모델 차이로 설명했지만, 그 비용 모델의 입력값이 통계입니다. 4축 모두를 정확히 옮겨도, 통계가 비어 있거나 부정확하면 옵티마이저는 잘못된 입력으로 계획을 만듭니다. 그래서 시리즈 마지막에 별도의 축으로 위치를 부여했습니다.


시리즈를 가로지르는 세 가지 공통 메시지

13편이 다룬 영역은 서로 다르지만, 글을 가로지르는 세 가지 메시지는 같습니다.

첫째, 도구의 기본값은 우리의 결정이 아닙니다.

자동 변환 도구는 합리적인 기본값을 적용할 뿐, 우리 시스템의 의도를 알지 못합니다. 숫자형 편(06)에서 본 NUMBER → NUMERIC(38,10)의 표현 왜곡, 캐릭터셋 편(01)의 묵음 변형(Silent Corruption), LOB 편(04)의 단순 타입 치환, 시퀀스 편(10)의 동기화 누락 — 이 모든 사고가 "도구가 결정했고 우리는 검토하지 않았다"의 결과입니다. Oracle의 모호함(precision 없는 NUMBER, USER ≡ SCHEMA의 일체성)은 오픈소스로 넘어오는 순간 누군가가 명시적으로 결정해야 하는 공백이 됩니다. 그 공백을 도구가 채우면, 스키마의 결정이 우리의 결정이 아니게 됩니다.

둘째, 1:1 매핑은 환상입니다.

같은 단어가 다른 구조를 가리킵니다. Oracle USER ≡ SCHEMA, PostgreSQL의 ROLE / DATABASE / SCHEMA 분리, MySQL의 DATABASE ≡ SCHEMA — 모두 "사용자"라는 한 개념을 다르게 쪼개고 묶은 결과입니다. 같은 옵션이 다른 기본값을 갖습니다. 시퀀스 CACHE 기본값 20 대 1, MySQL innodb_autoinc_lock_mode 5.7의 1 대 8.0의 2. 같은 객체가 다른 모델로 동작합니다. 트리거 모델, 머티리얼라이즈드 뷰의 refresh 정책, FK 검증 시점, NULL의 인덱스 저장 여부. 같은 함수가 다른 시점에 평가됩니다. PostgreSQL now()의 트랜잭션 시작 시점 대 Oracle SYSDATE의 호출 시점. 매핑은 항상 의도의 번역이고, 번역에는 정보 손실과 재해석이 따릅니다.

셋째, 이관은 정리의 기회입니다.

트리거 기반 채번을 IDENTITY로 정리할 시점, 흩어진 USER별 GRANT를 ROLE로 통합할 시점, MAX(id)+1 동시성 사고를 시퀀스로 교체할 시점, NOT NULL을 통과시키려고 넣어둔 더미값을 진짜 NULL로 정리할 시점, PK 없는 테이블에 PK를 정의할 시점, DB 안의 비즈니스 로직 중 "정말 DB 안에 있어야 하는가"를 다시 묻는 시점. 이관은 어차피 코드를 손대는 시점이므로, 옮기는 비용과 정리하는 비용의 차이가 가장 작은 시점입니다. 이 시점에 손대지 않은 레거시는 새 환경에서도 그대로 살아남고, 새 환경의 특성과 충돌해 더 큰 문제가 됩니다.


양방향성 — 한 방향 가이드는 절반의 진실

13편 모두가 양방향, 즉 Oracle → 오픈소스와 오픈소스 → Oracle 모두를 다뤘습니다. 이는 단순한 균형 잡기가 아니라 실무의 현실을 반영합니다.

오픈소스로의 이관은 흔히 일방향으로 인식되지만, 멀티 DB 운영 시대에는 양방향이 동시에 일어납니다. 한 시스템은 Oracle에서 PostgreSQL로, 다른 시스템은 클라우드 매니지드 PostgreSQL에서 Oracle로, 또 다른 시스템은 MySQL에서 PostgreSQL로 옮깁니다. 같은 조직 안에서, 같은 시점에. 이런 환경에서 한 방향만 다루는 가이드는 절반의 진실입니다.

방향이 달라지면 사고의 패턴도 달라집니다.

  • Oracle → 오픈소스: Oracle의 풍부한 메타데이터(패키지, 자율 트랜잭션, MV Fast Refresh, Bitmap Index, Plan Baseline, SecureFile DEDUPLICATION)가 단순 모델로 흡수됩니다. 무엇이 사라지고, 그 의도를 어떻게 우회할지가 핵심 결정입니다. 사라진 것을 그대로 두고 옮기면 운영 중에 의미가 빠진 코드가 발견됩니다.
  • 오픈소스 → Oracle: 단순한 모델이 Oracle의 풍부한 옵션을 만납니다. 무엇을 새로 설계할 기회로 삼을지가 핵심 결정입니다. 1:1로만 옮기면 Oracle 환경의 이점을 활용하지 못한 채 단순 모델이 그대로 굳어집니다.

방향에 따라 결정의 책임 위치가 다릅니다. 전자는 "잃지 않는 책임", 후자는 "새로 설계할 권한"입니다.


메타데이터의 본질 — 의미는 정의가 아니라 동작에 있다

13편을 관통하는 또 하나의 인식이 있습니다. 메타데이터의 의미는 DDL 정의가 아니라 데이터·SQL·운영 절차의 결합 안에 있습니다.

DDL은 정적입니다. VARCHAR2(100)이라는 정의는 두 환경에서 같은 글자로 보이지만, 의미는 캐릭터셋·길이 단위·NLS 설정의 결합 안에서 결정됩니다. FOREIGN KEY라는 정의는 ENABLE 상태인지, VALIDATE인지, DEFERRABLE인지, ON DELETE 액션이 무엇인지의 결합 안에서 의미를 갖습니다. CREATE INDEX라는 정의는 옵티마이저가 그 인덱스를 선택하는지, 통계가 정확한지, 비용 모델이 적절한지의 결합 안에서만 살아 있습니다.

따라서 메타데이터 이관은 DDL을 옮기는 작업이 아니라 데이터·SQL·운영 절차의 결합 전체를 옮기는 작업입니다. 그리고 이 작업의 표준 절차는 어느 편에서나 같았습니다 — 측정 → 결정 → 검증.

  • 측정: 정의가 아니라 실제 데이터를 본다. 컬럼이 실제로 어떤 길이로 쓰이는지, 시퀀스의 현재값이 무엇인지, 인덱스가 실제로 선택되는지, LOB 컬럼이 정말 LOB이어야 하는지.
  • 결정: 측정 결과를 근거로 결정한다. 도구의 기본값에 맡기지 않는다. 결정의 근거를 문서화한다.
  • 검증: 결정이 실제로 의도대로 동작하는지 확인한다. 데이터·SQL·운영 절차 모두에서.

이 세 단계는 어떤 편에서든 같은 형태로 반복되었습니다. 메타데이터 변환의 본질은 이 반복 안에 있습니다.


통합 체크리스트 — 단계별로 무엇을 점검할 것인가

13편의 결론에 흩어져 있던 질문들을 이관 단계별로 묶었습니다. 각 항목 옆 괄호는 자세한 논의가 있는 편입니다.

실사 단계 — 무엇을 측정하는가

  • 문자형 컬럼의 실제 최대 길이·문자 분포 (01·02·03)
  • LOB 컬럼이 실제로 얼마나 크고 어떻게 쓰이는지 (04)
  • 시간 컬럼이 타임존에 의존하는지 (05)
  • 숫자 컬럼의 실제 최댓값·최솟값·소수 사용 (06)
  • USER가 실제로 어떤 역할(접속·스키마·권한 묶음·사용 중단)인지 (07)
  • TABLESPACE 분리가 어떤 의도(공간 통제·백업 단위·수명주기)였는지 (08)
  • CHECK·FK가 실제로 ENABLE / VALIDATE 상태였는지 (09)
  • 채번 방식(시퀀스·IDENTITY·MAX(id)+1·UUID)이 무엇인지 (10)
  • PK 없는 테이블과 비효율 인덱스가 어디인지 (11)
  • 내부 로직 안에 숨은 의존성(DBMS_xxx·UTL_HTTP·DB Link·딕셔너리 참조)은 어디인지 (12)

설계 단계 — 무엇을 결정하는가

  • 캐릭터셋과 collation, OS·애플리케이션까지의 통일 (01)
  • 길이 정의 단위(BYTE vs CHAR)와 재산정 결과 (02·03)
  • LOB 타입 결정(VARCHAR2·Extended VARCHAR2·CLOB·SecureFile 옵션) (04)
  • 날짜형 타입과 타임존 처리 정책 (05)
  • 숫자형 타입(정수 범위·UNSIGNED·NUMERIC 정밀도) (06)
  • USER 분류와 매핑 단위(단일 DB / 다중 SCHEMA vs 다중 DATABASE) (07)
  • TABLESPACE·파티션·압축·LOB 저장 정책 (08)
  • DEFAULT·CHECK·FK·DEFERRABLE 동작과 UNIQUE+NULL 경계 (09)
  • 채번 방식의 일관 결정(시퀀스·IDENTITY·UUID), MAX+1 정리 (10)
  • PK 전략, 인덱스 분리·통합, 특수 인덱스 대체 (11)
  • 내부 로직의 DB 잔류 여부와 패키지·자율 트랜잭션·예외 모델 재설계 (12)
  • 통계 자동 수집·확장 통계·시스템 통계 정책 (13)

적재 단계 — 무엇을 동기화하는가

  • 시퀀스의 NEXTVAL을 max(PK) + 마진으로 일괄 재설정 (10)
  • IDENTITY 컬럼을 GENERATED BY DEFAULT로 두고 적재 (10·12)
  • 통계를 일괄 수집(DBMS_STATS.GATHER_SCHEMA_STATS / ANALYZE / ANALYZE TABLE) (13)
  • 확장 통계·히스토그램 명시적 재생성 (13)
  • PROCEDURE / FUNCTION / TRIGGER / PACKAGE의 의존성 순서 재컴파일 (utlrp.sql 격) (12)

검증 단계 — 무엇을 확인하는가

  • 멀티바이트 문자의 전수 검증, Source 무결성까지 (01)
  • 길이 초과·잘림·중복 충돌 (02·03)
  • LOB의 JDBC / ORM / Connection Pool 동작 (04)
  • 타임존이 다른 환경에서의 날짜 비교 (05)
  • 집계·반올림·정산 결과의 합계 일치 (06)
  • 권한·search_path·딕셔너리 의존 운영 스크립트 (07)
  • I/O 분산·파티션 정합성·LOB 저장량 (08)
  • 빈 문자열 ↔ NULL 변환으로 인한 의도하지 않은 중복 (09·11)
  • 모든 채번 컬럼에서 NEXTVAL > max(PK) (10)
  • 인덱스가 실제로 선택되는지 (실행 계획 확인) (11)
  • 예외 분기·SELECT INTO 동작 단위 테스트 (12)
  • 옵티마이저 비용 파라미터가 환경에 맞는지 (13)

운영 단계 — 무엇을 모니터링하는가

  • 운영 자동화 스크립트(딕셔너리 의존)의 작동 여부 (07·12)
  • 인덱스 사용 모니터링 (DBA_INDEX_USAGE / pg_stat_user_indexes / sys.schema_unused_indexes) (11)
  • Plan regression 모니터링 (pg_stat_statements, Performance Schema) (13)
  • LOB·압축 테이블의 디스크 사용량 변화 (08)
  • 자동 통계 수집의 작동 여부와 변경 적은 테이블의 통계 신선도 (13)

마치며

데이터는 옮길 수 있습니다. 하지만 의미까지 함께 옮길 수 있을지는 준비에 달려 있습니다. 서론(00)의 마지막 문장이었고, 13편을 마치는 자리에도 같은 문장이 답으로 남습니다.

13편은 그 준비의 지도였습니다. 어느 층에서 무엇을 점검해야 하는지, 어느 방향으로 옮길 때 어떤 결정이 따라오는지, 도구가 채워주지 않는 공백이 어디인지를 한 편 한 편 짚었습니다. 모든 메타데이터의 모든 함정을 다루지는 못했지만, 어떤 시각으로 메타데이터를 봐야 하는지는 충분히 전달되었을 것이라 믿습니다.

마지막으로 한 가지를 덧붙입니다. 이 시리즈는 "이관은 어렵다"를 강조하는 데 목적이 있지 않습니다. 이관이 어렵다는 사실을 직시한 사람만이 준비를 시작할 수 있고, 준비된 이관만이 의미까지 옮길 수 있다는 것을 말하고 싶었습니다. 모호함을 명확함으로 바꾸는 작업, 도구의 기본값을 우리의 결정으로 되찾는 작업, DDL을 의미로 채우는 작업 — 이 작업의 가치는 이관이 끝난 시스템의 첫 사용자가 "달라진 게 없네요"라고 말할 때 비로소 증명됩니다.

그 한 문장을 듣기 위한 시리즈였습니다.

← 목록으로 돌아가기