DBMS 1

DA, SQL, DB보안 등 실무자를 위한 위한 DB기술 바이블!

파티셔닝

DBMS 1
Oracle 가이드
11g, DBA를 위한 신기능
파티셔닝
작성자
dataonair
작성일
2021-02-17 16:47
조회
1742

완벽에 가까운 파티셔닝 기능의 구현

간략한 개요

분할하여 정복하라(Divide and conquer)"오라클 데이터베이스의 파티셔닝 기능에 적용된 원칙을 이보다 분명하게 설명해 주는 문장은 없을 것입니다. 오라클은 버전 8부터 테이블 또는 인덱스를 여러 개의 세그먼트로 파티셔닝하고 각각 다른 테이블스페이스에 위치시킬 수 있는 기능을 제공해 왔습니다. 파티셔닝된 테이블은 하나의 논리적 엔티티로 간주되지만 각각의 파티션은 별도의 세그먼트로 저장되므로 데이터의 처리를 한층 쉽게 해 줍니다.

버전 11에는 레퍼런스(reference) 파티셔닝, 인터벌(interval) 파티셔닝, 가상 컬럼의 파티셔닝, 확장형 컴포지트(extended composite) 파티셔닝 등의 옵션이 새로 추가되면서 파티셔닝 설계의 가능성과 관리성이 대폭적으로 향상되었습니다.

파티셔닝의 기본 개념과 파티셔닝 컬럼 또는 파티셔닝 방법을 결정하기 위한 고려 사항에 대해 알고 싶으시다면 오라클 매거진 2006년 9/10월호에 필자가 기고한 아티클을 먼저 참고하시기 바랍니다.

확장형 컴포지트(Extended Composite) 파티셔닝

Oracle8i Database에서 처음 소개된 컴포지트 파티셔닝을 이용하면 파티션에 서브파티션을 생성하고 테이블의 구조를 더욱 세분화할 수 있습니다. 하지만 8i 버전에서 서브파티션의 range 파티셔닝은 해시 서브파티셔닝을 통해서만 가능했습니다. Oracle9i에서는 컴포지트 파티셔닝에 range-list 서브파티셔닝이 포함되었습니다.

이러한 서브파티셔닝 기법은 대부분의 요구 사항을 만족하지만 경우에 따라서는 그렇지 못할 수도 있습니다. 예를 들어, 여러 개의 컬럼을 포함하는 SALES 테이블에 파티셔닝 대상이 될 수 있는 두 개의 컬럼으로 매출이 발생한 주의 2 자리 수 코드를 저장하는 state_code(sales tax의 계산을 위해 사용됩니다)와 판매된 제품의 3 가지 수 코드를 저장하는 product_code가 있다고 가정해 봅시다. 사용자는 두 가지 컬럼을 골고루 사용하여 쿼리를 실행하고 있습니다. 아카이브 요구 사항 역시 이 두 개의 컬럼을 기준으로 하고 있습니다. 파티셔닝 원칙을 기준으로 고려할 때, 이 두 가지 컬럼은 모두 파티셔닝 키를 위한 훌륭한 후보의 조건을 갖추고 있습니다.

Oracle Database 11g에서는 이러한 문제를 매우 쉽게 해결할 수 있습니다. 새로운 버전에서는 rang-hash, range-list 컴포지트 파티셔닝 이외에도 다양한 대안이 제공되고 있기 때문입니다. 선택의 폭은 사실상 무한합니다. 어떤 형태의 조합으로든 컴포지트 파티션을 구현할 수 있습니다.

위에서 예로 든 상황에서는 product_code를 기준으로 테이블의 LIST 파티셔닝을 수행하는 방법이 바람직할 수 있습니다. 이 컬럼이 state_code에 비해 더 많은 수의 유니크 값을 갖고 있기 때문입니다. 구현된 코드의 예가 아래와 같습니다:

create table sales
(
sales_id number,
product_code number,
state_code varchar2(2)
)
partition by list (product_code)
subpartition by list (state_code)
(
partition p101 values (101)
(
subpartition p101_ct values ('CT'),
subpartition p101_ny values ('NY'),
subpartition p101_def values (default)
),
partition p201 values (201)
(
subpartition p201_ct values ('CT'),
subpartition p201_ny values ('NY'),
subpartition p201_def values (default)
)
)

