데이터 블로그

AMAZON REDSHIFT. 데이터 압축3 - 관리

AWS

analyze compression


analyze compression 명령은 기존 테이블의 최적의 압축 인코딩을 찾는데 사용한다. analyze compression 명령을 실행하면 각 열의 최적의 압축 인코딩 유형과 압축률을 표시해 준다. 하지만 copy 명령과 달리 직접 테이블에 압축 인코딩을 적용하지 않는다.


analyze compression 명령 예제


-- 테이블의 모든 열에 대해 조사
analyze compression customer  

-- 테이블의 일부 열에 대해 조사
analyze compression customer  (c_city,c_nation,c_region)

-- 테이블의 일부 열에 대해 조사
analyze compression customer comprows 100



앞에서 만든 raw 인코딩의 customer 테이블에 analyze compression 명령을 수행한 결과는 다음과 같다.


Table

Column

Encoding

Est_reduction_pct

customer

c_custkey

delta

75

customer

c_name

zstd

97.06

customer

c_address

zstd

31.82

customer

c_city

bytedict

92.46

customer

c_nation

bytedict

90.68

customer

c_region

bytedict

90.46

customer

c_phone

zstd

61.98

customer

c_mktsegment

bytedict

92.31



Encoding 열은 압축률이 가장 좋은 최적의 압축 인코딩 유형이고, Est_reduction_pct값은 압축률을 뜻한다. 압축률이 75(%)이라면 원본의 크기가 100이라면 압축 후 25로 줄어든다는 이야기다.


analyze compression 명령의 결과대로 압축 인코딩을 적용하기 위해서는 테이블을 새로 만들어야 한다. 다음은 analyze compression 명령의 결과대로 압축 인코딩을 지정한 CREATE TABLE 문이다.


create table customer_new
(
c_custkey		integer not null sortkey encode raw,
c_name			varchar(25) not null encode zstd,
c_address		varchar(25) null encode zstd,
c_city			varchar(10) null encode bytedict,
c_nation		varchar(15) null encode bytedict,
c_region		varchar(12) null encode bytedict,
c_phone			varchar(15) null encode zstd,
c_mktsegment		varchar(10) not null encode bytedict
);



Amazon Redshift Column Encoding Utility


Amazon에서 만든 유틸리티를 소개하고자 한다. Amazon Redshift Column Encoding Utility는 analyze compression 명령의 결과에서 얻은 최적의 인코딩 유형대로 새 테이블을 만들어주는 SQL 스크립트를 만들어준다. 뿐만 아니라, 기존 테이블의 sortkey, distkey, primary key, foreign key 등도 모두 동일하게 만들 수 있도록 스크립트가 만들어 진다.


파이썬으로 작성된 이 유틸리티의 기본 사용법은 다음과 같다.


python analyze-schema-compression.py --db db_name --db-user username --db-pwd password --db-host redshift_address


이렇게 실행하면 데이터베이스 안에 존재하는 모든 테이블에 대해 조사와 스크립트를 생성이 이루어진다. 다음은 앞에서 만든 customer 테이블에 대해 유틸리티를 실행한 결과이다.


> python analyze-schema-compression.py --db db_name --db-user user_name --db-pwd password --db-host host --analyze-table customer


-- [36712] [36712] Running set statement_timeout = '1200000'

-- [36712] Success.

-- [36712] [36712] Running set application_name to 'ColumnEncodingUtility-v.9.3.4'

-- [36712] Success.

-- [36712] [36712] Running set search_path to '$user', public, public;

-- [36712] Success.

-- [36712] Connected to tendw-test.c9ucj9e4yn8e.ap-northeast-2.redshift.amazonaws.com:5439:tendw as redsa

-- [36712] Analyzing Table 'customer' for Columnar Encoding Optimisations with 1 Threads...

-- [36712] Analyzing 1 table(s) which contain allocated data blocks

-- [36712] Table public.customer contains 7 unoptimised columns

-- [36712] Analyzing Table 'public.customer'

-- [36712] Column Encoding will be modified for public.customer

-- [36712] No encoding modifications run for public.customer

begin;

lock table public."customer";

create table public."customer_$mig"(

"c_custkey" integer  NOT NULL encode RAW

,"c_name" varchar(25)  NOT NULL encode zstd

,"c_address" varchar(25)   encode zstd

,"c_city" varchar(10)   encode bytedict

,"c_nation" varchar(15)   encode bytedict

,"c_region" varchar(12)   encode bytedict

,"c_phone" varchar(15)   encode zstd

,"c_mktsegment" varchar(10)  NOT NULL encode bytedict

)

