개발독서/데이터

[고투런1기] 데이터 중심 애플리케이션 설계 (3장 저장소와 검색)

보리시스템 2024. 5. 15.

 

[1부 데이터 시스템의 기초]
2장 저장소와 검색

1. 데이터베이스를 강력하게 만드는 데이터 구조
1.1 해시 색인
1.2 SS테이블과 LSM 트리
1.3 B 트리
1.4 B 트리와 LSM 트리 비교
1.5 기타 색인 구조

2. 트랜잭션 처리나 분석?

2.1 데이터 웨어하우징
2.2 분석용 스키마: 별 모양 스키마와 눈꽃송이 모양 스키마

3. 칼럼 지향 저장소

3.1 칼럼 압축
3.2 칼럼 저장소의 순서 정렬
3.3 칼럼 지향 저장소에 쓰기
3.4 집계: 데이터 큐브와 구체화 뷰

 

[정리: 데이터 중심 애플리케이션 설계를 뒷받침하는 근본 개념을 설명]
*이번 장을 통해 선택한 DB의 문서를 이해할 수 있는 충분한 어휘와 개념을 갖출 수 있음

1. DB가 어떻게 저장과 검색을 다루는지?
- DB에 데이터를 저장할 때 어떤 일이 일어날까?
- 이후 다시 데이터에 질의할 때 DB가 수행하는 작업은 무엇일까?

2. 고수준 저장소 엔진은 OLTP(트랜잭션 처리 최적화)와 OLAP(분석 최적화)라는 큰 2가지 범주로 나뉨 (접근 패턴 간 큰 차이가 있음)
1) OLTP

- 보통 사용자 대면이라 대량의 요청을 받을 수 있음
- 부하 처리 위해 애플리케이션이 각 질의마다 작은 수의 레코드만 다룸
- 애플리케이션은 키의 일부만 사용하는 레코드를 요청하고 저장소 엔진은 요청한 키의 데이터를 찾기 위해 색인을 사용함
*이 경우 대개 디스크 탐색이 병목임

2) OLAP
- 최종 사용자가 아닌 비즈니스 분석가가 주로 사용함(그래서 덜 알려져 있음)
- OLTP 시스템보다 훨씬 더 적은 수의 질의를 다룸
- 각 질의는 매우 다루기 어렵고 짧은 시간에 수백만 개의 레코드를 스캔해야 함
*이 경우는 일반적으로 디스크 대역폭이 병목임
- 칼럼 지향 저장소는 이런 작업부하를 처리할 때 사용 가능한 솔루션임

3. OLTP 측면에서 2가지 주요한 관점
1) 로그 구조화 관점
- 파일 추가와 오래된 파일의 삭제만 허용하고 한 번 쓰여진 파일은 절대 갱신하지 않음
- 그 예로 비트캐스크, SS테이블, LSM 트리, 레벨DB, 카산드라, HBase, 루씬 등이 있음
- 로그 구조화 저장소 엔진은 비교적 최근에 개발된 것임
=> 핵심 아이디어는 임의 접근 쓰기를 체계적으로 디스크에 순차 쓰기로 바꾼 것
=> 하드드라이브와 SSD의 성능 특성에 맞춰 쓰기 처리량을 높이는 것이 가능함

2) 제자리 갱신 관점
- 덮어쓰기 할 수 있는 고정 크기 페이지의 셋으로 디스크를 다룸
- 그 예로 B 트리가 있음. B 트리는 모든 주요 관계형 DB와 많은 비정형 DB에서도 사용함

4. 더 복잡한 색인 구조와 모든 데이터를 메모리에 유지하기 위해 최적화된 DB

5. 일반적인 데이터 웨어하우스의 고수준 아키텍쳐를 보기 위해 저장소 엔진의 내부를 알아봄
- 이는 왜 분석 작업부하가 OLTP와 많이 다른지 설명해줌
- 질의가 많은 수의 로우를 순차적으로 스캔해야 한다면 색인을 사용하는 방버은 적절하지 않음
- 대신 질의가 디스크에서 읽는 데이터 양을 최소화하기 위해 데이터를 매우 작게 부호화하는 일이 중요해짐
- 칼럼 지향 저장소가 이 목표를 달성하는 데 어떻게 도움이 되는지 알아봄 

 


 

3장 저장소와 검색