하지만 이것 말고도 다른 대안이 여러 가지 있습니다. LIST-RANGE 컴포지트 파티션을 생성하는 것도 가능합니다. 위의 예에서 product_code가 유니크 값의 수는 적은 반면 더 넓은 영역을 갖고 있다고 가정해 봅시다. 이런 경우라면 state_code를 기준으로 LIST 파티션을 생성하고 다시 product_code를 기준으로 서브파티션을 생성하는 것이 좋을 것입니다. 구현된 코드의 예가 아래와 같습니다.

create table sales1
(
sales_id number,
product_code number,
state_code varchar2(2)
)
partition by list (state_code)
subpartition by range (product_code)
(
partition CT values ('CT')
(
subpartition ct_100 values less than (101),
subpartition ct_200 values less than (201)
),
partition NY values ('NY')
(
subpartition NY_100 values less than (101),
subpartition NY_200 values less than (201)
)
)

range-range 컴포지트 서브파티션을 생성하는 것도 가능합니다. 이 테크닉은 테이블이 두 개의 date 필드를 갖는 경우 매우 유용합니다. 예를 들어 세일즈 프로세싱 시스템을 위한 어떤 테이블에 결제일과 배송일 정보가 함께 저장되어 있는 경우를 생각해 봅시다. 이런 경우라면 두 개의 date 컬럼 중 하나를 기준으로 range 파티션을 생성하고 다른 date 컬럼으로 range 서브 파티션을 생성할 수 있습니다. 이 테크닉을 이용하면 날짜를 기준으로 한 백업, 아카이빙, 삭제 작업의 성능을 개선할 수 있습니다.

요약하자면, Oracle Database 11g은 다음과 같은 유형의 컴포지트 파티션을 지원합니다:

  • Range-range
  • Range-hash
  • Range-list
  • List-range
  • List-hash
  • List-list
레퍼런스(Reference) 파티셔닝

파티셔닝 구조를 설계하면서 자주 부딪히게 되는 문제로, 파티션의 기준이 되어야 하는 컬럼이 모든 테이블에 공통적으로 포함되지 않은 경우를 들 수 있습니다. sales, customer의 두 가지 테이블을 사용해서 세일즈 시스템을 구현한다고 가정해 봅시다:

create table customers
(
cust_id number primary key,
cust_name varchar2(200),
rating varchar2(1) not null
)
partition by list (rating)
(
partition pA values ('A'),
partition pB values ('B')
);

sales 테이블은 아래와 같이 구현되었습니다. 이 테이블은 customer 테이블의 자식(child) 테이블입니er primary key,
cust_id number not null,
sales_amt number,
constraint fk_sales_01
foreign key (cust_id)
references customers
);

가능하다면, sales 테이블에 customers 테이블과 동일한 파티셔닝 방식, 즉 rating 컬럼을 기준으로 한 list 파티셔닝을 적용하는 것이 가장 이상적일 것입니다. 하지만 한 가지 심각한 문제가 있습니다. sales 테이블에는 rating이라는 이름의 컬럼이 없습니다! 그렇다면 존재하지도 않는 컬럼을 기준으로 어떻게 파티션을 만들 수 있을까요

Oracle Database 11g이 제공하는 레퍼런스 파티셔닝(Reference Partitioning)이라는 새로운 기능을 이용하면 가능합니다. 레퍼런스 파티셔닝을 sales 테이블에 적용한 예가 아래와 같습니다.

create table sales
(
sales_id number primary key,
cust_id number not null,
sales_amt number,
constraint fk_sales_01
foreign key (cust_id)
references customers
)
partition by reference (fk_sales_01);

이렇게 하여 부모 테이블인 customer 테이블과 동일한 파티션이 생성되었습니다. 위에서 rating이라는 컬럼이 존재하지 않음에도 불구하고 이 컬럼을 기준으로 테이블이 파티셔닝 되었음을 주목하시기 바랍니다. "partition by reference (fk_sales_01)" 구문의 파티션 정의는 외래 키를 포함하고 있습니다. 이 구문은 Oracle Database 11g에서 부모 테이블, 즉 customers 테이블에서 사용된 파티셔닝 방법을 사용하도록 정의하고 있습니다. 컬럼 cust_id에 NOT NULL 제약 조건이 적용되어 있음을 참고하십시오. 이 제약 조건은 레퍼런스 파티셔닝의 기본 요구 사항입니다.