SORTKEY(c_custkey)

;

alter table public."customer_$mig" owner to redsa;

insert into public."customer_$mig"  select * from public."customer" order by "c_custkey";

analyze public."customer_$mig";

alter table public."customer" rename to "customer_20180802_jqZ9ADTiEZ_$old";

alter table public."customer_$mig" rename to "customer";

commit;

-- [36712] Performed modification of 1 tables

-- [36712] Processing Complete



AMAZON REDSHIFT. 데이터 압축2 - 압축 수행

AWS

테이블(열)에 데이터 압축을 적용하는 방법은 두 가지가 있다. 테이블에 처음으로 데이터를 로드할 때 COPY 명령을 이용해 자동으로 인코딩 형식을 지정하도록 하는 방법과 CREATE TABLE 문으로 직접 인코딩 형식을 지정하는 방법이 있다. 여기서 권장되는 방법은 COPY 명령을 이용하는 것이다.


COPY 명령

COPY 명령으로 빈 테이블에 데이터가 적재될 때 자동으로 최적의 인코딩 형식이 적용된다. 이와 관련된 옵션은 다음과 같은 것들이 있다.


  • COMPUPDATE [ON | OFF]

압축 인코딩을 자동으로 적용할지 여부를 결정한다.  COMPUPDATE 옵션을 생략했을 때는 테이블에 데이터가 없는 상태에서 테이블에 인코딩이 지정되지 않은 경우에 한해 자동으로 압축이 적용된다. COMPUPDATE 옵션을 ON으로 지정하면, 빈 테이블일 경우에 한해 이미 인코딩 유형이 지정되어 있어도 이를 무시하고 자동으로 최적의 인코딩 형식이 적용된다.


  • COMPROWS numrows

최적의 압축 인코딩을 결정하기 위해 조사할 데이터의 행 수를 지정한다. 원리는 비교적 간단하다. 여기서 지정한 행 수 만큼을 불러와 가능한 모든 인코딩 형식으로 압축해 보고, 가장 압축률이 좋은 인코딩 형식을 선택하게 된다.


행 수를 지정하지 않으면 조각(Slice) 마다 100,000개의 행이 적용된다. 행 수를 지정하면, 지정한 행 수를 총 조각수 만큼 나눈 숫자만큼 각 조각이 이용한다. 예를 들어, 클러스터에 총 4개의 조각이 있을 때, 행 수를 1,000,000으로 지정하면 각 조각마다 250,000개의 행을 이용한다.AWS Redshift 클러스터 유형별 조각수는 Amazon Redshift 클러스터 - 노드 유형에서 확인할 수 있다.


이제 실제 테이블을 만들어 COPY 명령을 이용한 예제를 살펴보자. 먼저 명시적으로 Raw 인코딩으로 테이블을 하나 생성하고, 별도로 인코딩 유형을 지정하지 않아 기본 인코딩 유형이 지정되도록 테이블을 하나 만들어 보자.



-- 명시적으로 압축하지 않도록 Raw 인코딩을 지정
create table customer
(
c_custkey		integer not null sortkey encode raw,
c_name			varchar(25) not null encode raw,
c_address		varchar(25) null encode raw,
c_city			varchar(10) null encode raw,
c_nation		varchar(15) null encode raw,
c_region		varchar(12) null encode raw,
c_phone			varchar(15) null encode raw,
c_mktsegment		varchar(10) not null encode raw
);

-- 테이블을 생성할 때 압축 인코딩을 지정하지 않아 기본 인코딩 유형이 선택된다.
create table customer_compressed
(
c_custkey		integer not null sortkey,
c_name			varchar(25) not null,
c_address		varchar(25) null,
c_city			varchar(10) null,
c_nation		varchar(15) null,
c_region		varchar(12) null,
c_phone			varchar(15) null,
c_mktsegment		varchar(10) not null
);



이제 앞에서 만든 두 테이블의 열들의 압축 인코딩을 확인하기 위해 pg_table_def  시스템 카달로그 테이블을 쿼리한다. 쿼리 결과에서 encoding 열의 값을 확인하면 압축 인코딩 유형을 알 수 있다. 결과를 보면 명시적으로 Raw 인코딩을 지정한 열들은 encoding 열 값이 none 으로 나타나 압축을 하지 않고 있음을 알 수 있다. customer_compressed 테이블의 경우 기본 인코딩 유형이 선택 되었음을 확인할 수 있다.



