[데이터 엔지니어링 디자인 패턴] 08. 데이터 스토리지
[데이터 엔지니어링 디자인 패턴] 08. 데이터 스토리지
데이터 엔지니어링 디자인 패턴 시리즈의 글입니다.
쿼리, 작업을 더 빠르게 할 수 있도록 데이터 스토어 디자인 패턴을 사용해 현명한 데이터 구성을 해보자.
8.1 파티셔닝
데이터셋을 쉽게 접근할 수 있도록 나누는 최선의 방법이 무엇인가?
수평 파티셔너
Horizontal Partitioner Pattern 수평적구성, 가장 일반적으로 사용됨
- 문제
- 나흘의 롤링 집계를 계산하는 배치 잡이 있음
- 실행시간이 오래걸리고있어, 이를 줄일 방법을 찾아야함
- 해결책
- 롤링집계는 데이터셋의 일부만 사용하는 증분데이터 처리의 대표 예시임
- 해결책은 distribution key (분산 키) = 파티셔닝 속성을 식별하는것
- 가장 일반적인것은 시간 기반 파티셔닝
- 시간경계를 정의하여 관련 정보를 저비용으로 쿼리 가능
- 비즈니스 경계도 간혹 파티셔닝 키로 활용됨 (ex. 고객 id, 지역, ..)
- 멱등성의 중요한 구성요소
- 결과
- 세분화와 메타데이터 오버헤드
- 파티션이 너무 많으면 db에 부정적 영향을 끼침
- 구별되는 값이 낮은 cardinality 속성을 사용해야함
- skew
- 수평 파티셔닝이 균등한 데이터를 항상 보장하지는 않음
- skewed partition은 지연 문제의 원인이됨
- micro batch에서 한 파티션이 불균형하면, 그 지연시간이 곧 배치 전체의 지연시간이 됨.
- 가변성
- 파티션 키 변경이 어려움.
- 일부스토어는 다행히 변경을 지원함. (ex. 메타데이터 계층에서만 파티셔닝..)
- 세분화와 메타데이터 오버헤드
수직 파티셔너
Vertical Partitioner pattern
- 문제
- 불변 정보의 저장을 피하고자함
- 이벤트 발생때마다 불변 정보는 한 번만 저장하고자 함
- 해결책
- 수직 파티셔너 패턴을 사용
- 그룹화해야하는 데이터를 분류
- 각 레코드에 대해 그룹화된 속성을 전용 위치에 write
- 데이터 보존 / 접근정책을 쉽게 적용 가능.
- 결과
- 도메인 분할
- 레코드가 분리되어 논리적으로 관련된 속성이 다른곳에 저장될 수 있음
- 양질의 문서화가 필요
- 쿼리
- 도메인을 분할하므로, 전체 그림 파악이 어려워짐
- 문제완화를 위해 모든 테이블을 결합한 구체화된 view를 사용할 수 있음
- 데이터 프로듀서
- 프로듀서에 레코드 분할 로직을 적용하여 복잡도가 높아짐
- 잠재적으로 더 높은 네트워크 통신 비용으로 여러번의 쓰기 수행
- 도메인 분할
8.2 레코드 구성
record co-location에 대한 최적화 적용 수평 파티셔너 패턴의 cardinality 문제 해결
버킷
bucket patter
- 문제
- 쿼리에서 자주 사용되는 특정 비즈니스 속성이 존재
- 이 컬럼을 파티셔닝 컬럼으로 사용하고싶지만 카디널리티가 너무 높은 상황
- 최적화하고자 함
- 해결책
- 쿼리에 자주 관련되는 높은 카디널리티 컬럼이 있다면 bucket pattern이 적절
- bucketing에 사용할 컬럼을 먼저 정의 (=보조 그룹화 키 집합=버킷컬럼)
- 버킷 수 설정
hash(key) % bucket number- 레코드 그룹화
- bucket pruning : 필요한 키가 없는 버킷을 제거할 수 있다
- shuffle 제거 : 저장시점에 join 기준으로 미리 분배하여 셔플(네트워크 교환)없이 결합
- 결과
- 가변성 : 버킷팅 스키마는 불변. 변경하게되면 백필링이 필요한 고비용 작업 필요.
- 버킷크기 : 적절한 크기 찾기가 매우 어려움
정렬기
Sorter Pattern 쿼리와 관련 없는 데이터 블록을 제거하기 위해서는 데이터 저장 순서 역시 중요함
- 문제
- 주간테이블에 데이터를 저장하려고함
- 동시에 데이터 접근 지연을 줄이고자함
- 사용자 쿼리 유형은 알고있는 상태
- 해결책
- 일반적으로 사용되는 컬럼을 안다? ⇒ 정렬, 필터링이 해답
- 테이블 생성 쿼리에서 정렬 컬럼을 선언
- 저장속가 이미 정렬되었으므로,
- 쿼리는 메타데이터 정보를 활용하여 관련없는 데이터 블록을 skip할 수 있음
- 곡선정렬 (Z-order)
- 결과
- 미리 정렬된 데이터셋은 reader에게는 긍정적 / writer 에게는 부정적 영향을 미침
- 정렬되지 않은 세그먼트
- 정렬이 항상 즉각적인 활동은 아님
- 데이터 쓰기 작업 내 or 외부에서 정렬 작업을 예약
- ⇒ 실행시간에 영향을 미칠 수 있음
- 복합 정렬 키
- 쿼리가 대상으로 하는 컬럼보다 앞선 정렬 컬럼을 참조해야함
- 가변성
- 생성 후 정렬키 변경은 비용이 많이 듬
8.3 조회 성능 최적화
데이터 접근을 최적화하기 위해 기존 데이터 구성 기법을 확장
메타데이터 강화기
Meadata Enhancer Pattern 조회성능 최적화를 위해 메타데이터 사용을 할 수 있음
- 문제
- 큰 파티션때문에 클라우드 요금 증가
- 현재는 적재 후 → 필터링 로직
- 필터링 → 적재로 변경하고자함
- 해결책
- 쿼리실행 시간, 비용 최적화의 핵심 = 적제하기 전 관련없는 데이터를 skip하기
- 메타데이터강화기구편은 각 파일에 메타데이터있는 푸터를 포함하여 저장
- 통계는 파일에 국한됨
- 프로듀서 파일 생성시 자동으로 계산 되는 값 범위를 포함
- 추가로 reader 최적화 가능한 추가 메타데이터를 커밋로그에 저장
- 결과
- 오버헤드
- 쓰기시점 통계구축 = writer추가작업
- 처리시간에 영향을 줌
- 통계 최신상태로 유지해야 의미있음
- 오래된 통계문제 해결을 위해 refresh 실행
- 오래된 통계
- 통계가 자동갱신되더라도 즉시 수행은 아님
- 결국 낙후된 통계를 사용하는 문제가 생김
- analyze table 명령어로 통계 refresh가능
- = 읽기 오버헤드 추가
- 오버헤드
데이터셋 구체화기
Dataset Materializer Pattern 읽기성능개선을 위해서는 데이터 중복을 활용하는게 도움이 될 수 있음
- 문제
- 쿼리 단순화를 위해 뷰를 생성했지만
- 뷰가 매번 기본쿼리를 실행하고있음
- 더 나은 성능의 단일 데이터 접근 지점을 제공하고자함
- 해결책
- 결과 계산이 느릴때 간단한 해결책 = 데이터를 구체화하여 문제를 피함
- 데이터셋을 식별 → 구체화를 구현
- 여러 데이터셋을 결합하여 Materialized view or table을 구현
- refresh에 대한 책임을 져야함
- 결과
- refresh비용
- 뷰를 refresh할때마다 쿼리 실행 비용이 듬
- 증분 새로고침으로 완화 가능
- 데이터 접근
- materialized view는 여러 테이블을 결합
- 일관된 데이터 관리가 어려움
- 데이터 스토리지 오버헤드
- 저장소를 위해 최적화를 절충할수있음
- 뷰의 일부만 구체화하고 재계산 가능한부분은 재계산으로 유지하는 혼합구현으로 완화
- refresh비용
매니페스트
Manifest Pattern 데이터 목록 작성을 통해 api 호출을 줄임
- 문제
- 데이터 스토에서 적재할 파일을 목록화하는게 가장 느린 상황
- 해결책
- 파일을 한번만 목록화 하거나 미리 파일명을 기록하고 목록화는 안하는게 좋음 = manifest pattern
- 주어진 transaction 내에서 생성된 파일 목록을 메타데이터 위치에 저장된 commit log에 write
- commit log file은 manifest의 역할을 함
- manifest file = 데이터에 대해 필요하고 중요한 모든 정보를 제공하는 파일
- 결과
- 복잡성
- 실행 flow가 복ㅈ잡해짐
- size
- manifest는 매우 커질수도있음
- 특히 연속 스트리밍작업의 경우.
- maximum limit을 구성해야함
- 복잡성
8.4 데이터 표현
- 어떤 속성들이 함께 저장될것인가?
- 어떤 테이블을 생성할것인가?
정규화기
Normalizer Pattern 디커플링 선호 정보를 중복시키지 않음으로서 일관성 유지에 장점이 있음
- 문제
- 불변속성이 반복되어 갱신 연산이 느려지고있음
- 데이터반복, 느린갱신 문제 해결이 필요
- 해결책
- 정보를 한번만 표현하여 반복을 줄이려고함 = 정규화
- 비즈니스 엔티티 정의
- 비즈니스 엔티티 설명
- 비즈니스 엔티티 간의 관계 정의
- 제1정규형
- 반복되지 않은 원자적 값을 가짐
- 기본키로 고유하게 식별
- 제2정규형
- 기본키에만 의존
- 제3졍규형
- 이행적 의존성이 없음
- 기본키가 아닌 모든컬럼은 기본키에만 의존
- 정보를 한번만 표현하여 반복을 줄이려고함 = 정규화
- 결과
- 복잡성이 매우 크지만, 일관성 유지를 위해 지불해야하는 대가임
- 쿼리비용
- 조인비용이 많이 든다
- 와노하하기 위해 broadcast mode 사용 가능
- archive
- dimension, entity table은 시간에 민감
- SCD기법으로 완화 가능 (slow changing dimension)
역정규화기
Denormalizer Pattern 조인비용을 완화하기위한 패턴
- 문제
- 쿼리실행시간이 지나치게 길어짐
- 원인은 8개테이블 조인
- 해결책
- 쿼리에서 조인을 줄이거나 제거 = 역정규화기 패턴
- 테이블을 평면화하여 네트워크로 데이터 교환이 필요 없도록함
- 일반적인 컬럼 or 중첩구조(json, struct)컬럼으로 구성 가능
- 결과
- 고비용 갱신
- 모든 속성이 중복되어
- 한 레코드 갱신시 여러 레코드를 함께 변경해야함
- 스냅샷으로 간주하여 갱신을 없애면 완화가능
- 스토리지
- 동일정보를 반복저장하므로 공간 낭비가 생김
- 인코딩기법으로 완화가능
- 안티패턴
- 도메인 지향적 로직을 따르지 않으면 안티패턴이 될 수 있음
- 고비용 갱신