sales 테이블에 정의된 파티션의 파티션 바운더리(partition boundary)를 조회한 결과가 아래와 같습니다.

partition boundary조회 결과

"high value"가 NULL로 표시되는 것은 바운더리를 부모 테이블로부터 가져 옴을 의미합니다. 이 파티션은 부모 테이블과 동일한 이름을 갖습니다. 파티셔닝 타입을 조회하려면 user_part_tables를 쿼리하면 됩니다. ref_ptn_constraint_name이라는 이름의 컬럼에 외래 키 제약 조건의 이름이 표시됩니다.

컬럼에 외래 키 제약 조건의 이름이 표시

레퍼런스 파티션은 자식 테이블을 부모 테이블과 동일한 방법으로 파티셔닝 하려 하지만 두 테이블이 동일한 컬럼을 포함하고 있지 않은 경우 매우 유용하게 활용됩니다. 또, 각각의 자식 테이블에 장황한 파티셔닝 정의 구문을 명시적으로 선언할 필요가 없다는 이점이 있습니다.

인터벌(Interval) 파티셔닝

Range 파티셔닝을 이용하면 파티션 키 컬럼의 값 영역을 기준으로 파티션을 생성할 수 있습니다. Range 파티셔닝이 적용된 테이블의 예가 아래와 같습니다.

create table sales6
(
sales_id number,
sales_dt date
)
partition by range (sales_dt)
(
partition p0701 values less than (to_date('2007-02-01','yyyy-mm-dd')),
partition p0702 values less than (to_date('2007-03-01','yyyy-mm-dd'))
);

여기에는 January 2007, February 2007을 위한 파티션만이 정의되어 있습니다. 그렇다면 sales_dt 컬럼에 March 2007의 값을 갖는 레코드가 추가되면 어떻게 될까요 INSERT 작업은 아래와 같은 에러와 함께 실패합니다:

ORA-14400: inserted partition key does not map to any partition

따라서 레코드를 삽입하기 전에 March 2007을 위한 파티션을 추가해 주어야 합니다. 하지만 실제 환경에서는 이런 작업이 무척 번거로울 수 있습니다. 때로는 지나치게 많은 파티션을 미리 만들어 놓을 수도 있고, 또 파티션을 너무 적게 만들어 에러가 발생할 수도 있습니다.

오라클이 새로운 파티션의 필요성을 자동으로 감지해서 파티션을 생성해 준다면 좋지 않을까요 Oracle Database 11g이 제공하는 인터벌 파티셔닝(Interval Partitioning) 기능을 이용하면 가능합니다. 인터벌 파티셔닝에서는 파티션과 그 바운더리를 정의하는 대신 각 파티션의 바운더리를 정의하는 인터벌(interval)만이 정의됩니다. 인터벌 파티셔닝의 예가 아래와 같습니다:

create table sales6
(
sales_id number,
sales_dt date
)
partition by range (sales_dt)
interval (numtoyminterval(1,'MONTH'))
(
partition p0701 values less than (to_date('2007-02-01','yyyy-mm-dd'))
);

여기서 굵은 글씨체로 표시된 interval 정의 구문을 주목하십시오. 위에서는 1 개월의 간격으로 오라고 있습니다. 또 January 2007 데이터를 위해 p0701이라는 이름의 파티션을 생성하였습니다. 이제 June 2007 데이터를 새로 INSERT한다고 가정해 봅시다:

SQL> insert into sales6 values (1,'01-jun-07');
1 row created.

오라클이 에러를 반환하지 않았습니다. 구문이 성공적으로 실행된 것입니다. 그렇다면 이 레코드는 어디에 저장되었을까요 파티션 p0701에는 이 레코드가 포함될 수 있습니다. June 2007을 위한 파티션을 정의하지도 않았습니다. 테이블의 파티션을 확인해 봅시다:

테이블의 파티션

SYS_P1이라는 이름의 파티션의 "high value"가 "July 1, 2007"로 설정되었음을 확인할 수 있습니다. 이 파티션은 오라클에 의해 자동으로 생성되었으며, 시스템이 자동 생성한 이름을 갖습니다.

이제 방금 입력한 것보다 낮은 값으션 인터벌을 설정했으므로 별도의 파티션이 생성되어야 할 것입니다.

01-May-07을 입력 후 화면

