스키마 하나를 통째로 다른 서버로 옮겨달라는 요청은 DBA라면 주기적으로 받는다. 개발 DB를 스테이징으로 복제하거나, 낡은 장비의 스키마를 신규 인스턴스로 넘길 때다. 예전엔 exp/imp로 하던 걸 요즘은 Data Pump(expdp/impdp)로 처리하는데, 막상 돌려보면 디렉터리 권한이나 스키마 이름 매핑에서 자주 걸린다. 이 글에서는 실무에서 바로 따라 할 수 있게 순서대로 정리한다.
결론부터 말하자면, 같은 스키마명으로 옮길 땐 expdp로 덤프를 뜬 뒤 impdp의 REMAP_SCHEMA로 대상 스키마에 적재하고, 두 DB가 네트워크로 연결돼 있으면 NETWORK_LINK로 덤프파일 없이 직접 이관하는 것이 빠르다. 대용량은 PARALLEL로 워커를 늘려 시간을 줄인다.
1. Data Pump가 무엇이고 exp/imp와 무엇이 다른가
Data Pump는 Oracle 서버가 직접 데이터를 읽고 쓰는 이관 도구다. 구식 exp/imp는 클라이언트가 데이터를 끌어와 파일로 만들었지만, Data Pump는 서버 프로세스가 서버의 디렉터리 객체에 덤프파일을 쓴다. 그래서 덤프파일은 클라이언트 PC가 아니라 DB 서버 파일시스템에 생긴다. 이 차이를 모르면 "덤프파일이 어디 갔지"에서 첫 번째로 막힌다.
expdp가 익스포트, impdp가 임포트다. 두 도구 모두 DIRECTORY라는 오라클 디렉터리 객체를 통해서만 파일에 접근한다.
2. 환경과 사전 준비
기준 버전은 Oracle Database 19c다. 소스와 대상이 서로 다른 인스턴스라고 가정한다. 먼저 버전을 확인한다.
$ sqlplus -s / as sysdba <<'EOF' set heading off feedback off select banner_full from v$version; EOF Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production Version 19.20.0.0.0
이관에 앞서 디렉터리 객체를 만들고 권한을 준다. 물리 경로는 oracle OS 계정이 읽고 쓸 수 있어야 한다.
SQL> CREATE OR REPLACE DIRECTORY dpump_dir AS '/u01/dpump'; Directory created. SQL> GRANT READ, WRITE ON DIRECTORY dpump_dir TO system; Grant succeeded.
# OS 레벨에서 실제 경로 권한 확인 $ ls -ld /u01/dpump drwxr-xr-x 2 oracle oinstall 4096 Sep 28 10:12 /u01/dpump
여기서 한 번 걸린다. 디렉터리 물리 경로의 소유자가 oracle이 아니면 익스포트 중에 파일 생성이 막힌다. 이 경우 뒤에서 다룰 ORA-39002가 뜬다.
3. expdp로 스키마 덤프 만들기
소스 스키마명이 HR이라고 하자. 스키마 단위 익스포트는 SCHEMAS 파라미터를 쓴다.
$ expdp system/****@SRCDB \
DIRECTORY=dpump_dir \
DUMPFILE=hr_%U.dmp \
LOGFILE=hr_exp.log \
SCHEMAS=HR \
PARALLEL=4
DUMPFILE의 %U는 워커 수만큼 파일을 분할 생성하는 치환 변수다. PARALLEL=4와 함께 쓰면 hr_01.dmp부터 hr_04.dmp까지 병렬로 쓴다. 파일이 하나뿐이면 병렬 효과가 없으니 %U는 사실상 필수다.
Export: Release 19.0.0.0.0 - Production Starting "SYSTEM"."SYS_EXPORT_SCHEMA_01": system/********@SRCDB DIRECTORY=dpump_dir ... Processing object type SCHEMA_EXPORT/TABLE/TABLE_DATA . . exported "HR"."EMPLOYEES" 17.08 KB 107 rows . . exported "HR"."DEPARTMENTS" 7.015 KB 27 rows Master table "SYSTEM"."SYS_EXPORT_SCHEMA_01" successfully loaded/unloaded Dump file set for SYSTEM.SYS_EXPORT_SCHEMA_01 is: /u01/dpump/hr_01.dmp /u01/dpump/hr_02.dmp Job "SYSTEM"."SYS_EXPORT_SCHEMA_01" successfully completed
PARALLEL 값은 무작정 올리지 않는다. CPU 코어 수와 I/O 대역폭이 받쳐줘야 효과가 난다. 현장에선 코어 수 이하로 시작해서 로그의 소요 시간을 보고 조정한다. 데이터가 작은 스키마에 PARALLEL=16을 줘도 빨라지지 않는다.
4. impdp REMAP_SCHEMA로 대상에 적재하기
만든 덤프파일을 대상 서버로 옮긴 뒤(scp 등), 대상 인스턴스의 디렉터리 객체 경로에 둔다. 대상 스키마명을 HR_NEW로 바꿔 넣는다면 REMAP_SCHEMA를 쓴다.
$ impdp system/****@TGTDB \
DIRECTORY=dpump_dir \
DUMPFILE=hr_%U.dmp \
LOGFILE=hr_imp.log \
REMAP_SCHEMA=HR:HR_NEW \
PARALLEL=4
REMAP_SCHEMA=소스:대상 형식이다. 소스 스키마의 모든 객체가 대상 스키마로 재매핑돼 적재된다. 대상 스키마 계정이 없으면 impdp가 생성을 시도하지만, 표준 방식은 대상 계정과 테이블스페이스를 먼저 만들어 두는 것이다. 계정 생성 권한이나 쿼터 문제로 실패하는 경우를 피할 수 있다.
-- 대상 DB에서 미리 준비 SQL> CREATE USER hr_new IDENTIFIED BY **** 2 DEFAULT TABLESPACE users QUOTA UNLIMITED ON users; User created. SQL> GRANT CONNECT, RESOURCE TO hr_new; Grant succeeded.
테이블스페이스 이름까지 다르면 REMAP_TABLESPACE=소스TS:대상TS를 함께 준다.
TABLE_EXISTS_ACTION 기본 동작
대상에 같은 이름의 테이블이 이미 있으면 impdp의 TABLE_EXISTS_ACTION이 동작을 결정한다. 값은 네 가지다.
- SKIP: 해당 테이블을 건너뛴다. 기본값이다.
- APPEND: 기존 데이터를 두고 행을 추가한다.
- TRUNCATE: 기존 행을 지우고 새로 넣는다.
- REPLACE: 테이블을 drop 후 재생성한다.
주의할 점이 하나 있다. 기본값은 SKIP이지만, CONTENT=DATA_ONLY를 지정하면 기본값이 APPEND로 바뀐다. 데이터만 넣는 재적재에서 "왜 SKIP이 안 되고 행이 중복됐지"라고 당황하는 경우가 여기서 나온다. 데이터만 새로 갈아끼우려면 CONTENT=DATA_ONLY TABLE_EXISTS_ACTION=TRUNCATE처럼 명시하는 편이 명확하다.
5. NETWORK_LINK로 덤프파일 없이 직접 이관
소스와 대상이 네트워크로 닿으면 덤프파일을 만들지 않고 바로 옮길 수 있다. 대상 DB에서 소스를 가리키는 데이터베이스 링크를 만든 뒤, impdp에 NETWORK_LINK를 지정한다. expdp 단계 자체가 사라진다.
-- 대상 DB에서 소스로 향하는 DB 링크 생성
SQL> CREATE DATABASE LINK src_link
2 CONNECT TO system IDENTIFIED BY ****
3 USING '//src-host:1521/SRCDB';
Database link created.
SQL> SELECT 1 FROM dual@src_link;
1
----------
1
$ impdp system/****@TGTDB \
NETWORK_LINK=src_link \
SCHEMAS=HR \
REMAP_SCHEMA=HR:HR_NEW \
LOGFILE=dpump_dir:hr_net.log \
PARALLEL=4
이 방식은 데이터가 소스에서 대상으로 곧장 흘러가므로 중간 파일과 scp 단계가 필요 없다. 디스크 여유가 빠듯하거나 이관을 자동화할 때 특히 편하다.
과거 자료에는 "NETWORK_LINK는 LONG, LONG RAW 컬럼을 옮기지 못한다"는 제약이 자주 등장한다. 이 제약은 12.1 이하에만 해당한다. Oracle 12c Release 2(12.2)부터 NETWORK_LINK 임포트가 LONG 컬럼을 지원하므로, 이 글의 기준인 19c에서는 그 제약이 없다. LONG 컬럼 때문에 network 방식을 피하고 테이블을 따로 옮길 필요는 없다.
다만 network 임포트에 남아 있는 다른 제약은 있다. 예를 들어 소스에서 읽으며 넣는 구조라 파일 기반보다 특정 조건에서 느릴 수 있고, 링크 계정에 소스 객체를 읽을 권한이 있어야 한다. 대용량 스키마는 파일 방식과 소요 시간을 비교해 결정한다.
6. 이관 후 검증
적재가 끝나면 impdp 로그의 마지막 줄에 성공 여부가 찍힌다. 그것만 믿지 말고 객체 수를 대조한다.
-- 대상 DB에서 객체 종류별 개수 확인 SQL> SELECT object_type, COUNT(*) 2 FROM dba_objects WHERE owner='HR_NEW' 3 GROUP BY object_type ORDER BY object_type; OBJECT_TYPE COUNT(*) --------------------- ---------- INDEX 19 SEQUENCE 3 TABLE 7 VIEW 1
무효 객체가 없는지도 본다. 컴파일이 깨진 프로시저나 뷰가 남을 수 있다.
SQL> SELECT object_name, object_type FROM dba_objects 2 WHERE owner='HR_NEW' AND status='INVALID'; no rows selected
무효 객체가 나오면 UTL_RECOMP.RECOMP_SERIAL('HR_NEW')로 재컴파일하거나 개별 ALTER ... COMPILE로 원인을 확인한다.
7. 흔한 ORA 오류와 대처
ORA-39002, ORA-39070: 디렉터리 접근 실패
가장 자주 만나는 조합이다. 디렉터리 객체가 없거나, 물리 경로에 oracle OS 계정의 쓰기 권한이 없을 때 뜬다.
ORA-39002: invalid operation ORA-39070: Unable to open the log file. ORA-29283: invalid file operation ORA-06512: at "SYS.UTL_FILE", line 536
디렉터리 객체 경로를 확인하고, OS에서 그 경로의 소유자와 권한을 손본다.
SQL> SELECT directory_name, directory_path FROM dba_directories 2 WHERE directory_name='DPUMP_DIR'; $ sudo chown oracle:oinstall /u01/dpump $ chmod 750 /u01/dpump
ORA-31626, ORA-31633: 마스터 테이블 생성 실패
Data Pump 잡은 진행 상태를 마스터 테이블에 기록한다. 이전 잡이 비정상 종료되면 같은 이름의 마스터 테이블이 남아 새 잡이 충돌한다.
ORA-31626: job does not exist ORA-31633: unable to create master table "SYSTEM.SYS_IMPORT_SCHEMA_01" ORA-06512: at "SYS.KUPV$FT", line 1163 ORA-00955: name is already used by an existing object
남아 있는 마스터 테이블을 지우거나, JOB_NAME을 명시해 이름 충돌을 피한다.
SQL> SELECT job_name, state FROM dba_datapump_jobs WHERE owner_name='SYSTEM'; SQL> DROP TABLE system.sys_import_schema_01 PURGE;
ORA-39082: 무효 상태로 생성된 객체
이건 오류라기보다 경고에 가깝다. 참조 대상이 아직 안 만들어진 상태에서 프로시저나 뷰가 컴파일돼 무효로 생성됐다는 뜻이다.
ORA-39082: Object type PROCEDURE:"HR_NEW"."ADD_JOB_HISTORY" created with compilation warnings
대개 임포트 완료 후 재컴파일하면 유효로 돌아온다. 6번의 무효 객체 조회로 확인하고 재컴파일한다. 재컴파일 후에도 무효면 실제 누락된 의존 객체나 권한 문제이므로 그때 파고든다.
ORA-01950: 테이블스페이스 쿼터 없음
대상 스키마 계정에 테이블스페이스 쿼터가 없으면 데이터 적재 중에 멈춘다.
ORA-01950: no privileges on tablespace 'USERS'
대상 계정에 쿼터를 준다. 4번에서 계정을 미리 만들며 QUOTA UNLIMITED를 준 이유가 이것이다.
SQL> ALTER USER hr_new QUOTA UNLIMITED ON users; User altered.
8. 마무리
같은 스키마명이면 expdp/impdp 파일 방식으로, 스키마명이나 테이블스페이스가 바뀌면 REMAP_SCHEMA와 REMAP_TABLESPACE로 매핑한다. 두 DB가 네트워크로 닿고 디스크를 아끼고 싶으면 NETWORK_LINK로 파일 없이 직접 옮긴다. 19c에서는 LONG 컬럼도 network 방식으로 함께 넘어간다. 대용량은 PARALLEL과 %U를 짝지어 시간을 줄인다. 이관 자체보다 디렉터리 권한, 대상 계정 쿼터, 무효 객체 검증에서 시간을 더 쓰게 되니 앞 단계에서 미리 손봐 두는 것이 실무에서 사고를 줄인다.