- 관계형 DB, NoSQL DB에 사용되는 저장소 엔진
- 로그 구조(log-structured) 계열 저장소 엔진
- 페이지 지향(page-oriented) 계열 저장소 엔진

 


 

1. 데이터베이스를 강력하게 만드는 데이터 구조

- 색인은 어떤 부가적인 메타데이터를 유지하는 것으로 메타데이터는 이정표 역할을 하여 원하는 데이터의 위치를 찾는 데 도움을 줌
- 색인은 기본 데이터(primary data)에서 파생된 추가적인 구조
=> 많은 DB는 색인의 추가, 삭제를 허용하고 이는 DB 내용에 영향을 미치지 않고, 질의 성능에만 영향을 줌

- 색인은 대개 쓰기 속도를 느리게 함
=> 데이터를 쓸 때마다 미번 색인도 갱신해야 하기 때문

 


 

1.1 해시 색인

- 키-값 저장소는 대부분의 프로그래밍 언어에서 볼 수 있는 사전 타입(dictionary type)과 매우 유사
=> 보통 해시 맵(hash map)으로 구현

- 단순히 파일에 추가하는 방식으로 데이터 저장소를 구성한다고 했을 때의 가장 간단한 색인 전략으로는?
=> 키를 데이터 파일의 바이트 오프셋에 매핑해 인메모리 해시 맵을 유지하는 전략
=> 파일에 새로운 키-값 쌍을 추가할 때마다 방금 기록한 데이터의 오프셋을 반영하기 위해 해시 맵도 갱신해야 함
=> 값을 조회하려면 해시 맵을 사용해 데이터 파일에서 오프셋을 찾아 해당 위치를 구하고 값을 읽음
=> 각 키의 값이 자주 갱신되는 상황에 매우 적합한 방식임

- 하지만 파일에 추가만 한다면 결국 디스크 공간이 부족해짐
=> 특정 크기의 세그먼트로 로그를 나누는 방식으로 해결할 수 있음
=> 특정 크기가 되면 세그먼트 파일을 닫고 새로운 세그먼트 파일에 이후의 쓰기 작업을 수행

- 세그먼트 파일들에 대해 컴팩션(compaction)을 수행할 수 있음
=> 컴팩션? 로그에서 중복된 키를 버리고 각 키의 최신 갱신 값만 유지하는 것
=> 실제 구현 시 중요한 문제들로는?
1) 파일 형식: 바이너리 형식(바이트 단위의 문자열 길이를 부호화하고 원시 문자열을 부호화함)이 더 빠르고 간단(CSV는 로그에 가장 적합한 형식은 아님)
2) 레코드 삭제: 키 관련 값 삭제하기 위해서는 데이터 파일에 특수한 삭제 레코드(툼스톤, tombstone)를 추가해야 함
3) 고장 복구: DB 재시작 시 인메모리 해시 맵은 손실 됨. 전체 세그먼트 파일을 처음부터 끝까지 읽고 각 키에 대한 최신 값의 오프셋을 확인해서 각 세그먼트 해시 맵을 복원할 수 있음
4) 부분적으로 레코드 쓰기: DB는 로그에 레코드 추가하는 도중에도 죽을 수 있음. 비트캐스트 파일은 체크섬을 포함해 로그의 손상 부분을 탐지해 무시할 수 있음
5) 동시성 제어: 쓰기를 엄격하게 순차적으로 로그에 추가할 때 일반적인 구현방법은 하나의 쓰기 스레드만 사용하는 것임. 데이터 파일 세그먼트는 추가 전용이거나 불변이므로 다중 스레드로 동시에 읽기를 할 수 있음

- 추가 전용 로그의 장점
1) 무작위 쓰기 작업보다 훨씬 빠름 (추가와 세그먼트 병합은 순차적인 쓰기 작업이기 때문)
2) 동시성과 고장 복구 간단함 (이전 값과 새로운 값을 둘다 가지고 있기 때문)
3) 세그먼트 병합을 통해 데이터 파일의 조각화 문제를 방지

- 해시 색인의 제한 사항
1) 키가 너무 많으면 문제가 됨 (디스크에 해시 맵을 유지할 수 있지만 디스크 상의 해시 맵에 좋은 성능은 기대하기 어려움)
2) 범위 질의(range query)에 효율적이지 않음 (범위 내의 모든 개별 키를 조회해야 함)

 


 