"high value"로 "June 1"을 갖는 새로운 파티션 SYS_P42가 생성되었습니다. 이 파티션을 생성하기 위해 (June 데이터를 위한) SYS_P41 파티션을 분할하는 작업이 수행되었습니다. 이처럼, 오라클은 인터벌 파티셔닝으로 정의된 구성에서 필요에 따라 자동으로 파티션을 생성해 줍니다.
파티션을 특정 테이블스페이스에 저장하고 싶다면 store in 구문을 이용하면 됩니다:

interval (numtoyminterval(1,'MONTH'))
store in (TS1,TS2,TS3)

이제 파티션은 TS1, TS2, TS3 테이블스페이스에 라운드 로빈 방식으로 저장됩니다.

애플리케이션 개발자가 특정 파티션에 접근하고자 할 때에는 어떻게 해야 할까요 물론 파티션의 이름으로 접근할 수도 있지만, 실제 환경에서는 적용하기 어렵거니와 에러의 가능성도 높습니다. 특정 파티션에 대한 접근을 지원하기 위해, Oracle Database 11g은 파티셔닝 SQL을 위한 새로운 구문을 제공합니다:

새로운 구문을 제공

"partition for (value)"라는 새로운 구문을 주목하시기 바랍니다. 이 구문을 이용하면 파티션 이름을 지정하지 않고도 파티션을 직접 참조할 수 있습니다. 파티션을 drop 또는 truncate 처리하고자 할 때에도 이 구문을 사용하면 됩니다.

인터벌 파티션이 적용된 테이블을 DBA_PART_TABLES 뷰에서 조회하면 PARTITIONING_TYPE 컬럼이 INTERVAL로 설정되어 있음을 확인할 수 있습니다.

시스템 파티셔닝

오라클은 이 기능이 실제로 적용되는 경우는 흔치 않을 것으로 예상하고 있습니다. 하지만 워낙 멋진 기능이라 설명을 생략하고 지나갈 수는 없을 것 같습니다.

정말 흔치 않지만 상상은 해 볼 수 있는 상황으로 다음과 같은 경우가 있습니다. 어떤 논리적인 방법으로도 파티셔닝 될 수 없는 테이블이 하나 있다고 가정해 봅시다. 그 결과로 엄청난 사이즈의 거대한 테이블이 생성되고, 인덱스 유지보수 등의 작업에서 심각한 문제가 발생하게 될 것입니다.

개발자들이 한 가지 해결책을 제안하고 나섰습니다. 그들은 테이블이 어떤 식으로든 파티셔닝 될 수 있다면, 애플리케이션에서 지능적인 방법으로 파티션에 접근할 수 있도록 구현하겠다고 약속합니다. 다시 말해, 어떤 레코드가 어떤 파티션에 저장되어야 하는지 애플리케이션에서 컨트롤하겠다는 의미입니다. DBA는 어떤 식으로든 파티션을 정의하기만 하면 됩니다. 그 예가 아래와 같습니다:

create table sales3
(
sales_id number,
product_code number,
state_code number
)
partition by system
(
partition p1 tablespace users,
partition p2 tablespace users
);

파티션 키나 바운더리가 전혀 정의되지 않았음을 주목하시기 바랍니다. 테이블은 물리적으로 두 개의 세그먼트로 분리되었지만 여전히 하나의 논리적 테이블을 구성하고 있습니다. 위의 정의에 따라, 데이터베이스는 테이블을 위한 두 개의 세그먼트를 생성합니다. 그 결과를 확인해 봅시다:

테이블을 위한 두 개의 세그먼트를 생성 결과 확인

로컬 인덱스를 생성하면, 인덱스도 동일한 방법으로 파티셔닝 됩니다.

파티셔닝

위에서 SYSTEM은 물론 시스템 파티셔닝을 의미합니다. 시스템 파티셔닝에서는 high_value 컬럼이 NULL로 표시됨을 함께 참고하시기 바랍니다.

시스템 파티셔닝

여기서 한 가지 흥미로운 질문을 던져 볼 수 있습니다. 파티셔닝 키나 range, list, hash 같은 파티셔닝 방법이 정의되지 않았는데, 오라클은 어떤 파티션에 어떤 레코드가 저장되는지 어떻게 알 수 있을까요

정답 오라클은 알지 못합니다. 테이블에 레코드를 INSERT 했을 때 어떤 결과가 발생하는지 한 번 살펴 봅시다:

SQL> insert into sales3 values (1,101,1);
insert into sales3 values (1,101,1)
*
ERROR at line 1:
ORA-14701: partition-extended name or bind variable must be used for DMLs on
tables partitioned by the System method

파티션 바운더리가 정의되지 않았기 때문에 애플리케이션이 데이터를 입력하면서 파티션을 직접 지정해 주어야 합니다. 따라서 구문을 아래와 같이 수정해 주어야 합니다:

SQL> insert into sales3 partition (p1) values (1,101,1);

DELETE 작업에서는 파티션을 지정해 줄 필요가 없습니다. 하지만 분명히 명심해야 할 것은 파티션 바운더리의 개념이 존재하지 않는다는 사실입니다. 따라서 아래와 같은 구문을 실행한 경우,

SQL> delete sales3 where state_code = 1;

오라클은 로할 것입니다. 이런 문제를 방지하려면 아래와 같이 실행해야 합니다:

SQL> delete sales3 partition (p1) where state_code = 1;

UPDATE 작업의 경우도 마찬가지입니다. 위와 같은 방법으로 레코드의 검색 대상을 제한할 수 있습니다.

시스템 파티션은 테이블이 어떤 논리적인 방법으로도 파티셔닝 될 수 없을 때 매우 유용한 대안을 제공합니다. 개발자들에게 레코드가 어떤 파티션에 저장되는지 결정할 수 있는 재량을 부여하는 동시에 파티셔닝의 기본적인 혜택을 보장할 수 있습니다.

단일 파티션을 위한 테이블스페이스 전송

오라클 데이터베이스는 테이블스페이스를 전송(transport)하고 나중에 필요에 따라 다른 데이터베이스 또는 동일한 데이터베이스에서 플러그인 처리하는 기능을 제공해 왔습니다. 이 프로세스에는 데이터파일을 복사하는 과정이 포함되며, 따라서 데이터베이스에서 데이터를 전송하기 위한 가장 빠른 방법으로 활용될 수 있습니다. 하지만 테이블스페이스에서 단일 파티션만을 전송하고 플러그인 처리하는 기능은 아직까지 지원되지 않고 있었습니다. Oracle Database 11g에서는 가능합니다.

CT, NY 등의 여러 파티션을 갖는 SALES5 테이블이 있다고 가정해 봅시다.

SALES5 테이블

이제 아래 명령을 사용해서 CT 파티션을 전송할 수 있습니다.:

