상세 컨텐츠

본문 제목

Row vs Column oriented Databases

데이터베이스

by moonionn 2023. 12. 4. 00:29

본문

혹시 통계 데이터 조회를 위해 구글 빅쿼리 혹은 아마존 레드시프트를 사용한다는 말, 들어보셨을까요? 흔히 사용되는 MySQL, PostreSQL 등 외에도 이런 별도의 데이터 저장소들을 사용하는 경우는 많이 보았을 겁니다. 막연하게 "그냥 MySQL과는 다른 어떤 통계용 DB? 데이터 웨어하우스? 인가보다?" 라고 생각하고 넘어갈 수 있지만, 오늘은 이 데이터 저장소들이 어떻게 차이가 나는지 적어보고자 합니다.

 

데이터베이스 시스템은 데이터를 어떻게 저장하느냐에 따라 크게 두 가지로 나뉩니다. 하나는 행(row)을 중심으로 저장하는 row oriented (한국말로는 행 기반, 혹은 행 지향), 다른 하나는 열(column)을 중심으로 저장하는 column oriented (한국말로는 열 기반, 혹은 열 지향) 방식입니다. 말로만 설명하면 이해하기 어려울 듯 싶으니 예시 테이블을 하나 준비해 보았습니다.


이해를 돕기 위한 payment 테이블 (primary key 외 인덱스 없음)

id payer amount state created_at updated_at
1 James 100 Completed 2022-12-03 2022-12-03
2 John 200 Cancelled 2022-12-03 2022-12-04
3 Parker 300 Completed 2022-12-04 2022-12-04
4 Anne 400 Cancelled 2022-12-05 2022-12-06
5 Adam 500 Completed 2022-12-09 2022-12-09
6 Mary 600 Requested 2022-12-09 2022-12-09

위 테이블과 같은 데이터를 저장해야 한다고 할 때, Row vs Column 데이터베이스는 어떤 차이를 보일까요?


두 시스템을 비교하기에 앞서 디스크 읽기의 최소 단위인 블락(block)에 대해 잠깐 설명하고자 합니다. 우리는 데이터베이스, 즉 디스크에서 데이터를 읽어오는 작업을 I/O(Input/Ouput) 라고도 표현하죠. 이 때 한 번의 I/O로 읽어오는 최소 데이터 단위를 블락(block)이라고 합니다. 즉 최대한 적은 I/O로 원하는 데이터를 얻는 것이 효율적인 디비 사용의 핵심이라고 할 수 있습니다. 이 row/column oriented 데이터베이스의 차이점 또한 이 블락 단위 안에 데이터가 어떻게 저장되는지에 따라 생긴다는 점을 일러두고 싶습니다.

* MySQL의 경우 디폴트 블락 사이즈는 16KB

 

Row-oriented database

Row-oriented의 경우 행 단위로 하나의 블락이 구성됩니다.

예시의 payment 테이블인 경우 간단하게 아래 두 개의 블럭으로 구성된다고 가정해 보겠습니다. 블럭 안에 여러 행들이 포함되어 있으니, 내가 원하는 건 단 하나의 행 데이터라 하더라도 해당 블락 안에 포함된 그 외 행들도 읽어오게 되어 있습니다.

 

블럭 1

1 James 100 Completed 2022-12-03 2022-12-03
2 John 200 Cancelled 2022-12-03 2022-12-04
3 Parker 300 Completed 2022-12-04 2022-12-04

 

블럭2

4 Anne 400 Cancelled 2022-12-05 2022-12-06
5 Adam 500 Completed 2022-12-09 2022-12-09
6 Mary 600 Requested 2022-12-09 2022-12-09

 

만약 여기서 아래와 같은 쿼리를 날린다면 내부적으로 어떤 일이 일어날까요?

SELECT payer FROM payment WHERE state = 'Requested';

 

첫 번째 읽어온 블럭의 경우 state가 Requested인 행이 없으니 다음 블럭을 또 읽어와야 합니다. 두 번째 읽어온 블럭에 Requested라는 값이 있는 행(id=6)을 발견하게 되는데 해당 행에 payer 컬럼에 대한 값도 포함되어 있으므로 Mary라는 결과물을 얻을 수 있습니다.