1.2 SS테이블과 LSM 트리

- SS테이블(정렬된 문자열 테이블 Sorted String Table)? 키로 정렬 된 형식
=> 각 키는 각 병합된 세그먼트 파일 내에 한 번만 나타나야 함

- SS테이블 장점?
1) 세그먼트 병합은 파일이 사용 가능한 메모리보다 크더라도 간단하고 효율적임
=> 병합정렬(mergesort) 알고리즘에서 사용하는 방식과 유사
2) 파일에서 특정 키를 찾기 위해 메모리에 모든 키의 색인을 유지할 필요가 없음
3) 디스크 공간 절약 및 I/O 대역폭 사용 줄임
=> 읽기 요청은 요청 범위 내에서 여러 키-값 쌍을 스캔해야 하기 때문에 해당 레코드들을 블록으로 그룹화하고 디스크에 쓰기 전에 압축함

 

  • SS테이블 생성, 유지
- 쓰기가 들어오면 인메모리 균형 트리(멤테이블, memtable) 데이터 구조에 추가
- 멤테이블이 보통 수 메가바이트 정도의 임곗값보다 커지면 SS테이블 파일로 디스크에 기록함
- 읽기 요청을 제공하려면 먼저 멤테이블에서 키를 찾고, 그 다음 디스크 상의 가장 최신 세그먼트에서 찾음
- 가끔 세그먼트 파일을 합치고 덮어 쓰여지거나 삭제된 값을 버리는 병합과 컴팩션 과정을 수행함(백그라운드에서 수행됨)

- 발생 가능 문제
1) DB가 고장나는 경우 디스크로 기록되지 않고 멤테이블에 있는 가장 최신 쓰기가 손실됨
2) 문제 방지를 위해 매번 쓰기를 즉시 추가할 수 있게 분리된 로그를 디스크 상에 유지해야 함
3) 이 로그는 손상 후 멤테이블 복원 시에만 필요하기 때문에 순서는 정렬되지 않아도 됨
4) 멤테이블을 SS테이블로 기록하고 나면 해당 로그는 버릴 수 있음
  •  

 

  • SS테이블에서 LSM 트리 만들기
- LSM 트리? = 로그 구조화 병합 트리(Log-Structured Merge-Tree)
=> 정렬된 파일 병합과 컴팩션 원리를 기반으로 하는 저장소 엔진

- 엘라스틱서치, 솔라에서 사용하는 전문 검색 색엔 엔진인 '루씬(Lucene)'이 용어 사전을 저장하기 위해 유사한 방법을 사용함
=> 검색 질의로 단어가 들어오면 단어가 언급된 모든 문서를 찾음
=> 이 접근법은 키를 단어(용어)로, 값은 단어를 포함한 모든 문서의 ID 목록(포스팅 목록 등)으로 하는 키-값 구조로 구현함

 

  • 성능 최적화
- LSM 트리 알고리즘은 DB에 존재하지 않는 키를 찾는 경우 느릴 수 있음
=> '블룸 필터(Bloom filter)'를 추가적으로 사용해 최적화함

- SS테이블을 압축하고 병합하는 순서와 시기를 결정하는 다양한 전략이 있음
1) 크기 계층(size-tiered)
2) 레벨 컴팩션(leveled compaction)

- LSM 트리의 기본 개념은 백그라운드에서 연쇄적으로 SS테이블을 지속적으로 병합하는 것임
=> 데이터셋이 가능한 메모리보다 훨씬 더 크더라도 여전히 효과적임
=> 데이터가 정렬된 순서로 저장돼 있다면 범위 질의를 효율적으로 실행할 수 있음
=> 이 접근법의 디스크 쓰기는 순차적이기 때문에 LSM 트리가 매우 높은 쓰기 처리량을 보장할 수 있음

 


 

1.3 B 트리

- B트리는 1970년대 등장해 10년도 안 돼 '보편적인 색인 구조'가 됨
- SS테이블과 같이 키로 정렬된 키-값 쌍을 유지하기 때문에 키-값 검색과 범위 질의에 효율적임
=> SS테이블과 설계 철학은 매우 다름
=> 전통적으로 4KB 크기의 고정 크기 블록이나 페이지로 나누고 한 번에 하나의 페이지에 읽기 또는 쓰기를 함
=> 디스크가 고정 크기 블록으로 배열되기 때문에 이런 설계는 근본적으로 하드웨어와 조금 더 밀접한 관련이 있음