-- 테이블의 인코딩 유형을 확인한다.
select tablename, "column", type, encoding 
from pg_table_def 
where tablename in('customer','customer_compressed');


tablename

column

type

encoding

customer

c_custkey

integer

none

customer

c_name

character varying(25)

none

customer

c_address

character varying(25)

none

customer

c_city

character varying(10)

none

customer

c_nation

character varying(15)

none

customer

c_region

character varying(12)

none

customer

c_phone

character varying(15)

none

customer

c_mktsegment

character varying(10)

none

customer_compressed

c_custkey

integer

none

customer_compressed

c_name

character varying(25)

lzo

customer_compressed

c_address

character varying(25)

lzo

customer_compressed

c_city

character varying(10)

lzo

customer_compressed

c_nation

character varying(15)

lzo

customer_compressed

c_region

character varying(12)

lzo

customer_compressed

c_phone

character varying(15)

lzo

customer_compressed

c_mktsegment

character varying(10)

lzo



앞에서 만든 두 테이블에 COPY 명령을 이용해 데이터를 적재하여 보자. customer 테이블은 compupdate off 옵션을 이용해 자동 압축이 일어나지 않아 압축되지 않은 형태로 데이터를 저장한다. 반면에 customer_compressed 테이블은 compupdate on 옵션을 이용해 최적의 압축 인코딩을 찾도록 했다.



-- 각 테이블에 데이터를 로드한다. 아래 데이터는 AWS 계정이 있으면 누구나 접근할 수 있다.
copy customer from 's3://awssampledbuswest2/ssbgz/customer' 
credentials 'aws_access_key_id=*************;aws_secret_access_key=*************'
gzip
compupdate off  -- 압축 인코딩을 자동으로 수행하지 않는다.
region 'us-west-2';

copy customer_compressed from 's3://awssampledbuswest2/ssbgz/customer' 
credentials 'aws_access_key_id=*************;aws_secret_access_key=*************'
gzip
compupdate on  -- 압축 인코딩을 자동으로 수행한다.
region 'us-west-2';



데이터 적재가 완료되고 압축 인코딩의 변화를 살펴보기 위해 다시한번 pg_table_def  시스템 카달로그 테이블을 쿼리한다. 결과를 보면 customer_compressed 테이블의 압축 인코딩 유형이 처음과 바뀐것을 알 수 있다.



select tablename, "column", type, encoding 
from pg_table_def 
where tablename in('customer','customer_compressed');


tablename

column

type

encoding

customer

c_custkey

integer

none

customer

c_name

character varying(25)

none

customer

c_address

character varying(25)

none

customer

c_city

character varying(10)

none

customer

c_nation

character varying(15)

none

customer

c_region

character varying(12)

none

customer

c_phone

character varying(15)

none

customer

c_mktsegment

character varying(10)

none

customer_compressed

c_custkey

integer

delta

customer_compressed

c_name

character varying(25)

lzo

customer_compressed

c_address

character varying(25)

lzo

customer_compressed

c_city

character varying(10)

bytedict

customer_compressed

c_nation

character varying(15)

bytedict

customer_compressed

c_region

character varying(12)

bytedict

customer_compressed

c_phone

character varying(15)

lzo

customer_compressed

c_mktsegment

character varying(10)

bytedict



마지막으로 두 테이블과 두 테이블의 각 열들의 크기(MB)를 비교해 보자.


-- 테이블의 크기를 비교해 보자.
select "table", size
from svv_table_info
where "table" in('customer','customer_compressed');


table

size

customer

394

customer_compressed

172




-- 열 크기를 비교해 보자.
select p.name, b.col, count(*)
from stv_blocklist b
	join stv_tbl_perm p on b.tbl = p.id and b.slice = p.slice
where p.name in ('customer', 'customer_compressed')
	and b.col < 8
group by p.name, b.col
order by 1,2


name

col

count

customer                                                                

0

14

customer                                                                

1

66

customer                                                                

2

60

customer                                                                

3

46

customer                                                                

4

38

customer                                                                

5

36

customer                                                                

6

60

customer                                                                

7

40

customer_compressed                                                     

0

6

customer_compressed                                                     

1

16

customer_compressed                                                     

2

56

customer_compressed                                                     

3

6

customer_compressed                                                     

4

6

customer_compressed                                                     

5

6

customer_compressed                                                     

6

36

customer_compressed                                                     

7

6



테이블은 대략 절반 정도로 줄어 들었으며, 4번째 열(col=3)인 c_city 열은 원본이 46MB에서 6MB로 줄어 원본대비 13%로 줄어들었다.



