1. 개요

제가 속한 회사 조직에서는 postgresql을 기본 rdbms로 채택해서 사용하고 있고,
boolean 또는 timestamp 형식의 컬럼들을 아래와 같은 방식으로 사용하고 있어요.

  1. boolean 값 표현 -> varchar(1)
  2. yyyyMMddHHmmss 값 표현 -> varchar(14)

왜 boolean 타입이나, timestamp 등 명시된 값을 사용하지 않는지 궁금해서 질문을 해보아도, 이전부터 그랬다는 답변이었고.. 더욱 더 궁금해져서 조금 조사를 해봤어요.

제 생각에는.. boolean과 timestamp가 저장공간(또는 index 공간) 면에서 월등히 우수하니, 선택하지 않을 이유가 없을 것 같은데 말이죠.

2. 다른 RDBMS 들과의 통일성

제가 속한 조직은, 데이터를 중앙에서 관리하고 여러 사내 서비스들에 서빙하는 조직이다 보니
데이터 종류가 다양하고 데이터의 성격 별로 여러 DB가 존재해요.

그 여러 DB들의 시스템으로 postgresql을 채택해서 통일해 사용하기 이전까지는, postgresql, mysql, 오라클을 같이 사용했다고 해요.

  1. 오라클은, boolean타입이 최근에 추가되었고 이전에는 없었어요.
  2. mysql은, boolean타입이 있긴 하지만, tinyint(1) 이라는 형식으로 치환되어요.

지금은 조직의 DB들이 모두 postgresql로 통일되었지만, 이전에 여러 DB시스템을 같이 사용할 때에는 RDBMS가 비록 다를지라도, 통일된 형식의 쿼리문을 사용하는 것이 업무방식에 중요했을 것 같아요.

boolean을 예시로 설명했지만, datetime도 마찬가지! postgresql, mysql, 오라클 등에서 datetime을 비교하고 생성하는 형식이 서로 상이할 것 같아요.

그래서 제가 생각하기에,

각각의 RDBMS가 제공하는 자료형들을 따로 따로 활용하는 것이 아니라
모든 RDBMS가 공통적으로 가지는 varchar 타입을 사용함으로서 여러 DB로 전송되는 쿼리의 스타일을 통일하고 가독성을 높이고 사용성을 높이려 한 것이다.

하는 결론에 도달했어요. 이렇게 생각하고 보니,, 꽤 괜찮은 답변 같기도 해요!!?

3. 정책 유지 vs performance 저울질…

사실은 조직의 모든 DB 시스템이 postgresql로 통일되었기에 이제는 postgresql의 boolean, timestamp 값을 사용해도 조직 내의 쿼리 통일성은 유지될 수 있을 것 같아요.

하지만,

모든 varchar(1) 컬럼들을 boolean 자료형으로, varchar(14) 컬럼들을 timestamp 자료형으로 변환한다.
라는 큰 작업을 할 만큼 성능에 이점이 있을까?

하는 생각도 들었어요. 너무 어려운 질문인 것 같긴 해요.. 저울질을 얼만큼의 강도로 해야할지…

그래서 일단은 눈으로 직접 확인해보고 싶어서, 로컬에서 postgresql을 띄우고 데이터를 구성한 후 쿼리 실행 성능을 측정해봤어요.

3.1. 실험환경

Docker를 이용해 postgresql을 띄우고, DataGrip으로 접속해 쿼리를 실행했어요. ( 도커는 신이네요. 클릭 한 번으로 너무 편리하게 뽀그리를 띄웠어요.. )
테이블은 아래와 같이 만들고, 데이터는 1000만 개의 데이터를 입력했어요.

boolean, timestamp 테이블

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
-- boolean 테이블
create table public.bool_table
(
    pk      integer              not null
        constraint bool_table_pk
            primary key,
    boolean boolean default true not null
);

alter table public.bool_table
    owner to postgres;

create index bool_table_boolean_index
    on public.bool_table (boolean);

-- timestamp 테이블
create table public.timestamp_table
(
    pk        integer not null
        constraint timestamp_table_pk
            primary key,
    timestamp timestamp
);

alter table public.timestamp_table
    owner to postgres;

create index timestamp_table_timestamp_index
    on public.timestamp_table (timestamp);

varchar 테이블

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
-- varchar(1) 테이블
create table public.varchar_table
(
    pk      integer                                   not null
        constraint varchar_table_pk
            primary key,
    varchar varchar(1) default 'Y'::character varying not null
);

alter table public.varchar_table
    owner to postgres;

create index varchar_table_varchar_index
    on public.varchar_table (varchar);

-- varchar(14) 테이블
create table public.timestamp_char_table
(
    pk             integer not null
        constraint timestamp_char_table_pk
            primary key,
    timestamp_char varchar(14)
);

alter table public.timestamp_char_table
    owner to postgres;

create index timestamp_char_table_timestamp_char_index
    on public.timestamp_char_table (timestamp_char);

데이터 입력