- B 트리 색인을 이용한 키 검색
-> 각 페이지는 주소나 위치를 이용해 식별할 수 있음
-> 한 페이지는 B 트리의 루트로 지정되어 색인에서 키를 찾으려면 루트에서 시작함
-> 페이지는 여러 키와 하위 페이지의 참조를 포함해 각 하위 페이지는 키가 계속 이어지는 범위를 담당하고 참조 사이의 키는 해당 범위 경계가 어디인지 나타냄
-> 최종적으로 개별 키(리프 페이지, leaf page)를 포함하는 페이지에 도달함
-> 이 페이지는 각 키의 값을 포함하거나 값을 찾을 수 있는 페이지의 참조를 포함함

- 분기 계수(branching factor)? B 트리의 한 페이지에서 하위 페이지를 참조하는 수
=> 분기 계수는 페이지 참조와 범위 경계를 저장할 공간의 양에 의존적이고, 보통 수백 개에 달함

- B 트리에 존재하는 키의 값 갱신
-> 키를 포함하고 있는 리프 페이지를 검색하고 페이지의 값을 바꾼 다음 페이지를 디스크에 다시 기록함
-> 새로운 키를 추가하려면 새로운 키를 포함하는 범위의 페이지를 찾아 해당 페이지에 키와 값을 추가함
-> 새로운 키를 수용한 페이지에 충분한 여유 공간이 없다면 페이지 하나를 반쯤 채워진 페이지 둘로 나누고 상위 페이지가 새로운 키 범위의 하위 부분들을 알 수 있게 갱신함
=> 이 알고리즘은 트리가 계속 균형을 유지하는 것을 보장함

 

  • 신뢰할 수 있는 B 트리 만들기
- DB 고장 상황에서 스스로 복구할 수 있게 만들려면 일반적으로 디스크 상에 쓰기 전 로그(재실행 로그)라는 데이터 구조를 추가해 B트리를 구현함
=> 쓰기 전 로그는 트리 페이지에 변경된 내용을 적용하기 전에 모든 B 트리의 변경 사항을 기록하는 추가 전용 파일임
=> 이 로그는 DB가 고장 이후 복구될 때 일관성 있는 상태로 B 트리를 다시 복원하는 데 사용함

- 다중 스레드가 동시에 B 트리에 접근할 때에는 주의 깊게 동시성 제어를 해야 함
=> 그렇지 않으면 스레드가 일관성이 깨진 상태의 트리에 접근할 수 있음
=> 동시성 제어는 보통 래치(latch, 가벼운 잠금 lock)로 트리의 데이터 구조를 보호함

 

  • B 트리 최적화
- 최적화 기법
1) 페이지 덮어 쓰기, 고장 복구를 위한 WAL 유지 대신 일부 DB는 쓰기 시 복사 방식을 사용
2) 페이지에 전체 키가 아닌 키를 축약해 공간을 절약
3) 리프(leaf) 페이지를 디스크 상에 연속된 순서로 나타나게끔 트리를 배치
4) 트리에 포인터를 추가함

 


 

1.4 B 트리와 LSM 트리 비교

- LSM 트리는 쓰기, B 트리는 읽기에서 더 빠름
=> 읽기가 LSM 트리에서 더 느린 이유? 각 컴팩션 단계에 있는 여러 테이블 구조와 SS테이블을 확인해야 하기 때문

 

  • LSM 트리의 장점
- LSM 트리는 B 트리보다 쓰기 처리량을 높게 유지할 수 있음
=> 순차적으로 컴팩션된 SS테이블 파일을 쓰기 때문임

- 압축률이 더 좋아 디스크에 더 적은 파일을 생성함
=> LSM 트리는 페이지 지향적이지 않고 주기적으로 파편화를 없애기 위해 SS테이블을 다시 기록하기 때문에 저장소 오버헤드가 더 낮음
=> 레벨 컴팩션(leveled compaction)을 사용하면 더욱 그러함

 

  • LSM 트리의 단점
- 컴팩션 과정이 때로는 진행 중인 읽기와 쓰기의 성능에 영향을 줌
=> 디스크가 가진 자원은 한계가 있기 때문에 디스크에서 비싼 컴팩션 연산이 끝날 때까지 요청이 대기해야 하는 상황이 발생하기 쉬움

