HFile 형식
HFile 형식 (HFile Format)
HBase가 데이터를 저장하는 저수준 파일 형식인 HFile의 구조를 설명하는 페이지예요. 버전 1, 2, 3의 차이와 블록 인덱스, 블룸 필터, 트레일러 구조를 이해하는 데 도움이 됩니다.
출처: 문서
본문
HBase 파일 형식 (버전 1) (HBase File Format (version 1))
HFile 형식의 변경을 논의할 것이므로 원본(HFile 버전 1) 형식에 대한 짧은 개요를 드리는 것이 유용해요.
버전 1 개요 (Overview of Version 1)
버전 1 형식의 HFile은 다음과 같이 구조화돼요.
(다이어그램)
버전 1의 블록 인덱스 형식 (Block index format in version 1)
버전 1의 블록 인덱스는 매우 직관적이에요. 각 항목에 대해 다음을 포함해요.
- Offset (long)
- Uncompressed size (int)
- Key (Bytes.writeByteArray로 작성된 직렬화된 바이트 배열)
- Key 길이(가변 길이 정수, VInt)
- Key 바이트
블록 인덱스의 항목 수는 고정 파일 트레일러에 저장되며, 블록 인덱스를 읽는 메서드에 전달해야 해요. 버전 1 블록 인덱스의 제한 중 하나는 블록의 압축 크기를 제공하지 않는다는 것이고, 이것이 압축 해제에 필요하다는 것이 밝혀졌어요. 따라서 HFile reader는 블록 사이의 오프셋 차이에서 이 압축 크기를 추론해야 해요. 우리는 버전 2에서 이 제한을 수정하는데, uncompressed size 대신 on-disk block size를 저장하고 블록 헤더에서 uncompressed size를 얻어요.
인라인 블록이 있는 HBase 파일 형식 (버전 2) (HBase file format with inline blocks (version 2))
메모: 이 기능은 HBase 0.92에서 도입됐어요.
동기 (Motivation)
우리는 region server에서 큰 블룸 필터와 블록 인덱스로 인한 높은 메모리 사용과 느린 시작 시간을 겪은 뒤 HFile 형식을 재검토해야 한다는 것을 발견했어요. 블룸 필터는 HFile당 100 MB까지 커질 수 있고, 20개 region에 걸쳐 합산하면 2 GB까지 늘어나요. 블록 인덱스는 같은 region 집합에 대해 합산 크기 6 GB까지 커질 수 있어요. region은 모든 블록 인덱스 데이터가 로드될 때까지 열린 것으로 간주되지 않아요. 큰 블룸 필터는 다른 성능 문제를 일으켜요: 블룸 필터 조회가 필요한 첫 get 요청은 전체 블룸 필터 비트 배열을 로드하는 지연 시간을 감수해야 해요.
region server 시작을 빠르게 하기 위해 블룸 필터와 블록 인덱스를 여러 블록으로 쪼개고 그것들이 차오르는 대로 그 블록들을 작성해요. 이는 HFile writer의 메모리 사용량도 줄여요. 블룸 필터의 경우 "블록 채우기"는 고정 크기 비트 배열을 효율적으로 활용하기에 충분한 키를 축적하는 것을 의미하고, 블록 인덱스의 경우 원하는 크기의 "인덱스 블록"을 축적해요. 블룸 필터 블록과 인덱스 블록(우리는 이들을 "인라인 블록"이라고 불러요)은 데이터 블록과 섞여 배치되며, 부작용으로 버전 1에서 했던 것처럼 블록 오프셋의 차이에 의존해 데이터 블록 길이를 결정할 수 없게 돼요.
HFile은 설계상 저수준 파일 형식이며, StoreFile 수준에서 처리되는 블룸 필터 같은 애플리케이션 특정 세부 사항을 다루지 않아야 해요. 따라서 우리는 HFile의 블룸 필터 블록을 "인라인" 블록이라고 불러요. 또한 HFile에 이러한 인라인 블록을 작성하는 인터페이스를 제공해요.
region server 시작 시간을 줄이기 위한 또 다른 형식 변경은 HFile이 열릴 때 메모리에 로드해야 하는 연속된 "load-on-open" 섹션을 사용하는 것이에요. 현재 HFile이 열리면 트레일러, 데이터/meta 인덱스, 파일 정보를 읽기 위한 별도의 seek 연산이 있어요. 블룸 필터를 읽으려면 "data"와 "meta" 부분에 대한 두 번 더 seek 연산이 있어요. 버전 2에서는 트레일러를 읽기 위해 한 번 seek하고, 연속된 블록에서 파일을 여는 데 필요한 다른 모든 것을 읽기 위해 다시 한 번 seek해요.
버전 2 개요 (Overview of Version 2)
위 기능을 도입한 HBase 버전은 버전 1과 2 HFile을 모두 읽지만 버전 2 HFile만 작성해요. 버전 2 HFile은 다음과 같이 구조화돼요.
(다이어그램)
통합 버전 2 블록 형식 (Unified version 2 block format)
버전 2에서 데이터 섹션의 모든 블록은 다음 필드를 포함해요.
- 8 바이트: 블록 타입. 버전 1의 "magic records"와 동등한 바이트 시퀀스예요. 지원되는 블록 타입은 다음과 같아요.
- DATA – 데이터 블록
- LEAF_INDEX – 다중 레벨 블록 인덱스의 리프 레벨 인덱스 블록
- BLOOM_CHUNK – 블룸 필터 청크
- META – 메타 블록(버전 2에서는 더 이상 블룸 필터에 사용되지 않음)
- INTERMEDIATE_INDEX – 다중 레벨 블록 인덱스의 중간 레벨 인덱스 블록
- ROOT_INDEX – 다중 레벨 블록 인덱스의 루트 레벨 인덱스 블록
- FILE_INFO – 메타데이터의 작은 키-값 맵인 "파일 정보" 블록
- BLOOM_META – load-on-open 섹션의 블룸 필터 메타데이터 블록
- TRAILER – 고정 크기 파일 트레일러. 위와 달리 HFile v2 블록이 아니라 (각 HFile 버전에 대해) 고정 크기 데이터 구조
- INDEX_V1 – 이 블록 타입은 레거시 HFile v1 블록에만 사용됨
- 헤더를 제외한 블록 데이터의 압축 크기(int). HFile 데이터를 스캔할 때 현재 데이터 블록을 건너뛰는 데 사용할 수 있어요.
- 헤더를 제외한 블록 데이터의 압축 해제 크기(int). 압축 알고리즘이 NONE이면 압축 크기와 같아요.
- 같은 타입의 이전 블록의 파일 오프셋(long). 이전 데이터/인덱스 블록으로 seek하는 데 사용할 수 있어요.
- 압축된 데이터(압축 알고리즘이 NONE이면 압축되지 않은 데이터).
위 블록 형식은 다음 HFile 섹션에서 사용돼요.
Scanned 블록 섹션 (Scanned block section)
이 섹션은 HFile을 순차적으로 스캔할 때 읽어야 하는 모든 데이터 블록을 포함하기 때문에 그런 이름이 붙었어요. 또한 Leaf 인덱스 블록과 Bloom 청크 블록을 포함해요.
Non-scanned 블록 섹션 (Non-scanned block section)
이 섹션은 여전히 통합 형식 v2 블록을 포함하지만 순차 스캔을 할 때 읽을 필요는 없어요. 이 섹션은 "meta" 블록과 중간 레벨 인덱스 블록을 포함해요.
우리는 더 이상 이 블록에 블룸 필터 데이터를 저장하지 않지만, 버전 2에서 버전 1이 지원한 것과 같은 방식으로 "meta" 블록을 지원해요.
버전 2의 블록 인덱스 (Block index in version 2)
HFile 버전 2에는 세 가지 유형의 블록 인덱스가 있고, 두 가지 다른 형식(루트와 비-루트)으로 저장돼요.
- 데이터 인덱스 — 버전 2 다중 레벨 블록 인덱스로, 다음으로 구성돼요:
- 파일의 데이터 블록 인덱스 섹션에 저장된 버전 2 루트 인덱스
- 선택적으로, 파일의 데이터 인덱스 섹션에 비-루트 형식으로 저장된 버전 2 중간 레벨. 중간 레벨은 리프 레벨 블록이 있는 경우에만 존재할 수 있어요.
- 선택적으로, 데이터 블록과 인라인으로 비-루트 형식으로 저장된 버전 2 리프 레벨.
- 메타 인덱스 — 버전 2 루트 인덱스 형식만, 파일의 메타 인덱스 섹션에 저장돼요.
- 블룸 인덱스 — 버전 2 루트 인덱스 형식만, 블룸 필터 메타데이터의 일부로 "load-on-open" 섹션에 저장돼요.
버전 2의 루트 블록 인덱스 형식 (Root block index format in version 2)
이 형식은 다음에 적용돼요.
- 버전 2 데이터 인덱스의 루트 레벨
- 항상 단일 레벨인 버전 2의 전체 메타·블룸 인덱스
버전 2 루트 인덱스 블록은 버전 1 블록 인덱스의 항목과 유사하지만 uncompressed size 대신 on-disk size를 저장하는 다음 형식의 항목 시퀀스예요.
- Offset (long). 이 오프셋은 데이터 블록이나 더 깊은 레벨의 인덱스 블록을 가리킬 수 있어요.
- On-disk size (int)
- Key (Bytes.writeByteArray로 저장된 직렬화된 바이트 배열)
- Key (VInt)
- Key 바이트
단일 레벨 버전 2 블록 인덱스는 단일 루트 인덱스 블록으로만 구성돼요. 버전 2의 루트 인덱스 블록을 읽으려면 항목 수를 알아야 해요. 데이터 인덱스와 메타 인덱스의 경우 항목 수가 트레일러에 저장되고, 블룸 인덱스의 경우 복합 블룸 필터 메타데이터에 저장돼요.
다중 레벨 블록 인덱스의 경우 HFile의 load-on-open 섹션에 있는 루트 인덱스 블록에 위에서 설명한 데이터 구조에 더해 다음 필드도 저장해요.
- 중간 리프 인덱스 블록 오프셋
- 중간 리프 블록의 on-disk 크기(파일의 "중간" 데이터 블록에 대한 참조를 포함하는 리프 인덱스 블록을 의미)
- 중간 리프 레벨 블록에서 mid-key(아래 정의)의 인덱스.
이 추가 필드는 HFile split에서 사용되는 HFile의 mid-key를 효율적으로 검색하는 데 사용돼요. mid-key는 HFile의 총 블록 수가 n일 때 0 기반 인덱스 (n – 1) / 2인 블록의 첫 키로 정의해요. 이 정의는 HFile 버전 1에서 mid-key가 결정된 방식과 일치하며, 블록이 평균적으로 같은 크기일 가능성이 높으므로 일반적으로 합리적이에요. 하지만 개별 키/값 쌍 크기에 대한 추정치는 없어요.
버전 2 HFile을 작성할 때 모든 리프 레벨 인덱스 블록이 가리키는 데이터 블록의 총 수를 추적해요. 작성이 끝나고 리프 레벨 블록의 총 수가 결정되면 어떤 리프 레벨 블록이 mid-key를 포함하는지 분명해지고, 위에 나열된 필드가 계산돼요. HFile을 읽고 mid-key가 요청되면 중간 리프 인덱스 블록(잠재적으로 블록 캐시에서)을 검색하고 그 리프 블록 내 적절한 위치에서 mid-key 값을 얻어요.
버전 2의 비-루트 블록 인덱스 형식 (Non-root block index format in version 2)
이 형식은 버전 2 다중 레벨 데이터 블록 인덱스의 중간 레벨과 리프 인덱스 블록에 적용돼요. 모든 비-루트 인덱스 블록은 다음과 같이 구조화돼요.
- numEntries: 항목 수(int).
- entryOffsets: 블록에서 항목 오프셋의 "보조 인덱스"로, 키에 대한 빠른 이진 검색을 용이하게 해줘요(
numEntries + 1int 값). 마지막 값은 이 인덱스 블록의 모든 항목의 총 길이예요. 예를 들어 항목 크기가 60, 80, 50인 비-루트 인덱스 블록에서 "보조 인덱스"는 다음 int 배열을 포함해요:{0, 60, 140, 190}. - 항목들. 각 항목은 다음을 포함해요:
- 파일에서 이 항목이 참조하는 블록의 오프셋(long)
- 참조된 블록의 on-disk 크기(int)
- 키. 길이는 entryOffsets에서 계산할 수 있어요.
버전 2의 블룸 필터 (Bloom filters in version 2)
버전 1과 달리, 버전 2 HFile에서는 빠른 시작을 위해 블룸 필터 메타데이터가 HFile의 load-on-open 섹션에 저장돼요.
- 복합 블룸 필터(compound Bloom filter).
- 블룸 필터 버전 = 3 (int). 블룸 필터 버전 번호 2를 가진 DynamicByteBloomFilter 클래스가 예전에 있었음
- 모든 복합 블룸 필터 청크의 총 바이트 크기(long)
- 해시 함수 수(int)
- 해시 함수 유형(int)
- 블룸 필터에 삽입된 총 키 수(long)
- 블룸 필터의 최대 총 키 수(long)
- 청크 수(int)
- 블룸 필터 키에 사용되는 Comparator 클래스, Bytes.writeByteArray로 저장된 UTF-8 인코딩 문자열
- 버전 2 루트 블록 인덱스 형식의 블룸 블록 인덱스
버전 1과 2의 파일 정보 형식 (File Info format in versions 1 and 2)
파일 정보 블록은 바이트 배열에서 바이트 배열로 가는 직렬화된 맵이며, 무엇보다도 다음 키를 가져요. StoreFile 수준 로직이 여기에 더 많은 키를 추가해요.
| Key | Description |
|---|---|
| hfile.LASTKEY | 파일의 마지막 키(바이트 배열) |
| hfile.AVG_KEY_LEN | 파일의 평균 키 길이(int) |
| hfile.AVG_VALUE_LEN | 파일의 평균 값 길이(int) |
버전 2에서는 파일 형식을 변경하지 않았지만, 파일 정보를 파일의 마지막 섹션으로 옮겼어요. 이 섹션은 HFile이 열릴 때 하나의 블록으로 로드될 수 있어요.
또한 버전 2 파일 정보에 comparator를 더 이상 저장하지 않아요. 대신 고정 파일 트레일러에 저장해요. 이는 HFile의 load-on-open 섹션을 파싱할 때 comparator를 알아야 하기 때문이에요.
버전 1과 2 사이의 고정 파일 트레일러 형식 차이 (Fixed file trailer format differences between versions 1 and 2)
다음 표는 버전 1과 2의 고정 파일 트레일러 간 공통 필드와 다른 필드를 보여줘요. 트레일러의 크기는 버전에 따라 다르므로 한 버전 안에서만 "고정"이라는 점을 유의하세요. 그러나 버전은 항상 파일의 마지막 4바이트 정수로 저장돼요.
HFile 버전 1과 2의 차이 (Differences between HFile Versions 1 and 2)
| Version 1 | Version 2 |
|---|---|
| File info offset (long) | Data index offset (long) |
| loadOnOpenOffset (long) — 파일을 열 때 로드해야 하는 섹션의 오프셋 | |
| Number of data index entries (int) | metaIndexOffset (long) — 이 필드는 버전 1 reader가 사용하지 않으므로 버전 2에서 제거했음 |
| uncompressedDataIndexSize (long) | 전체 데이터 블록 인덱스(루트 레벨, 중간 레벨, 리프 레벨 블록 포함)의 총 압축 해제 크기 |
| Number of meta index entries (int) | Total uncompressed bytes (long) |
| numEntries (int) | numEntries (long) |
| Compression codec: 0 = LZO, 1 = GZ, 2 = NONE (int) | Compression codec: 0 = LZO, 1 = GZ, 2 = NONE (int) |
| The number of levels in the data block index (int) | |
| firstDataBlockOffset (long) — 첫 번째 데이터 블록의 오프셋. 스캔 시 사용 | |
| lastDataBlockEnd (long) — 마지막 키/값 데이터 블록 뒤 첫 바이트의 오프셋. 스캔할 때 이 오프셋을 넘어갈 필요 없음 | |
| Version: 1 (int) | Version: 2 (int) |
getShortMidpointKey (데이터 인덱스 블록 최적화) (getShortMidpointKey (an optimization for data index block))
메모: 이 최적화는 HBase 0.95+에서 도입됐어요.
HFile은 정렬된 Cell 범위를 포함하는 많은 블록을 포함해요. 각 cell에는 키가 있어요. Cell을 읽을 때 IO를 절약하기 위해 HFile에는 Cell의 시작 키를 특정 블록의 시작 오프셋에 매핑하는 인덱스도 있어요. 이 최적화 이전에는 HBase가 각 데이터 블록의 첫 cell의 키를 인덱스 키로 사용했어요.
HBASE-7845에서 우리는 이전 블록의 마지막 키보다 사전식으로 크고, 현재 블록의 시작 키와 사전식으로 같거나 작은 새 키를 생성해요. 실제 키는 잠재적으로 매우 길 수 있지만, 이 "가짜 키"나 "가상 키"는 훨씬 더 짧을 수 있어요. 예를 들어 이전 블록의 stop key가 "the quick brown fox"이고 현재 블록의 start key가 "the who"라면 hfile 인덱스에서 "the r"을 가상 키로 사용할 수 있어요.
여기에는 두 가지 이점이 있어요.
- 더 짧은 키는 hfile 인덱스 크기를 줄여줘요(더 많은 인덱스를 메모리에 유지할 수 있게 해줘요), 그리고
- 이전 블록의 끝 키에 더 가까운 것을 사용하면 대상 키가 "가상 키"와 대상 블록의 첫 요소 키 사이에 있을 때 잠재적 추가 IO를 피할 수 있어요.
이 최적화(getShortMidpointKey 메서드로 구현)는 LevelDB의 ByteWiseComparatorImpl::FindShortestSeparator()와 FindShortSuccessor()에서 영감을 받았어요.
보안 강화가 포함된 HBase 파일 형식 (버전 3) (HBase File Format with Security Enhancements (version 3))
메모: 이 기능은 HBase 0.98에서 도입됐어요.
동기 (Motivation)
HFile 버전 3은 미사용 데이터의 암호화와 cell 수준 메타데이터(이것은 cell 수준 ACL과 cell 수준 가시성 레이블에 필요함)를 쉽게 관리하기 위해 필요한 변경을 만들어요. 자세한 내용은 hbase.encryption.server, hbase.tags, hbase.accesscontrol.configuration, hbase.visibility.labels를 참고하세요.
개요 (Overview)
위 기능을 도입한 HBase 버전은 버전 1, 2, 3의 HFile을 읽지만 버전 3 HFile만 작성해요. 버전 3 HFile은 버전 2 HFile과 동일하게 구조화돼요. 자세한 내용은 hfilev2.overview를 참고하세요.
버전 3의 파일 정보 블록 (File Info Block in Version 3)
버전 3은 파일 정보 블록의 예약 키에 두 가지 추가 정보를 더했어요.
| Key | Description |
|---|---|
| hfile.MAX_TAGS_LEN | 이 hfile의 단일 cell에 직렬화된 태그를 저장하는 데 필요한 최대 바이트 수(int) |
| hfile.TAGS_COMPRESSED | 이 hfile의 블록 인코더가 태그를 압축하나요? (boolean). hfile.MAX_TAGS_LEN이 있는 경우에만 존재해야 함 |
버전 3 HFile을 읽을 때 MAX_TAGS_LEN의 존재 여부를 사용해 데이터 블록 내 cell을 어떻게 역직렬화할지 결정해요. 따라서 소비자는 데이터 블록을 읽기 전에 파일의 정보 블록을 읽어야 해요.
버전 3 HFile을 작성할 때 HBase는 memstore를 기본 파일시스템으로 flush할 때 항상 MAX_TAGS_LEN을 포함해요.
기존 파일을 컴팩트할 때 선택된 파일 모두가 태그가 있는 cell을 포함하지 않는다면 기본 writer는 MAX_TAGS_LEN을 생략해요.
컴팩션 파일 선택 알고리즘의 세부 사항은 compaction을 참고하세요.
버전 3의 데이터 블록 (Data Blocks in Version 3)
HFile 내에서 HBase cell은 KeyValue 시퀀스로 데이터 블록에 저장돼요(hfilev1.overview 또는 Lars George의 훌륭한 HBase Storage 소개 참고). 버전 3에서 이 KeyValue는 선택적으로 0개 이상의 태그 집합을 포함해요.
| Version 1 & 2, Version 3 without MAX_TAGS_LEN | Version 3 with MAX_TAGS_LEN | |
|---|---|---|
| Key Length (4 bytes) | ✓ | ✓ |
| Value Length (4 bytes) | ✓ | ✓ |
| Key bytes (variable) | ✓ | ✓ |
| Value bytes (variable) | ✓ | ✓ |
| Tags Length (2 bytes) | ✓ | |
| Tags bytes (variable) | ✓ |
주어진 HFile의 정보 블록에 MAX_TAGS_LEN 항목이 있으면 각 cell은 그 cell의 태그 길이를 포함해요. 그 길이가 0이어도요. 실제 태그는 태그 길이(2 바이트), 태그 유형(1 바이트), 태그 바이트(가변) 시퀀스로 저장돼요. 개별 태그 바이트의 형식은 태그 유형에 따라 달라져요.
정보 블록의 내용에 의존한다는 것은 데이터 블록을 읽기 전에 먼저 파일의 정보 블록을 처리해야 한다는 것을 의미해요. 또한 데이터 블록을 작성하기 전에 파일의 정보 블록이 MAX_TAGS_LEN을 포함할지 알아야 한다는 것도 의미해요.
버전 3의 고정 파일 트레일러 (Fixed File Trailer in Version 3)
HFile 버전 3으로 작성된 고정 파일 트레일러는 항상 프로토콜 버퍼로 직렬화돼요. 또한 버전 2 프로토콜 버퍼에 encryption_key라는 선택적 필드를 추가해요. HBase가 HFile을 암호화하도록 구성되면 이 필드는 현재 클러스터 마스터 키로 AES로 암호화된, 이 특정 HFile용 데이터 암호화 키를 저장해요. 자세한 내용은 hbase.encryption.server를 참고하세요.