콘텐츠로 이동

Row vs Column Oriented Databases

예시 테이블

rowid id first_name last_name ssn salary dob title joined
1001 1 John Smith 111 101,000 1/1/1991 eng 1/1/2011
1002 2 Kary White 222 102,000 2/2/1992 mgr 2/1/2012
1003 3 Norman Freeman 333 103,000 3/3/1993 mkt 3/1/2013
1004 4 Nole Smith 444 104,000 4/4/1994 adm 4/1/2014
1005 5 Dar Sol 555 105,000 5/5/1995 adm 5/1/2015
1006 6 Yan Thee 666 106,000 6/6/1996 mkt 6/1/2016
1007 7 Hasan Ali 777 107,000 7/7/1997 acc 7/1/2017
1008 8 Ali Bilal 888 108,000 8/8/1998 acc 8/1/2018

테스트 쿼리 (인덱스 없음)

SELECT first_name FROM emp WHERE ssn = 666;
SELECT * FROM emp WHERE id = 1;
SELECT SUM(salary) FROM emp;

Row-Oriented Database (행 기반)

  • 테이블이 행(Row) 단위로 디스크에 저장됨
  • 한 번의 블록 IO로 여러 행의 모든 컬럼을 가져옴
  • 특정 행을 찾기 위해 더 많은 IO가 필요하지만, 행을 찾으면 모든 컬럼을 즉시 얻을 수 있음

디스크 저장 구조

[Page 1] 1001,1,John,Smith,111,101000,1/1/1991,eng,1/1/2011 ||| 1002,2,Kary,White,222,102000,2/2/1992,mgr,2/1/2012
[Page 2] 1003,3,Norman,Freeman,333,103000,3/3/1993,mkt,3/1/2013 ||| 1004,4,Nole,Smith,444,104000,4/4/1994,adm,4/1/2014
[Page 3] 1005,5,Dar,Sol,555,105000,5/5/1995,adm,5/1/2015 ||| 1006,6,Yan,Thee,666,106000,6/6/1996,mkt,6/1/2016
[Page 4] 1007,7,Hasan,Ali,777,107000,7/7/1997,acc,7/1/2017 ||| 1008,8,Ali,Bilal,888,108000,8/8/1998,acc,8/1/2018

쿼리 예시 1: SELECT first_name FROM emp WHERE ssn = 666

  • Page 1 읽기 → ssn 666 없음 ❌
  • Page 2 읽기 → ssn 666 없음 ❌
  • Page 3 읽기 → ssn 666 발견! ✅ → Yan 반환
  • 3번의 IO 필요

쿼리 예시 2: SELECT * FROM emp WHERE id = 1

  • Page 1 읽기 → id=1 발견! ✅
  • 모든 컬럼을 이미 가지고 있음 → 즉시 반환
  • 1번의 IO로 충분

쿼리 예시 3: SELECT SUM(salary) FROM emp

  • 모든 페이지를 읽어야 함 (4번의 IO)
  • 각 행에서 salary 값만 필요하지만, 불필요한 다른 컬럼 데이터도 함께 읽힘

Column-Oriented Database (열 기반)

  • 테이블이 열(Column) 단위로 디스크에 저장됨
  • 한 번의 블록 IO로 하나의 컬럼에 대한 여러 행의 값을 가져옴
  • 특정 컬럼의 값을 더 적은 IO로 가져올 수 있음. 하지만 여러 컬럼을 다루려면 더 많은 IO 필요
  • OLAP에 적합

디스크 저장 구조

[id]         1:1001, 2:1002, 3:1003, 4:1004, 5:1005, 6:1006, 7:1007, 8:1008
[first_name] John:1001, Kary:1002, Norman:1003, Nole:1004 ||| Dar:1005, Yan:1006, Hasan:1007, Ali:1008
[last_name]  Smith:1001, White:1002, Freeman:1003, Sol:1004 ||| Thee:1005, Ali:1006, Bilal:1007, Ali:1008
[ssn]        111:1001, 222:1002, 333:1003, 444:1004, 555:1005 ||| 666:1006, 777:1007, 888:1008
[salary]     101000:1001, 102000:1002, 103000:1003, 104000:1004, 105000:1005, 106000:1006, 107000:1007, 108000:1008
[dob]        1/1/1991:1001, 2/2/1992:1002, ... 8/8/1998:1008
[title]      eng:1001, mgr:1002, mkt:1003, adm:1004, adm:1005, mkt:1006, acc:1007, acc:1008
[joined]     1/1/2011:1001, 2/1/2012:1002, ... 8/1/2018:1008
  • 각 값은 값:rowid 형식으로 저장

쿼리 예시 1: SELECT first_name FROM emp WHERE ssn = 666

  1. ssn 컬럼 스캔 → 666:1006 발견 ✅ → rowid = 1006
  2. first_name 컬럼에서 rowid 1006 찾기 → Yan:1006 반환
  3. 2개의 컬럼만 읽으면 됨 (다른 컬럼은 건드리지 않음)

쿼리 예시 2: SELECT * FROM emp WHERE id = 1

  1. id 컬럼에서 1:1001 찾기 ✅
  2. 모든 컬럼에서 rowid 1001에 해당하는 값을 각각 찾아야 함
  3. 모든 컬럼 파일을 읽어야 하므로 많은 IO 필요 ❌

쿼리 예시 3: SELECT SUM(salary) FROM emp

  • salary 컬럼 하나만 읽으면 됨
  • 모든 salary 값이 연속적으로 저장되어 있어 1번의 IO로 충분
  • 매우 효율적

Pros & Cons 비교

항목 Row-Based Column-Based
읽기/쓰기 최적화됨 쓰기가 느림
적합한 워크로드 OLTP OLAP
압축 비효율적 매우 효율적
집계 (Aggregation) 비효율적 매우 효율적
다중 컬럼 쿼리 효율적 비효율적