- 높은 쓰기 처리량 시 컴팩션 설정을 주의 깊게 하지 않으면 컴팩션이 유입 쓰기 속도를 따라갈 수 없어 디스크 상에 병합되지 않은 세그먼트 수는 디스크 공간이 부족할 때까지 증가함

 

  • B 트리의 장점
- 각 키가 색인의 한 곳에만 정확하게 존재함
=> 강력한 트랜잭션 시맨틱을 제공하는 DB에는 B 트리가 적합
=> B 트리 색인에서는 트리에 직접 잠금을 포함함 (다른 관계형 DB에서 트랜잭션 격리는 키 범위의 점금을 사용해 구현함)

 

  • B 트리의 단점
- B 트리 색인은 모든 데이터 조각을 최소한 두 번 기록해야 함
=> 쓰기 전 로그 한 번, 트리 페이지에 한 번
=> 해당 페이지 내 몇 바이트만 바뀌어도 한 번에 전체 페이지를 기록해야 하는 오버헤드가 있음

 


 

1.5 기타 색인 구조

 

  • 색인 안에 값 저장하기
- 색인에서 키는 질의가 검색하는 대상이지만, 값은 다음의 두 가지 중에 하나에 해당
1) 값은 질문의 실제 로우
2) 다른 곳에 저장된 로우를 가리키는 참조

- 다른 곳을 가리키는 참조가 가리키는 곳(힙 파일, heap file)
1) 특정 순서 없이 데이터를 저장
2) 여러 보조 색인이 존재할 때 데이터 중복을 피할 수 있음
3) 각 색인은 힙 파일에서 위치만 참조하고 데이터는 일정한 곳에 유지함
=> 키를 변경하지 않고 값만 갱신할 때 효과적이다.
=> 변경 될 데이터의 크기가 기존보다 작거나 같다면 record 를 그 자리에 덮어 쓸 수 있지만, 크다면 새로운 곳으로 위치를 이동해야 하기 때문에 더 복잡함
=> 레코드의 새로운 힙 위치를 가리키게끔 갱신하거나 이전 힙 위치에 포인터를 남겨둬야 하기 때문
=> 색인에서 힙 파일로 다시 이동하는 일은 읽기 성능에 불이익이 너무 많기 때문에, 어떤 상황에서는 색인 안에 바로 색인된 로우를 저장하는 편이 바람직함(클러스터드 색인, clustered index)

 

  • 다중 칼럼 색인
- 다중 컬럼에 동시에 질의할 때 결합 색인(concatenated index)을 사용
=> 하나의 컬럼에 다른 컬럼을 추가하는 방식으로 하나의 키에 여러 필드를 결합
=> 다차원 색인은 지리 공간 데이터에 중요하게 사용되는데, 경위도에 대해 이차원 범위 질의가 필요

- B 트리나 LSM 는 이러한 색인 유형에 효율적으로 응답할 수 없어 아래의 방법을 사용
1) 2차원 위치를 공간 채움 곡선(space-filling curve) 을 이용해 단일 숫자로 변환하여 B 트리 색인
2) [좀 더 일반적인 방식] PostGIS와 같이 R 트리처럼 전문 공간 색인(specialized spatial index)을 사용

 

  • 전문 검색과 퍼지 색인
- 철자가 틀린 단어와 같이 유사한 혹은 애매모호한(fuzzy) 질의에는 다른 기술이 필요
=> 전문 검색 엔진은 단어를 검색할 때 단어의 동의어로 질의를 확장함
=> 루씬은 문서나 질의의 오타에 대처하기 위해 특정 편집 거리(edit distance) 내 단어를 검색할 수 있는 기능이 있음

- SS테이블은 인메모리 색인이 필요한데 루씬에서 인메모리 색인은 여러 키 내 문자에 대한 유한 상태 오토마톤으로 트라이(trie)와 유사한 메모리 색인을 사용함
- 그밖에 퍼지 검색 기술은 문서 분류 및 머신머닝의 방향으로 진행되고 있음

 

  • 모든 것을 메모리에 보관