예시의 경우 두 개의 블럭만 있었지만 만일 실세계에서 사용되는 데이터베이스였다면 엄청나게 많은 블락을 읽어와야 하고, 이 말인 즉슨 엄청나게 많은 I/O 작업을 필요로 한다는 뜻이기도 합니다.

 

해당 쿼리를 row-oriented 데이터베이스인 MySQL에서 explain 해봐도 풀스캔을 시행하는 걸 확인할 수 있습니다.

 

그렇다면 아래 쿼리는 어떨까요?

SELECT * FROM payment WHERE id=6;

 

조건절에 쓰인 id 컬럼은 primary key이기 때문에 엉뚱한 블락을 가지고 올 필요도 없이 바로 아래 행에 접근할 수 있습니다. (primary key, index에 대한 글이 아니니 추가 설명은 스킵하도록 합니다.)

6 Mary 600 Requested 2022-12-09 2022-12-09

 

중요한 점은, row-oriented의 경우 이미 이 행이 가지고 있는 컬럼값들이 동시에 조회되기 때문에 SELECT * 에 대한 부담이 비교적 덜 하다는 점입니다. 이 부분은 후술할 column-oriented를 보면 얼마나 극명하게 차이가 나는지 아실 수 있습니다.

 

이제 Aggregation 성격의 쿼리를 실행해 보겠습니다.

SELECT sum(amount) FROM payment;

 

첫 번째 블럭을 읽어옵니다. 블럭에 포함된 행들의 amount 컬럼 값들을 합칩니다.

1 James 100 Completed 2022-12-03 2022-12-03
2 John 200 Cancelled 2022-12-03 2022-12-04
3 Parker 300 Completed 2022-12-04 2022-12-04

 

이제 두 번째 블럭을 읽어서 동일한 작업을 진행합니다. 예상 가능하다시피 테이블의 규모가 크면 클 수록 I/O는 늘어납니다.

4 Anne 400 Cancelled 2022-12-05 2022-12-06
5 Adam 500 Completed 2022-12-09 2022-12-09
6 Mary 600 Requested 2022-12-09 2022-12-09

 

이 경우도 마찬가지로 풀 테이블 스캔을 시행해야 함을 알 수 있습니다. 다시 설명하자면, 현재 나의 관심 밖에 있는 payer, state, created_at, updated_at 컬럼 데이터들 또한 모두 메모리에 올라가게 됨을 의미합니다.

 

위 사례들에서 보았듯 통계성 데이터 조회에 row-oriented는 적합하지 않습니다. 물론 인덱스를 별도로 추가하면 또 달라지겠지만, 인덱스는 어디까지나 보충장치일 뿐 이는 근본적으로 사용 목적에 부합한 툴이 아님을 알 수 있습니다. 그리고 아무리 인덱스나 쿼리 튜닝으로 호흡기를 달아놔도 column-oriented와 비교했을 때 그 한계가 명확히 드러나는 시점이 올 수도 있습니다.


Column-oriented database

그렇다면 column-oriented는 뭐가 다를까요? 아까 테이블을 다시 봅시다.

id payer amount state created_at updated_at
1 James 100 Completed 2022-12-03 2022-12-03
2 John 200 Cancelled 2022-12-03 2022-12-04
3 Parker 300 Completed 2022-12-04 2022-12-04
4 Anne 400 Cancelled 2022-12-05 2022-12-06
5 Adam 500 Completed 2022-12-09 2022-12-09
6 Mary 600 Requested 2022-12-09 2022-12-09

 

column-oriented의 경우 블락 단위는 대략 아래와 같습니다.

동일한 컬럼의 값 군집이 하나의 블락을 이루며, 각 데이터들에는 어떤 행에 매칭되는 데이터인지 유추할 수 있는 유니크 키(여기선 primary key, 즉 id)가 포함되어 있습니다.

(아래 다이어그램은 단순화되었지만 하나의 컬럼 != 하나의 블락 입니다. 엄연히 블락도 리밋 사이즈가 있기 때문에 하나의 컬럼에 대해 여러 블락이 생성됩니다!)

 

James:1 John:2 Parker:3 Anne:4 Adam:5 Mary:6

 

100:1 200:2 300:3 400:4 500:5 600:6

 

Completed:1 Cancelled:2 Completed:3 Cancelled:4 Completed:5 Requested:6

 

2022-12-03:1 2022-12-03:2 2022-12-04:3 2022-12-05:4 2022-12-09:5 2022-12-09:6

 