인코딩 유형 지정

CREATE TABLE 문으로 테이블을 생성하거나 ALTER TABLE 문으로 새로운 열을 추가할 때 인코딩 유형을 지정할 수 있다. 다만 이미 데이터가 있는 테이블이려면 인코딩 유형을 변경할 수 없다. 다음은 cusomter 테이블을 만들고, 나이(age) 열을 추가할 때 인코딩 유형을 지정하는 예제이다.



create table customer 
(
c_custkey integer not null encode delta,
c_name varchar(25) not null encode zstd,
c_address varchar(25) null encode zstd,
c_city varchar(10) null encode bytedict,
c_nation varchar(15) null encode bytedict,
c_region varchar(12) null encode bytedict,
c_phone varchar(15) null encode zstd,
c_mktsegment varchar(10) not null encode bytedict
);

alter table customer
add column c_age int null encode mostly8;



AMAZON REDSHIFT. 데이터 압축1 - 압축 유형

AWS


데이터 압축은 저장 공간과 읽어야 할 데이터를 줄여준다. 따라서 특별한 이유가 없다면 데이터를 압축하도록 구성하는 것이 일반적이다.  Amazon Redshift에서는 열 단위로 압축 유형(인코딩)을 지정하기 때문에 열에 포함된 데이터의 특성에 맞게 압축 유형을 지정해야 한다. 그러기 위해 Amazon Redshift의 압축 유형을 먼저 정리해 본다.



Raw

데이터를 압축하지 않고 원본 그대로 저장한다. 정렬 키(SORTKEY) 및 BOOLEAN, REAL, DOUBLE PRECISION 데이터 형식의 열에서 압축 인코딩 유형을 지정하지 않으면 기본적으로 Raw 인코딩이 선택된다.


  • CREATE TABLE 키워드: raw

  • 적용 가능한 데이터 형식: 모든 데이터 형식



Byte Dictionary

256개의 사전을 만들어, 원래 값을 사전(딕셔너리)의 순번(인덱스)으로 대체하여 저장한다. 사전의 순번은 1바이트를 차지한다. 따라서 반복적으로 나타나는 값이 적을 때 유리하다. 예를 들어, 다음과 같이 시도를 나타내는 열이 있을 때 압축전후 크기를 비교해 보자.


열 값

원본 크기

인덱스

압축된 크기

서울특별시

15

0

1

서울특별시

15

0

1

서울특별시

15

0

1

경기도

9

1

1

경기도

9

1

1

전라북도

12

2

1

전라북도

12

2

1

전라북도

12

2

1

제주도

9

3

1

제주도

9

3

1

제주도

9

3

1

제주도

9

3

1

135


12


데이터를 압축하지 않았을 때는 원본 크기 항목의 총 합인 135 바이트를 차지한다. 하지만 Byte Dictionary 압축을 하게되면 데이터의 크기가 사전의 크기 + (1바이트 x 행수)로 줄어든다. 위 예제에서 사전은 서울특별시(15), 경기도(9), 전라북도(12), 제주도(9)로 구성되어 크기가 45 바이트이다. 그리고 원래 데이터는 모두 사전의 순번으로 대체되므로 1바이트만 차지하게 된다. 따라서 최종 압축된 크기는 45 + (1 x 12) 이렇게 계산을 하여 57 바이트가 된다.


만약 단어의 수가 256개를 초과할 경우, 초과되는 값은 압축되지 않은 형태로 저장된다.


  • CREATE TABLE 키워드: bytedict

  • 적용 가능한 데이터 형식: BOOLEAN을 제외한 모든 형식



Delta

Delta 인코딩은 인접한 값의 차이만 저장하는 식으로 저장 공간을 줄인다. 큰 숫자인 1,000,000,000과 1,000,000,001이 있다고 할 때, 이 두 값을 압축하지 않고 저장하면 각각 4바이트가 필요하다.


여기서 첫 번째 값 1,000,000,000을 기준(플래그)이라고 할 때, 두 번째 값은 기준과의 차이(델타)인 1만 증시키면 얻을 수 있다. 그래서 두 번째 값은 큰 값 대신에 기준과 차이인 1만 저장하게 되고, 그 크기는 1바이트로 줄일 수 있게 된다. 그래서 Delta 인코딩은 날짜/시간 매우 유용하다고 한다. 절대 날짜/시간 값이 아닌, 이전과의 날짜/시간의 차이만 저장하면 되기 때문이다.


세부적으로는 Delta 인코딩은 차이의 크기를 1바이트로 저장하고, Delta32k  인코딩은 차이의 크기를 2바이트로 저장하는 것으로 구분할 수 있다.