- 디스크는 메인 메모리보다 비교해 다루기 어려움
=> 디스크와 SSD를 사용할 때 읽기 쓰기에 좋은 성능을 원한다면, 주의해서 데이터를 디스크(HDD, SSD)에 배치해야 함
=> 이러한 불편함에도 불구하고 디스크를 선택하는 이유는 지속성과 가격 때문
* 램(ram)이 점점 저렴해지기 때문에 가격 논쟁은 약해짐

- 이런 이유로 인메모리 데이터베이스가 개발됨
=> 맴캐시드 같은 경우는 데이터 손실을 허용하는 캐시 용도로만 사용되지만, 다른 인메모리 DB는 지속성을 목표로 하여 배터리 전원을 공급 RAM 과 같은 특수 장비를 사용하거나 디스크를 함께 사용하여 주기적인 snapshot을 만드는 방식으로 지속성 문제를 해결함

- 인메모리 DB가 재시작 되는 경우 특수 장비를 사용하지 않는다면 지속성을 위한 추가 전용(append-only) 로그와 함께 사용
=> 인메모리는 디스크에 데이터가 저장되더라도 읽기는 전적으로 메모리에서 제공됨
=> 디스크 상의 파일은 쉽게 백업이 가능하고, 외부 유틸을 사용해 검사와 분석도 가능함

- 성능 이외에도 인메모리 DB는 디스크 기반의 색인이 제공하지 못하는 데이터 모델을 제공함
=> 레디스는 우선 순위 큐와 셋(set) 같은 데이터 구조를 데이터베이스의 인터페이스로 제공하기 때문에 구현이 간단함
=> 안티 캐싱(anti-caching)은 메모리가 충분하지 않을 때 사용하는데 최근에 사용하지 않는 데이터를 디스크로 내보내고 나중에 다시 접근할 때 메모리에 적재하는 방식으로 동작함
=> 이는 운영 체제가 가상 메모리와 swap 파일에서 수행하는 방식이 유사하지만, 데이터베이스는 전체 메모리 페이지보다 개별 레코드 단위로 작업하기 때문에 OS 보다 더 효율적으로 메모리를 관리할 수 있음
* 하지만 이 접근 방식은 여전히 전체 색인이 메모리에 있어야 함

 


 

2. 트랜잭션 처리나 분석?

- 초창기 비즈니스 모델은 논리 단위의 형태로 읽기와 쓰기 그룹을 나타내는 커머셜 트랜잭션(commercial transaction, 상거래)에 해당함

- 온라인 트랜잭션 처리(OnLine Transaction Processing, OLTP)
=> 레코드가 사용자 입력을 기반으로 삽입되거나 갱신됨

- 온라인 분석 처리(OnLine Analytic Processing, OLAP)
=> 데이터 분석 용도 많은 수의 레코드를 스캔해 레코드당 일부 칼럼만 읽어 집계 통계를 계산

- 처음에는 트랜잭션 처리와 분석 질의를 위해 동일한 데이터베이스를 사용함
=> 개별 데이터베이스를 데이터 웨어하우징(Data warehouse)라고 했음

 

  • 트랜잭션 처리와 분석 시스템의 특징 비교
특성 트랜잭션 처리 시스템(OLTP) 분석 시스템(OLAP)
주요 읽기 패턴 질의당 적은 record, 키 기준 fetch 많은 record 에 대한 집계
주요 쓰기 패턴 임의 접근, 사용자 입력을 낮은 지연 시간 대규모 불려오기(bulk import, ETL), 이벤트 스트림(event stream)
주요 사용처 웹 앱, 사용자, 소비자  의사 결정을 위한 내부 분석가
데이터 표현 데이터의 최신 상태 시간이 지나며 일어난 이밴트 이력
데이터셋 크기 기가바이트에서 테라바이트 테라바이트에서 페타바이트

 


 

2.1 데이터 웨어하우징

- OLTP 작업에 영향을 주지 않고 마음껏 질의할 수 있는 개별 데이터베이스
=> 회사 내의 모든 다양한 OLTP 시스템에 있는 데이터의 읽기 전용 복사본
=> 개별 데이터 웨어하우스를 사용하면 분석 접근 패턴에 맞게 최적화 가능

- ETL(Extract-Transform-Load)

=> OLTP 데이터베이스에서 데이터를 추출(extract)하고
=> 분석 친화적인 스키마로 변환(transform)하고
=> 데이터 웨어하우스에 적재(load)함

 

  • OLTP 데이터베이스와 데이터 웨어하우스의 차이점