2022-12-03:1 2022-12-04:2 2022-12-04:3 2022-12-06:4 2022-12-09:5 2022-12-09:6

 

이제 여기에 아까 row-oriented에 사용했던 쿼리를 순차적으로 사용해 비교해보겠습니다.

SELECT payer FROM payment WHERE state = 'Requested';

 

WHERE 절에 쓰인 것은 state 컬럼입니다. 따라서 우리는 state 컬럼에 대한 블락을 조회하면 됩니다. 그리고 그 중 Requested 값을 지닌 데이터는 id 6을 가리키고 있습니다.

Completed:1 Cancelled:2 Completed:3 Cancelled:4 Completed:5 Requested:6

 

그럼 이제 payer 컬럼에 대한 블락을 조회해 해당 id를 지닌 데이터를 조회하면 원하는 값을 얻을 수 있습니다.

James:1 John:2 Parker:3 Anne:4 Adam:5 Mary:6

 

힌트 없이 모든 데이터를 스캔해야 했던 row-oriented와 비교해보면 효율적으로 보일 수 있습니다만, 이는 단순히 payer라는 단 하나의 컬럼만을 조회하기 때문입니다. 만일 payer 뿐 아니라 state, amount 등 여러 컬럼을 조회하려 한다면 과연 그 때도 효율적이라고 할 수 있을까요?

 

이제 모든 컬럼 값을 조회하는 쿼리를 비교해봅시다.

SELECT * FROM payment WHERE id=6;

 

 

각 데이터마다 id값을 지니고 있기에 찾는게 어려운 건 아닐테지만, 문제는 모든 컬럼을 가지고 와야 하기 때문에 모든 컬럼에 대한 블락을 읽어와야 합니다.

James:1 John:2 Parker:3 Anne:4 Adam:5 Mary:6

 

100:1 200:2 300:3 400:4 500:5 600:6

 

Completed:1 Cancelled:2 Completed:3 Cancelled:4 Completed:5 Requested:6

 

2022-12-03:1 2022-12-03:2 2022-12-04:3 2022-12-05:4 2022-12-09:5 2022-12-09:6

 

2022-12-03:1 2022-12-04:2 2022-12-04:3 2022-12-06:4 2022-12-09:5 2022-12-09:6

 

단 한 번의 I/O만 필요로 했던 row-oriented와 비교해보면 column-oriented에서는 상당량의 I/O 작업이 필요하다는 걸 알 수 있습니다. column-oriented를 사용한다면 SELECT * 와 같은 액션은 되도록 피하는 게 좋겠습니다.

 

이제 column-oriented가 빛을 발할 수 있는 aggregation성 쿼리입니다.

SELECT sum(amount) FROM payment;

 

이미 예상했겠지만 amount라는 하나의 컬럼만 읽어오면 되기 때문에 불필요한 그 외 블락들을 읽어올 필요가 없게 됩니다. 앞선 row-oriented와 비교해보면 상당히 효율적임을 알 수 있습니다.

100:1 200:2 300:3 400:4 500:5 600:6

 


결론

row-oriented database

- 여러 컬럼을 조회하는 쿼리인 경우 효율적이다

- 불필요한 컬럼과 같은 무의미한 데이터들까지 조회해올 수 있다

- 위 이유로 인해 데이터 집합/연산 시 매우 비효율적이다

- 데이터 저장 시 맨 마지막 블락 공간에 행을 추가해주기만 하면 되기에 쓰기가 빠르다

- 대표적인 예: MySQL, Oracle, PostreSQL

 

column-based database

- 특정 컬럼을 조회하는 쿼리인 경우 효율적이다

- 반대로 다수의 컬럼을 조회하는 경우 비효율적이다

- row-oriented와 반대의 이유로 데이터 집합/연산 시 효율적이다

- 데이터 저장 시 각 컬럼들에 해당하는 구조물마다 삽입 처리를 해줘야 해서 쓰기가 느리다

- 대표적인 예: Redshift, BigQuery, Cassandra, PostreSQL (postre는 오픈소스답게 두 옵션을 다 지원하는 듯)

 

'데이터베이스' 카테고리의 다른 글

[데이터베이스]B tree가 뭐죠?  (0) 2021.06.28

관련글 더보기

댓글 영역