AWS 공식 문서의 예제를 살펴보는 것도 이해에 도움이 된다.


  • CREATE TABLE 키워드: delta, delta32k

  • 적용 가능한 데이터 형식: SMALLINT, INTEGER, BIGINT, NUMERIC, DATE, TIMESTAMP



LZO

LZO 인코딩은 LZO 압축 알고리즘을 사용하여 문자열 형식인 CHAR 및 VARCHAR 열에서 유용하다. LZO는 정렬 키로 지정되는 열과 BOOLEAN, REAL 또는 DOUBLE PRECISION 데이터 형식으로 정의되는 열을 제외하고 기본 인코딩이다.


LZO 압축 알고리즘에 대한 추가 정보는 여기서 확인할 수 있다.


  • CREATE TABLE 키워드: lzo

  • 적용 가능한 데이터 형식: REAL, DOUBLE PRECISION, BOOLEAN을 제외한 모든 형식



Mostly

Mostly 인코딩은 정수형 데이터 형식에서 대부분의 값이 주어진 최대 크기보다 작은 값이 저장될때 유용하다. 만약 INT 형식의 열에 가끔 10억 단위의 큰 값이 들어오지만, 대부분 1~10 사이의 값이 저장되는 경우가 여기에 해당된다.


Mostly 인코딩은 범위에 따라 Mostly8, Mostly16, Mostly32로 나뉜다. 주어진 범위를 벗어나는 값은 원본 그대로 저장하고 범위 안의 값이라면 8비트(1바이트), 16비트(2바이트), 32비트(바이트) 중 하나의 크기로 저장된다.


예를 들어, INT 형식에 Mostly8 인코딩을 지정한 경우에서 12는 1바이트, 3000은 4바이트(Mostly8 인코딩의 범위를 벗어나기 때문에) 를 차지한다.


Mostly8, Mostly16, Mostly32 구체적 범위는 아래와 같다.

인코딩

크기

범위

MOSTLY8

1바이트(8비트)

-128~127

MOSTLY16

2바이트(16비트)

-32768~32767

MOSTLY32

4바이트(32비트)

4 bytes



  • CREATE TABLE 키워드: mostly8, mostly16, mostly32

  • 적용 가능한 데이터 형식:

    • Mostly8: SMALLINT, INTEGER, BIGINT, NUMERIC

    • Mostly16: INTEGER, BIGINT, NUMERIC

    • Mostly32: BIGINT, NUMERIC



Runlength

Runlength 인코딩은 연속적으로 나타나는 값을 그 값과 발생 횟수로 구성된 토큰 하나로 대체한다. 예를 들어, Blue, Blue, Blue, Blue, Red, Red, Green, Green, Green 이렇게 값이 연속적으로 나타난다면, 이 값은 (4, Blue), (2, Red), (3, Green) 이런 토큰으로 대체하여 공간을 절약할 수 있다. 여기서 (4, Blue) 토큰은 Blue가 4번 연속적으로 나타났음을 말한다.


  • CREATE TABLE 키워드: runlength

  • 적용 가능한 데이터 형식: 모든 데이터 형식



Text255 및 Text32k

Text255 및 Text32k 인코딩은 Byte Dictionary 인코딩과 유사하게 단어를 사전에 등록하고 원래 단어를 사전의 순번으로 대체하여 공간을 줄여준다. Byte Dictionary와 다른점은 열에 있는 값 전체를 하나로 사전에 등록하는 것이 아니라, 각 단어를 개별로 사전에 등록한다. Text255 인코딩은 디스크 블록마다 먼저 나타나는 단어 245개를 사전에 등록한다. Text32k 인코딩은 사전의 크기가 약 32k에 도달할 때까지 단어를 등록한다. 사전에 없는 나머지 단어는 원본 그대로 저장한다.


  • CREATE TABLE 키워드: text255, text32k

  • 적용 가능한 데이터 형식: VARCHAR



Zstandard

Zstandard 인코딩은 페이스북에서 Zip을 대체할 목적으로 공개한 Zstandard 압축  알고리즘이다. 개인적인 경험으로는 텍스트 형식 데이터의 경우 lzo 보다 압축률이 좋아 보였다. 알고리즘에 대한 자세한 내용은 ZDNet의 “페이스북, ZIP 대체할 압축기술 소스코드 공개” 기사를 참고하자.


  • CREATE TABLE 키워드: zstd

  • 적용 가능한 데이터 형식: 모든 데이터 형식