- 표면적으로 데이터 웨어하우스와 관계형 OLTP DB는 둘 다 SQL 질의 인터페이스를 지원하기 떄문에 비슷해보임
=> 하지만 각각 매우 다른 질의 패턴에 맞게 최적화됐기 때문에 시스템의 내부는 완전히 다름

- 공통 SQL 인터페이스로 접근할 수 있는 저장소와 질의 엔진으로 점점 분리되고 있음

- 상용 라이선스 데이터 웨어하우스 벤더
=> 테라데이터(Teradata), 버티카(Vertica), SAP 하나파르에이셀(ParAccel)

- 오픈소스 SQL 온 하둡(SQL-on-Hadoop)
=> 아파치 하이브(Apache Hive), 스파크 SQL(Spark SQL), 클라우데라 임팔라(Cloudera Impala), 페이스북 프레스토(Facebook Presto), 아파치 타조(Apache Tajo), 아파치 드릴(Apache Drill)

 


 

2.2 분석용 스키마: 별 모양 스키마와 눈꽃송이 모양 스키마

- 별 모양 스키마(star schema, 차원 모델링 dimensional modeling)로 알려진 정형화된 방식을 사용함
=>사실 테이블(fact table)이 가운데에 있고 차원 테이블로 둘러싸고 있는 모양임

- 사실 테이블(fact table)
=> 특정 시각에 발생한 이벤트(제품 구매) 사실 테이블의 다른 컬럼은 차원 테이블(dimension table)이라 부르는 다른 테이블을 가리키는 외래 키

- 눈꽃송이 모양 스키마(snowflake schema)
=> 별 모양 스키마의 변형임
=> 차원이 하위차원으로 더 세분화됨
* 질의를 수행하기 위해서 더 많은 조인을 필요 → 검색의 효과를 감소 → 시스템의 성능에 악영향을 끼칠 수 있음
=> 데이터웨어하우스는 설계에 있어서 스타스키마 만큼 널리 쓰이지 않음


 

3. 칼럼 지향 저장소

-데이터 웨어하우스의 사실 테이블에는 엄청난 개수의 로우의 페타바이트 데이터가 있다면 효율적으로 저장하고 질의하기에는 어려운 문제가 있음

- 컬럼 지향 저장소의 개념은 모든 값을 하나의 로우에 저장하지 않고 모든 값(column)을 함께 저장함
=> 각 칼럼은 개별 파일에 저장하면 질의에 필요한 칼럼만 읽고 구문 분석할 수 있음
=> 각 칼럼 파일에 포함된 로우가 모두 순서가 같아야 함

 


 

3.1 칼럼 압축

- 데이터를 압축하면 디스크 처리량을 줄일 수 있음
=>
컬럼 저장소는 대개 압축에 적합함

- 컬럼의 데이터에 따라 다양한 압축 기법을 사용할 수 있음
=> 데이터 웨어하우스에 효과적인 압축 중 비트맵 부호화(bitmap encoding)

- B-tree 가 가진 문제점을 해결하기 위해 비트맵 인덱스 등장
=> B-tree 인덱스에서는 실제 칼럼 값을 보관: 대용량 데이터를 관리에는 부담
=> 결합 인덱스에서 조건을 사용하지 않는 칼럼이나 =(equals) 조건이 아닌 칼럼이 결합 인덱스 중간에 있으면 액세스 효율이 떨어짐
=> 다양한 액세스 패턴을 수용하기 위해 많은 인덱스가 필요할 수 있음
=> NOT 이나 NULL을 사용하거나 복잡한 OR 조건에서는 인덱스의 성능을 보장받지 못함

 

  • 메모리 대역폭과 백터화 처리
- 수백만 로우를 스캔해야 하는 데이터 웨어하우스 질의는 디스크로부터 메모리로 데이터를 가져오는 대역폭이 큰 병목임
=> 분석용 DB 개발자는 메인 메모리에서 CPU 캐시로 가는 대역폭을 효율적으로 사용함
* 클럭: 데이터를 보내는 빠르기 / 대역폭: 한 번에 전송되는 데이터의 양
=> CPU 명령 처리 파이프라인에서 분기 예측 실패(branch misprediction)와 버블(bubble)을 피해야 함
* 다음 실행될 조건문이 어떤 곳으로 분기할 것인지를 확실히 알게 되기 전에 미리 추측하여 실행하여 파이프라인 효율성 확보
* 버블: 빈 공간

