Protobuf 형식
Protobuf 형식
Protobuf 형식은 Protocol Buffers 형식이에요. 외부 형식 스키마가 필요하며 쿼리 간에 캐시됩니다. ClickHouse는 proto2와 proto3 구문 모두를, 그리고 Repeated/optional/required 필드를 지원합니다. 컬럼과 메시지 필드의 대응은 이름 비교로 찾습니다.
출처: 문서
본문
| Input | Output | Alias |
|---|---|---|
| ✔ | ✔ |
설명 (Description)
Protobuf 형식은 Protocol Buffers 형식입니다. 이 형식은 쿼리 간에 캐시되는 외부 형식 스키마가 필요합니다.
ClickHouse는 다음을 지원합니다:
proto2와proto3구문 모두.Repeated/optional/required필드.
테이블 컬럼과 Protocol Buffers 메시지 타입의 필드 사이의 대응을 찾기 위해 ClickHouse는 이름을 비교합니다. 이 비교는 대소문자를 구분하지 않으며 _(밑줄)와 .(점) 문자가 동일하게 간주됩니다.
컬럼과 Protocol Buffers 메시지의 필드 타입이 다르면 필요한 변환이 적용됩니다.
중첩 메시지가 지원됩니다. 예를 들어 다음 메시지 타입의 필드 z에 대해:
message MessageType {
message XType {
message YType {
int32 z;
};
repeated YType y;
};
XType x;
};
ClickHouse는 x.y.z(또는 x_y_z 또는 X.y_Z 등)라는 이름의 컬럼을 찾으려고 합니다. 중첩 메시지는 중첩 데이터 구조의 입력 또는 출력에 적합합니다.
와이어에서 누락된 매핑된 필드에 대해:
- 일반 non-nullable 매핑 컬럼은 파싱 중 프로토콜 버퍼 스키마 필드 기본값(
proto2[default = …], 그 외에는 타입 기본값)을 사용합니다 — 테이블DEFAULT표현식은 아닙니다. - 매핑된
Nullable(...)컬럼은 필드가 없으면NULL로 해석됩니다(프로토콜 버퍼 필드/타입 기본값을 취하지 않습니다). - input_format_protobuf_flatten_google_wrappers가
google.protobuf.*Value래퍼에 대해 활성화되면:Nullable(...)컬럼의 없는 래퍼는 누락된 외부 필드로 취급되어NULL이 됩니다.- 있지만 빈 래퍼(
str {})는 중첩 스칼라 기본값(''/0)을 유지합니다. - 없는 래퍼에 매핑된 non-nullable 컬럼은
NULL이 아니라 중첩 스칼라 기본값을 얻습니다.
테이블 DEFAULT(및 기본 표현식)는 input_format_defaults_for_omitted_fields가 활성화된 경우(기본값) 메시지 타입에 일치하는 필드가 없는 테이블 컬럼에 적용됩니다. 해당 설정이 0이면 매핑되지 않은 컬럼은 테이블 DEFAULT 표현식 대신 파싱 중에 삽입된 데이터 타입 기본값을 유지합니다.
proto2 스키마 필드 기본값의 예(메시지에서 없는 매핑된 필드에 사용):
syntax = "proto2";
message MessageType {
optional int32 result_per_page = 3 [default = 10];
}
메시지에 oneof가 포함되고 input_format_protobuf_oneof_presence가 설정되면 ClickHouse는 oneof의 어떤 필드가 발견되었는지 나타내는 컬럼을 채웁니다.
syntax = "proto3";
message StringOrString {
oneof string_oneof {
string string1 = 1;
string string2 = 42;
}
}
CREATE TABLE string_or_string ( string1 String, string2 String, string_oneof Enum('no'=0, 'hello' = 1, 'world' = 42)) Engine=MergeTree ORDER BY tuple();
INSERT INTO string_or_string from INFILE '$CURDIR/data_protobuf/String1' SETTINGS format_schema='$SCHEMADIR/string_or_string.proto:StringOrString' FORMAT ProtobufSingle;
SELECT * FROM string_or_string
┌─────────┬─────────┬──────────────┐
│ string1 │ string2 │ string_oneof │
├─────────┼─────────┼──────────────┤
1. │ │ string2 │ world │
├─────────┼─────────┼──────────────┤
2. │ string1 │ │ hello │
└─────────┴─────────┴──────────────┘
존재를 나타내는 컬럼의 이름은 oneof의 이름과 같아야 합니다.
중첩 메시지가 지원됩니다(basic-examples 참조). 빈 메시지도 지원됩니다.
허용되는 타입은 Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64, UInt64, Enum, Enum8 또는 Enum16입니다.
Enum(Enum8 또는 Enum16도)은 부재를 나타내는 0과 대상 테이블에 일치하는 컬럼이 있는 각 oneof 케이스의 태그를 포함해야 합니다. 문자열 표현은 중요하지 않아요.
일치하는 테이블 컬럼이 없는 oneof 메시지 멤버의 경우 누락된 Enum 태그도 허용됩니다. 그러한 분기가 입력에 있으면 ClickHouse는 oneof 존재를 생략된 것으로 취급하고 존재 컬럼에 0을 기록합니다.
input_format_protobuf_oneof_presence 설정은 기본적으로 비활성화되어 있습니다.
ClickHouse는 프로토콜 버퍼 메시지를 길이 구분(length-delimited) 형식으로 입력하고 출력합니다. 이는 각 메시지 앞에 그 길이를 가변 폭 정수(varint)로 기록해야 한다는 뜻입니다.
사용 예시 (Example usage)
데이터 읽기 및 쓰기 (Reading and writing data)
이 예시에서 사용된 파일들은 examples 저장소에서 사용할 수 있어요. 이 예시에서는 protobuf_message.bin 파일의 일부 데이터를 ClickHouse 테이블로 읽은 다음, Protobuf 형식을 사용하여 protobuf_message_from_clickhouse.bin이라는 파일로 다시 쓸 것입니다.
schemafile.proto 파일이 있다고 가정합니다:
syntax = "proto3";
message MessageType {
string name = 1;
string surname = 2;
uint32 birthDate = 3;
repeated string phoneNumbers = 4;
};
바이너리 파일 생성
이미 Protobuf 형식으로 데이터를 직렬화하고 역직렬화하는 방법을 안다면 이 단계를 건너뛸 수 있어요. Python을 사용하여 일부 데이터를 protobuf_message.bin으로 직렬화하고 ClickHouse로 읽어 들일 것입니다.
다른 언어를 사용하고 싶다면 "How to read/write length-delimited Protobuf messages in popular languages"도 참조하세요.
schemafile.proto와 같은 디렉토리에 schemafile_pb2.py라는 Python 파일을 생성하려면 다음 명령을 실행합니다. 이 파일에는 UserData Protobuf 메시지를 나타내는 Python 클래스가 포함됩니다:
protoc --python_out=. schemafile.proto
이제 schemafile_pb2.py와 같은 디렉토리에 generate_protobuf_data.py라는 새 Python 파일을 만듭니다. 다음 코드를 붙여 넣습니다:
import schemafile_pb2 # Module generated by 'protoc'
from google.protobuf import text_format
from google.protobuf.internal.encoder import _VarintBytes # Import the internal varint encoder
def create_user_data_message(name, surname, birthDate, phoneNumbers):
"""
Creates and populates a UserData Protobuf message.
"""
message = schemafile_pb2.MessageType()
message.name = name
message.surname = surname
message.birthDate = birthDate
message.phoneNumbers.extend(phoneNumbers)
return message
# The data for our example users
data_to_serialize = [
{"name": "Aisha", "surname": "Khan", "birthDate": 19920815, "phoneNumbers": ["(555) 247-8903", "(555) 612-3457"]},
{"name": "Javier", "surname": "Rodriguez", "birthDate": 20001015, "phoneNumbers": ["(555) 891-2046", "(555) 738-5129"]},
{"name": "Mei", "surname": "Ling", "birthDate": 19980616, "phoneNumbers": ["(555) 956-1834", "(555) 403-7682"]},
]
output_filename = "protobuf_messages.bin"
# Open the binary file in write-binary mode ('wb')
with open(output_filename, "wb") as f:
for item in data_to_serialize:
# Create a Protobuf message instance for the current user
message = create_user_data_message(
item["name"],
item["surname"],
item["birthDate"],
item["phoneNumbers"]
)
# Serialize the message
serialized_data = message.SerializeToString()
# Get the length of the serialized data
message_length = len(serialized_data)
# Use the Protobuf library's internal _VarintBytes to encode the length
length_prefix = _VarintBytes(message_length)
# Write the length prefix
f.write(length_prefix)
# Write the serialized message data
f.write(serialized_data)
print(f"Protobuf messages (length-delimited) written to {output_filename}")
# --- Optional: Verification (reading back and printing) ---
# For reading back, we'll also use the internal Protobuf decoder for varints.
from google.protobuf.internal.decoder import _DecodeVarint32
print("\n--- Verifying by reading back ---")
with open(output_filename, "rb") as f:
buf = f.read() # Read the whole file into a buffer for easier varint decoding
n = 0
while n < len(buf):
# Decode the varint length prefix
msg_len, new_pos = _DecodeVarint32(buf, n)
n = new_pos
# Extract the message data
message_data = buf[n:n+msg_len]
n += msg_len
# Parse the message
decoded_message = schemafile_pb2.MessageType()
decoded_message.ParseFromString(message_data)
print(text_format.MessageToString(decoded_message, as_utf8=True))
이제 커맨드라인에서 스크립트를 실행합니다. python 가상 환경에서 실행하는 것이 좋습니다. 예를 들어 uv를 사용:
uv venv proto-venv
source proto-venv/bin/activate
다음 python 라이브러리를 설치해야 합니다:
uv pip install --upgrade protobuf
스크립트를 실행하여 바이너리 파일을 생성합니다:
python generate_protobuf_data.py
스키마와 일치하는 ClickHouse 테이블을 만듭니다:
CREATE DATABASE IF NOT EXISTS test;
CREATE TABLE IF NOT EXISTS test.protobuf_messages (
name String,
surname String,
birthDate UInt32,
phoneNumbers Array(String)
)
ENGINE = MergeTree()
ORDER BY tuple()
커맨드라인에서 테이블에 데이터를 삽입합니다:
cat protobuf_messages.bin | clickhouse-client --query "INSERT INTO test.protobuf_messages SETTINGS format_schema='schemafile:MessageType' FORMAT Protobuf"
Protobuf 형식을 사용하여 데이터를 바이너리 파일로 다시 쓸 수도 있어요:
SELECT * FROM test.protobuf_messages INTO OUTFILE 'protobuf_message_from_clickhouse.bin' FORMAT Protobuf SETTINGS format_schema = 'schemafile:MessageType'
Protobuf 스키마를 사용하여 ClickHouse에서 protobuf_message_from_clickhouse.bin 파일로 기록된 데이터를 이제 역직렬화할 수 있어요.
ClickHouse Cloud로 데이터 읽기 및 쓰기 (Reading and writing data using ClickHouse Cloud)
ClickHouse Cloud에서는 Protobuf 스키마 파일을 업로드할 수 없습니다. 그러나 format_protobuf_schema 설정을 사용하여 쿼리에 스키마를 지정할 수 있어요. 이 예시에서는 로컬 머신에서 직렬화된 데이터를 읽어 ClickHouse Cloud의 테이블에 삽입하는 방법을 보여줍니다.
이전 예시처럼 ClickHouse Cloud의 Protobuf 스키마 스키마에 따라 테이블을 만듭니다:
CREATE DATABASE IF NOT EXISTS test;
CREATE TABLE IF NOT EXISTS test.protobuf_messages (
name String,
surname String,
birthDate UInt32,
phoneNumbers Array(String)
)
ENGINE = MergeTree()
ORDER BY tuple()
format_schema_source 설정은 format_schema 설정의 소스를 정의합니다.
가능한 값:
- 'file' (기본값): Cloud에서 지원되지 않음
- 'string':
format_schema는 스키마의 리터럴 내용입니다. - 'query':
format_schema는 스키마를 검색하는 쿼리입니다.
format_schema_source='string'
스키마를 문자열로 지정하여 ClickHouse Cloud에 데이터를 삽입하려면 실행합니다:
cat protobuf_messages.bin | clickhouse client --host <hostname> --secure --password <password> --query "INSERT INTO testing.protobuf_messages SETTINGS format_schema_source='syntax = "proto3";message MessageType { string name = 1; string surname = 2; uint32 birthDate = 3; repeated string phoneNumbers = 4;};', format_schema='schemafile:MessageType' FORMAT Protobuf"
테이블에 삽입된 데이터를 선택합니다:
clickhouse client --host <hostname> --secure --password <password> --query "SELECT * FROM testing.protobuf_messages"
Aisha Khan 19920815 ['(555) 247-8903','(555) 612-3457']
Javier Rodriguez 20001015 ['(555) 891-2046','(555) 738-5129']
Mei Ling 19980616 ['(555) 956-1834','(555) 403-7682']
format_schema_source='query'
Protobuf 스키마를 테이블에 저장할 수도 있어요.
데이터를 삽입할 ClickHouse Cloud 테이블을 만듭니다:
CREATE TABLE testing.protobuf_schema (
schema String
)
ENGINE = MergeTree()
ORDER BY tuple();
INSERT INTO testing.protobuf_schema VALUES ('syntax = "proto3";message MessageType { string name = 1; string surname = 2; uint32 birthDate = 3; repeated string phoneNumbers = 4;};');
스키마를 실행할 쿼리로 지정하여 ClickHouse Cloud에 데이터를 삽입합니다:
cat protobuf_messages.bin | clickhouse client --host <hostname> --secure --password <password> --query "INSERT INTO testing.protobuf_messages SETTINGS format_schema_source='SELECT schema FROM testing.protobuf_schema', format_schema='schemafile:MessageType' FORMAT Protobuf"
테이블에 삽입된 데이터를 선택합니다:
clickhouse client --host <hostname> --secure --password <password> --query "SELECT * FROM testing.protobuf_messages"
Aisha Khan 19920815 ['(555) 247-8903','(555) 612-3457']
Javier Rodriguez 20001015 ['(555) 891-2046','(555) 738-5129']
Mei Ling 19980616 ['(555) 956-1834','(555) 403-7682']
자동 생성 스키마 사용 (Using autogenerated schema)
데이터에 대한 외부 Protobuf 스키마가 없다면 자동 생성된 스키마를 사용하여 Protobuf 형식으로 데이터를 출력/입력할 수 있어요. 이를 위해 format_protobuf_use_autogenerated_schema 설정을 사용합니다.
예를 들어:
SELECT * FROM test.hits format Protobuf SETTINGS format_protobuf_use_autogenerated_schema=1
이 경우 ClickHouse는 structureToProtobufSchema 함수를 사용하여 테이블 구조에 따라 Protobuf 스키마를 자동 생성합니다. 그런 다음 이 스키마를 사용하여 Protobuf 형식으로 데이터를 직렬화합니다.
자동 생성된 스키마로 Protobuf 파일을 읽을 수도 있어요. 이 경우 파일이 동일한 스키마로 생성되어야 합니다:
$ cat hits.bin | clickhouse-client --query "INSERT INTO test.hits SETTINGS format_protobuf_use_autogenerated_schema=1 FORMAT Protobuf"
format_protobuf_use_autogenerated_schema 설정은 기본적으로 활성화되어 있으며 format_schema가 설정되지 않은 경우 적용됩니다.
입력/출력 중에 output_format_schema 설정을 사용하여 자동 생성된 스키마를 파일로 저장할 수도 있어요. 예를 들어:
SELECT * FROM test.hits format Protobuf SETTINGS format_protobuf_use_autogenerated_schema=1, output_format_schema='path/to/schema/schema.proto'
이 경우 자동 생성된 Protobuf 스키마가 path/to/schema/schema.capnp 파일에 저장됩니다.
protobuf 캐시 삭제 (Drop protobuf cache)
format_schema_path에서 로드된 Protobuf 스키마를 다시 로드하려면 SYSTEM DROP ... FORMAT CACHE 문을 사용하세요.
SYSTEM DROP FORMAT SCHEMA CACHE FOR Protobuf