| 입력 | 출력 | 별칭 |
|---|---|---|
| ✔ | ✔ |
설명
Native 형식의 전체 공식 명세는 여기에서 확인할 수 있으며, 이를 전달하는 TCP wire 프로토콜인 Native 프로토콜의 보완 명세는 여기에서 확인할 수 있습니다.
두 명세는 모두 ClickHouse 소스 코드를 바탕으로 LLM이 생성했습니다. 코드는 여전히 가장 중요한 기준입니다. 명세와 코드가 일치하지 않으면 코드가 올바릅니다.
Native 형식은 컬럼을 행으로 변환하지 않는 진정한 “열 지향” 포맷이므로 ClickHouse에서 가장 효율적인 포맷입니다.
이 포맷에서는 데이터가 바이너리 형식으로 블록 단위로 기록되고 읽힙니다.
각 블록에는 행 수, 컬럼 수, 컬럼 이름과 타입, 그리고 블록 내 각 컬럼 데이터의 일부가 차례대로 기록됩니다.
이 포맷은 서버 간 상호작용을 위한 네이티브 인터페이스, command-line client 사용, 그리고 C++ 클라이언트에서 사용됩니다.
데이터 타입 wire 형식
네이티브 TCP 바이너리 프로토콜을 사용하거나(또는 HTTP 엔드포인트가
?client_protocol_version=<n>을 받을 때),
컬럼 수와 행 수 앞에 BlockInfo 구조체가 기록됩니다. 이 섹션의 예시는
프로토콜 버전이 없는 일반 HTTP 인터페이스를 사용하므로 BlockInfo는 생략됩니다.블록 구조
number와 str 두 개의 컬럼과 3개의 행을 반환합니다:
여러 블록
단순 데이터 타입
RowBinary/RowBinaryWithNamesAndTypes와 유사합니다.
이에 해당하는 전체 타입 목록은 다음과 같습니다.
- (U)Int8, (U)Int16, (U)Int32, (U)Int64, (U)Int128, (U)Int256
- Float32, Float64
- Bool
- String
- FixedString(N)
- Date
- Date32
- DateTime
- DateTime64
- IPv4
- IPv6
- UUID
복합 데이터 타입
RowBinary 및 RowBinaryWithNamesAndTypes와 다릅니다.
- 널 허용
- LowCardinality
- 배열
- 맵
- Variant
- Dynamic
- JSON
널 허용
Native 형식에서는 널 허용 컬럼의 실제 데이터 앞에 블록의 행 수와 동일한 바이트 수가 옵니다. 각 바이트는 해당 값이 NULL인지 여부를 나타냅니다. 예를 들어, 다음 쿼리에서는 각 홀수가 NULL이 됩니다:
Nullable(String)도 비슷한 방식으로 작동합니다. null 표시자는 항상 널 허용 마스크 바이트에서 오며 —
마스크 값이 0x01이면 문자열 내용과 관계없이 해당 행은 NULL입니다. NULL 행의 경우,
내부 문자열은 빈 문자열(LEB128 길이 0)로 저장됩니다. 비-NULL 빈
문자열 역시 LEB128 길이가 0이므로, 두 경우를 구분하는 것은 마스크 바이트뿐입니다. 예를 들어, 다음 쿼리:
LowCardinality
LowCardinality가 투명하게 처리되는 RowBinary와 달리, Native 형식은 딕셔너리 기반의 열 지향 인코딩을 사용합니다. 컬럼은 버전 접두사로 시작하고, 이어서 고유 값 딕셔너리와 해당 딕셔너리를 참조하는 정수 인덱스 배열로 인코딩됩니다.
컬럼은
LowCardinality(Nullable(T))로 정의할 수 있지만, Nullable(LowCardinality(T))로 정의할 수는 없습니다 — 이렇게 정의하면 항상 서버에서 오류가 발생합니다.1인 UInt64(LE)이며, 컬럼마다 한 번만 기록됩니다. 그다음 각 블록마다 다음이 기록됩니다.
UInt64(LE)—IndexesSerializationType비트 필드입니다. 비트 0–7은 인덱스 너비를 인코딩합니다(0 = UInt8, 1 = UInt16, 2 = UInt32, 3 = UInt64). 비트 8(NeedGlobalDictionaryBit)은 Native 형식에서는 절대 설정되지 않습니다(이 값이 나타나면 서버가 예외를 발생시킵니다). 비트 9는 추가 딕셔너리 키가 있음을 나타냅니다. 비트 10은 딕셔너리를 재설정해야 함을 나타냅니다.UInt64(LE)— 딕셔너리 키 수이며, 뒤이어 내부 타입 인코딩을 사용해 키가 일괄 직렬화되어 기록됩니다.UInt64(LE)— 행 수이며, 뒤이어 적절한 UInt 너비를 사용해 인덱스 값이 일괄 직렬화되어 기록됩니다.
String의 경우 빈 문자열, 숫자 타입의 경우 0). LowCardinality(Nullable(T))에서는 인덱스 0이 NULL을 나타내며, 키는 Nullable 래퍼 없이 직렬화됩니다.
예를 들어, 5개 행 ['foo', 'bar', 'baz', 'foo', 'bar']를 가지는 LowCardinality(String)은 다음과 같습니다:
LowCardinality(Nullable(String))에서는 인덱스 0은 NULL입니다:
배열
- 누적
UInt64오프셋 N개(리틀 엔디언, 각각 8바이트).i번째 행에는offset[i] - offset[i-1]개의 요소가 있으며,offset[-1]은 암묵적으로 0입니다. - 모든 행의 중첩 요소를 하나로 이어서 연속적으로 일괄 직렬화합니다.
[[0, 10], [1, 11], [2, 12]]를 가진 Array(UInt32)는 다음과 같습니다:
Array(String) 값이 [[], ['0'], ['0','1'], ['0','1','2']]인 경우는 다음과 같습니다:
맵
Map(K, V)는 Array(Tuple(K, V))로 인코딩됩니다. 즉, 배열 오프셋 다음에 모든 키가 나오고, 그 뒤에 모든 값이 나옵니다. 이는 각 항목마다 키와 값이 번갈아 저장되는 RowBinary와 다릅니다.
예시로, 3개의 행이 있는 Map(String, UInt64) [{'a':0,'b':10}, {'a':1,'b':11}, {'a':2,'b':12}]는 다음과 같습니다:
Variant
Variant 컬럼은 다음과 같이 인코딩됩니다.
UInt64(LE)판별자 모드 접두사 (0= BASIC,1= COMPACT). Native 형식 출력은 일반적으로 BASIC (0)을 사용하며, COMPACT 모드는use_compact_variant_discriminators_serialization이 활성화된 상태로 저장된 데이터를 읽을 때 나타날 수 있습니다.- 각 행마다 하나씩, N개의
UInt8판별자. - 각 variant type의 데이터는 판별자 순서대로, 해당하는 행만 포함하는 별도의 대량 컬럼으로 인코딩됩니다.
Variant(String, UInt32)에 5개의 행 [0::UInt32, 'hello', NULL, 3::UInt32, 'hello']가 있는 경우(정렬 결과: String = 0, UInt32 = 1):
Dynamic
Dynamic을 구조체 접두사 다음에 Variant 컬럼이 이어지는 형태로 직렬화합니다.
구조체 접두사에는 UInt64(LE) 직렬화 버전, 동적 타입의 개수(VarUInt), 그리고 문자열 형태의 타입 이름이 포함됩니다. V1 버전에서는 호환성을 위해 타입 개수를 두 번 기록합니다. 그 뒤에 오는 데이터는 동적 타입과 내부 SharedVariant 타입을 합친 뒤 알파벳순으로 정렬한 타입 목록을 가지는 Variant 컬럼입니다.
예를 들어, 5개의 행 [0::UInt32, 'hello', NULL, 3::UInt32, 'hello']이 있는 Dynamic은 다음과 같습니다:
JSON
JSON을 열 지향 구조로 직렬화합니다. 인코딩은 복잡하고 버전에 따라 달라집니다. 즉, 직렬화 버전, 동적 경로 이름, shared data 레이아웃을 포함하는 구조체 접두사로 구성되며, 그 뒤에 타입이 지정된 경로(각각 벌크 컬럼), 동적 경로(각각 Dynamic 컬럼), 그리고 오버플로우 경로용 shared data가 이어집니다.
더 간단한 상호 운용성을 위해 output_format_native_write_json_as_string=1 설정 사용을 고려하십시오. 이 설정은 JSON 컬럼을 일반 JSON 텍스트 문자열(각 행당 String 1개)로 직렬화합니다.)