- 최신 CPU에서 단일 명령 다중 데이터(Single Instruction Multi Data, SIMD) 명령을 사용하게끔 신경 써야 함
=> SIMD: CPU에서 지원되는 명령어 셋으로 하나의 명령어로 동일한 형태/구조의 여러 데이터를 한 번에 처리하는 병렬처리기법
=> SISD(Single Instruction Single Data)
=> 4번의 연산과 1번의 연산

- 벡터화 처리
=> 한 번에 처리하는 데이터의 양을 늘려서 CPU 사용률을 높이고 처리속도를 빠르게 하는 기법 비트 AND와 OR같은 연산자는 압축된 칼럼 데이터 덩어리를 바로 연산할 수 있게 설계할 수 있음

 


 

3.2 칼럼 저장소의 순서 정렬

- 로우가 저장되는 순서가 중요하지는 않음
=> 하지만 각 칼럼은 독립적으로 정렬하면 안되고 한 칼럼의 k번째 항목이 다른 칼럼 k번째 항목과 같은 로우에 속해야 함
=> 그룹화, 필터링 용이
=> 칼럼 압축에 도움

* 기본 정렬 칼럼은 연속해서 같은 값이 연속해서 길게 반복됨
* 간단한 런 렝스 부호화는 수십억 개의 로우를 가진 테이블이라도 수 킬로바이트로 칼럼을 압축할 수 있음

 

  • 다양한 순서 정렬
- 데이터 복원력을 위해 데이터를 여러 장비에 복제
=> 데이터마다 서로 다른 방식으로 정렬해서 저장 → 질의를 처리할 때 패턴에 적합한 버전 사용 가능

- 컬럼 지향 저장에서 여러 정렬 순서를 갖는 것은 로우 지향 저장에서 2차 색인을 갖는 것과 약간 비슷함
=> 로우 지향 저장한 곳(힙 파일이나 클러스터 색인)에 모든 로우를 유지하고 2차 색인은 일치하는 로우를 가리키는 포인터만 포함
=> 컬럼 저장 지장일반적인 데이터를 가리키는 포인터가 없고, 단지 값을 포함한 컬럼만 존재함

 


 

3.3 칼럼 지향 저장소에 쓰기

칼럼 지향 저장소, 압축, 정렬은 모두 읽기에 더 빠름
=>
제자리 갱신(update-in-place) 불가

- 쓰기를 위한 해결책은 LSM 트리 구조가 적절함
=> 쓰기 → 인메모리 저장소로 이동해 정렬된 구조에 추가 → 디스크에 쓸 준비
=> 디스크의 칼럼 파일에 병합하고 → 대량으로 새로운 파일에 기록

 


 

3.4 집계: 데이터 큐브와 구체화 뷰

- 구체화 집계(materialized aggregate)
=> 데이터 웨어하우스 질의는 보통 SQL에 COUNT, SUM, AVG, MIN, MAX 같은 집계 함수를 포함함
=> 동일한 집계를 많은 많은 질의에서 사용한다면 매번 원시 데이터를 처리하는 일은 낭비임
=> 질의 자주 사용하는 일부 카운트(count)나 합(sum)을 캐시하는 하는 방법중 하나는 구체화 뷰(materialized view)임

- 구체화 뷰는 원본데이터의 복사본
=> 원본 데이터를 변경하면 구체화 뷰를 갱신해야 함 → DB가 자동으로 수행
=> 비용이 비싸기 때문에 OLTP에서는 사용하지 않음
=> 데이터 웨어하우스는 읽기 비중이 크기 때문에 합리적
=> 데이터 큐브(data cube) 또는 OLAP 큐브라고 알려진 구체화 뷰는 일반화된 구체화 뷴의 특별한 사례임

- 구체화 데이터 큐브
1) 장점
=> 특정 질의를 효과적으로 미리 계산했기 때문에 해당 질의를 수행할 때 매우 빠름

2) 단점
=> 원시 데이터의 질의하는 것과 동일한 유연성이 없음
=> 포함되지 않은 차원을 기준으로 집계를 할 수 없음
=> 데이터 재적재해야 함

- 데이터 큐브와 같은 집계 값은 특정 질의에 다한 성능 향상에만 사용함