1
2
3
4
5
6
7
8
9
10
11
12
13
14
do $$  
--- 1000만개 데이터 입력
--- boolean/varchar(1) : 랜덤 true or false
--- timestamp/varchar(14) : 2024-07-27 12:08:20 에서 1초 씩 더한 값
begin  
    for i in 1..10000000 loop  
        insert into bool_table (pk, boolean) values (i, random() > 0.5);  
        insert into varchar_table (pk, varchar) values (i, case when random() > 0.5 then 'Y' else 'N' end);
        insert into timestamp_table (pk, timestamp) values (i, TIMESTAMP '2024-07-27 12:08:20' + make_interval(secs => i));
        insert into timestamp_char_table (pk, timestamp_char) values (i, to_char(TIMESTAMP '2024-07-27 12:08:20' + make_interval(secs => i),  
               'yyyymmddhh24miss'));
    end loop;  
end;  
$$;

3.2. 쿼리 실행

3.2.1. boolean vs varchar(1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
--- boolean 평균 execution 시간 : 350ms
explain analyze  
select count(*)  
from bool_table  
where boolean;  
--- boolean 평균 execution time : 780ms
explain analyze  
select *  
from bool_table  
where boolean  
  and pk > 100123;  

--- varchar(1) 실행 평균 시간 : 190ms
explain analyze  
select count(*)  
from varchar_table  
where varchar = 'Y';
--- varchar(1) 평균 execution time : 1100ms
explain analyze  
select *  
from varchar_table  
where varchar = 'Y'  
  and pk > 100123;

연산 별 비교 결과

웬 걸, boolean이 항상 빠르지 않고, 작업 별로 다르네요..? boolean은 index scan을 하지 않고, full scan을 때리고 있어요.

postgresql optimizer가, boolean column은 index scan 보다 full scan이 더 효율적이고 적합하다고 판단했나봐요…

varchar 테이블의 인덱스를 제거하고 돌려보면, 평균 450ms 정도가 나오네요. 인덱스가 없는 상태에서의 boolean과 varchar(1)의 full scan의 성능은 확연히 차이가 나긴 해요.

무조건 index를 생성한 상태가 현업의 기준일테니.. varchar(1)가 일반적으로 더 빠르다고 봐도 될 것 같아요. 너무 의외의 결과라서 놀랐어요.
boolean은 index 생성이 필요없이, 즉 DB 저장 공간의 낭비 없이 그나마 빠른 속도로 실행할 수 있다. 라고 생각해야겠어요.

3.2.2. timestamp vs varchar(14)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
--- timestamp count 평균 execution time : 15 ~ 20ms
EXPLAIN ANALYZE  
select count(*)  
from timestamp_table  
where timestamp between timestamp '2024-09-01 00:00:00' and timestamp '2024-09-02 00:00:00'
--- timestamp between 평균 execution time : 15 ~ 20ms
EXPLAIN ANALYZE  
select *  
from timestamp_table  
where timestamp between timestamp '2024-09-01 00:00:00' and timestamp '2024-09-02 00:00:00'
--- timestamp equal 평균 execution time : 0.030 ~ 0.050ms
EXPLAIN ANALYZE  
select *  
from timestamp_table  
where timestamp = timestamp '2024-09-01 00:00:00'

--- varchar count 평균 execution time : 30 ~ 40ms
EXPLAIN ANALYZE  
select count(*)  
from timestamp_char_table  
where timestamp_char > '20240901000000'  
  and timestamp_char < '20240902000000'
--- varchar between 평균 execution time : 30 ~ 45ms
EXPLAIN ANALYZE  
select *  
from timestamp_char_table  
where timestamp_char > '20240901000000'  
  and timestamp_char < '20240902000000'
--- varchar equal 평균 execution time : 0.030 ~ 0.050ms
EXPLAIN ANALYZE  
select *  
from timestamp_char_table  
where timestamp_char = '20240901000000'

연산 별 비교 결과

역시, 시간 연산을 위한 timestamp가 varchar(14)를 앞서는 결과를 보여줬어요. 특히 기간(between)에 대한 쿼리를 할 때 그 차이가 많이 나타났는데요, varchar(14) 는 큰 용량의 데이터를 비교할 때 효과적인 bitmap index scan을 사용하네요. 이론적으로 bitmap index scan이 빨라야 하는데.. 작은 데이터 개수에서는 오히려 bitmap index scan이 오버헤드로 인해 비교적 느릴 수 있다고 해요.

3.3. 실험 결과에 따른 생각 정리

4. 결론

varchar(1)이 좋을까, boolean이 좋을까… varchar(14)가 좋을까, timestamp가 좋을까… 라는, 성능의 관점에서 간단한 궁금증으로 조사를 시작했지만, 자꾸 곱씹어보니 성능만이 중요한 것이 아니라는 생각이 점점 들었어요.

  1. 기존 정책 유지 및 안정성 vs 성능 및 legacy 제거
  2. 조직과 데이터의 목적

꾸준히 이런 글을 쓰다 보면 제 생각도 정리되고 명확한 저만의 기준이 생기지 않을까요.

정진 !