$ expdp tables=scott.sales5:ct transportable=always directory=data_pump_dir dumpfile=p_ct.dmp Export: Release 11.1.0.4.0 - Beta on Sunday, 10 June, 2007 16:05:40 Copyright (c) 2003, 2005, Oracle. All rights reserved. Username: / as sysdba
Connected to: Oracle Database 11g Enterprise Edition Release 11.1.0.4.0 - Beta
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Oracle Database Vault options
Starting "SYS"."SYS_EXPORT_TABLE_01": /******** AS SYSDBA tables=scott.sales5:ct transportable=
always directory=data_pump_dir dumpfile=p_ct.dmp
Processing object type TABLE_EXPORT/TABLE/PLUGTS_BLK
Processing object type TABLE_EXPORT/TABLE/TABLE
Processing object type TABLE_EXPORT/TABLE/END_PLUGTS_BLK
Master table "SYS"."SYS_EXPORT_TABLE_01" successfully loaded/unloaded
****************************************************************************
Dump file set for SYS.SYS_EXPORT_TABLE_01 is:
/home/oracle/oracle/admin/PROBE2/dpdump/p_ct.dmp
******************************************************************************
Datafiles required for transportable tablespace TS1:
/home/oracle/oradata/PROBE2/PROBE2/ts1_01.dbf
Job "SYS"."SYS_EXPORT_TABLE_01" successfully completed at 16:05:55

이제 생성된 두 개의 파일(p_ct.dmp와 ts1_01.dmp)를 이용해서 다른 시스템의 데이터베이스에 플러그인 처리할 수 있습니다. 여기에서는 학습 편의상 동일한 데이터베이스에서 플러그인 처리하기로 하겠습니다. 먼저, 테이블을 드롭하고 테이블스페이스 ts1을 드롭합니다.

SQL> drop table scott.sales5;Table dropped.SQL> drop tablespace ts1 including contents;Tablespace dropped.

이제 테이블스페이스를 데이터베이스에 플러그인 합니다. 하지만 한 가지 문제가 있습니다. 테이블 sales 5가 존재하지 않으며 앞에서 전체 테이블이 아닌 하나의 파티션(ct)만을 내보내었습니다. 그렇다면 어떻게 존재하지 않는 테이블의 1 개 파티션만 가져오기가 가능할까요

Oracle Database 11g는 Data Pump Import 유틸리티에 partition_options라는 새로운 커맨드 라인 옵션을 제공하고 있습니다. 여기에 "departition" 옵션을 명시하면 Data Pump는 내보내기 처리된 파티션으로부터 새로운 테이블을 생성합니다. 여기서 하나의 파티션을 테이블로부터 "떼어내는" 셈이므로 "departition"이라는 용어는 적절해 보입니다. 적용 방법이 아래와 같습니다.

$ impdp partition_options=departition dumpfile=p_ct.dmp
transport_datafiles='/home/oracle/oradata/PROBE2/PROBE2
/ts1_01.dbf'Import: Release 11.1.0.4.0 - Beta on Sunday, 10 June, 2007 21:58:08Copyright (c) 2003, 2005, Oracle. All rights reserved.Username: / as sysdbaConnected to: Oracle Database 11g Enterprise Edition Release 11.1.0.4.0 - Beta
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Oracle Database Vault options
Master table "SYS"."SYS_IMPORT_TRANSPORTABLE_04" successfully loaded/unloaded
Starting "SYS"."SYS_IMPORT_TRANSPORTABLE_04": /******** AS SYSDBA partition_options=
departition dumpfile=p_ct.dmp transport_datafiles=/home/oracle/oradata/PROBE2/PROBE2/ts1_01.dbf
Processing object type TABLE_EXPORT/TABLE/PLUGTS_BLK
Processing object type TABLE_EXPORT/TABLE/TABLE
Processing object type TABLE_EXPORT/TABLE/END_PLUGTS_BLK
Job "SYS"."SYS_IMPORT_TRANSPORTABLE_04" successfully completed at 21:58:23

위의 SQL 구문은 sales5_ct라는 이름의 테이블을 생성합니다. 이 테이블은 Transportable Tablespace 기능에 의해 내보내기 처리된 sales5 테이블의 ct 파티션만을 포함하고 있습니다. 위에서 볼 수 있듯, 테이블 이름은 기존 테이블 이름과 파티션 이름의 조합으로 명명됩니다. DBA_SEGMENTS 뷰에서 세그먼트의 존재를 확인할 수 있습니다.

세그먼트의 존재를 확인

단일 파티션에 대한 Transportable Tablespace 기능을 이용하여 테이블의 파티션 중 하나만을 다른 데이터베이스에 플러그인 처리할 수 있습니다.

가상 컬럼에 대한 파티셔닝

자주 경험하게 되는 문제가 또 한 가지 있습니다. 테이블 sales에 다음과 같은 컬럼이 정의되어 있다고 가정해 봅시다:

테이블 sales에 다음과 같은 컬럼이 정의

관리자는 sales_amt 값을 기준으로 삭제(purge), 아카이브 작업을 수행하기 위한 파티셔닝 방안을 고민 중입니다. sale_category는 다음과 같이 4 가지로 구분됩니다:

sale_category 구분

관리자는 이 테이블을 sales_category 컬럼을 기준으로 파티셔닝 하기로 합니다. 하지만 한 가지 문제가 있습니다: 테이블에는 "sales_category"라는 이름의 컬럼이 존재하지 않습니다. sales_category는 sales_amt 컬럼으로부터 파생된 결과값일 뿐입니다. 그렇다면 테이블을 어떻게 파티셔닝 할 수 있을까요

오라클의 이전 버전에서는 테이블에 sales_category라는 새로운 컬럼을 추가하고 트리거를 이용하여 컬럼 값을 생성하는 방법을 사용해야 했습니다. 하지만 새로운 컬럼에 트리거를 설정하는 경우 성능 상의 오버헤드를 감수해야만 합니다.

Oracle Database 11g는 가상 컬럼(Virtual Column)이라는 새로운 기능을 지원합니다. 가상 컬럼을 이용하면 테이블에 실제로 저장되지 않은 컬럼을 런타임에 계산하여 생성할 수 있습니다. 또 가상 컬럼에 파티션을 적용하는 것도 가능합니다. 그 방법이 아래와 같습니다.

create table sales
(
sales_id number,
cust_id number,
sales_amt number,
sale_category varchar2(6)
generated always as
(
case
when sales_amt <= 10000
then 'LOW'
when sales_amt > 10000
and sales_amt <= 100000
then case
when cust_id < 101 then 'LOW'
when cust_id between 101 and 200 then 'MEDIUM'
else 'MEDIUM'
end
when sales_amt > 100000
and sales_amt <= 1000000
then case
when cust_id < 101 then 'MEDIUM'
when cust_id between 101 and 200 then 'HIGH'
else 'ULTRA'
end
else 'ULTRA'
end
) virtual
)
partition by list (sale_category)
(
partition p_low values ('LOW'),
partition p_medium values ('MEDIUM'),
partition p_high values ('HIGH'),
partition p_ultra values ('ULTRA')
)

이제 새로운 레코드를 추가해 봅시다:

SQL> insert into sales (sales_id,cust_id,sales_amt) values (1,1,100);1 row created.SQL> insert into sales (sales_id,cust_id,sales_amt) values (2,1,1500);1 row created.SQL> insert into sales (sales_id,cust_id,sales_amt) values (3,102,1500);1 row created.SQL> insert into sales (sales_id,cust_id,sales_amt) values (4,102,10000);1 row created.SQL> commit;Commit complete.

위에서 sales_category의 값이 입력되지 않았음을 주목하시기 바랍니다. 이제 p_low 파티션에 저장된 데이터를 조회하면, 올바르게 데이터가 입력되었음을 확인할 수 있습니다:

데이터가 입력 확인

The record was placed in the appropriate partition.

가상 컬럼에 대한 파티셔닝을 이용하여, 실제로 컬럼이 존재하지 않는 경우에도 가상 컬럼을 기준으로 한 파티션을 생성할 수 있습니다. 위의 예제에서는 매우 간단한 계산식을 가상 컬럼에 적용하였지만, 계산식이 아무리 복잡하더라도 상관은 없습니다. 복잡한 계산식이 사용되는 경우에는 가상 컬럼에 대한 파티셔닝이 한층 더 유용하게 활용될 것입니다.

Partition Advisor

데이터베이스 관리자들이 파티션을 설계하는 과정에서 가장 고민하는 사항은 바로 파티셔닝 방법과 파티셔닝 컬럼의 선택입니다. 숙련된 전문가가 복잡한 워크로드 분석을 거쳐 그 방법을 결정했다 하더라도, 최적의 대안이 선택되었다고 보장하기는 어렵습니다. Oracle Database 11g에서 새로이 제공되는 Partition Advisor를 이용하면 데이터 및 액세스 패턴에 대한 분석을 통해 파티셔닝 방법에 대한 조언을 얻을 수 있습니다. Partitioning Advisor에 대해서는 본 시리즈의 다른 연재에서 자세하게 설명하겠습니다.

결론

파티셔닝은 이전에도 가장 유용한 도구의 하나로 활용되어 왔지만 Oracle Database 11g이 출시되면서 그 유용성이 한층 개선되었습니다.

  • 레퍼런스 파티셔닝을 이용하면 서로 연관된 두 테이블에 (자식 테이블에 파티셔닝 기준이 되는 컬럼이 존재하지 않는 경우에도) 동일한 기준의 파티션을 적용할 수 있습니다.
  • 인터벌 파티셔닝은 일정한 간격을 갖는 파티션 구성의 유지보수 편의성을 크게 개선해 줍니다.
  • range-range, list-range, list-hash, list-list 등의 조건을 이용한 확장 컴포지트 파티셔닝은 파티셔닝 선택의 폭을 넓혀 주고 관리성을 향상시키는 효과를 제공합니다.
  • Data Pump는 테이블스페이스 내의 단일 파티션에 대한 Transportable Tablespace 기능을 제공합니다. 이 기능은 아카이브 및 데이터 보존 환경에서 매우 유용합니다.
  • 마지막으로, 가상 컬럼에 대한 파티셔닝을 전략을 구현할 수 있습니다.

오라클의 파티셔닝 기능이 제공하는 선택의 폭이 이처럼 광범위했던 적이 없었습니다. 칠면조 요리에서 가장 맛있는 부위만 잘라낼 수 있는 멋들어진 나이프 세트를 얻으신 셈입니다!