INSERT 페이로드, 실행 텔레메트리, 그리고 오류 신호를 전달합니다. command-line client와 C++, 그리고 대부분의 서드파티 네이티브 드라이버가 사용하는 기반 프로토콜이기도 합니다.
이 페이지에서는 프로토콜 자체를 다룹니다. 구체적으로는 패킷 프레이밍, 연결 state machine, 버전 협상, 그리고 Block이 아닌 모든 메시지의 본문을 설명합니다. Data 계열 패킷 내부의 바이트(Block, 그 안의 컬럼, 그리고 유형별 인코딩)는 별개의 주제이며, Native 형식 명세에서 문서화되어 있습니다.
함께 읽는 명세이 페이지는 한 쌍으로 제공되는 명세 중 하나이며, 함께 제공되는 Native 형식 명세와 같이 게시됩니다. 두 명세는 역할이 명확히 나뉩니다. 이 페이지는 패킷과 전송 계층을 다루고, Native 형식 명세는
Data 계열 패킷 내부의 바이트를 다룹니다.BlockInfo 내부를 제외하면 필드 태그가 없으므로, 바이트 하나만 어긋나도 그 뒤에 오는 모든 내용의 동기화가 깨집니다. 또한 상태를 유지하는 프로토콜이며, 각 TCP 연결은 한 번에 하나의 쿼리만 처리합니다. 즉, 멀티플렉싱은 없습니다. 고정 폭 정수는 리틀 엔디언입니다.
개요
| Property | Value |
|---|---|
| 전송 방식 | TCP, 필요에 따라 TLS로 래핑 가능 |
| 바이트 순서 | 고정 폭 정수에는 리틀 엔디언 사용 |
| 인코딩 | 바이너리 및 위치 기반(BlockInfo를 제외하면 필드 태그 없음) |
| 연결 모델 | 상태 유지형, 한 번에 하나의 쿼리, 멀티플렉싱 없음 |
| 버전 관리 | 핸드셰이크에서 협상되며, 개별 기능은 버전에 따라 제한됨 |
| 데이터 포맷 | 모든 테이블 형식 데이터에 Native 형식 사용 |
VarUInt 패킷 유형 코드로 시작하며, 그 뒤에는 해당 코드와 협상된 프로토콜 버전에 따라 형식이 달라지는 본문이 이어집니다.
연결은 세 단계로 진행됩니다. 먼저 일회성 핸드셰이크를 수행한 다음, Ping 또는 Query 교환을 원하는 만큼 수행하고, 마지막으로 종료됩니다:
네이티브 TCP 프로토콜은 SQL의 FORMAT 절과 관계없이 항상 표 형식 데이터를 Native 형식으로 전달합니다. RowBinary, CSV, JSON 등으로 다시 포맷하는 작업은 클라이언트의 역할이며, 클라이언트가 Native 블록을 디코딩한 후 수행합니다. (HTTP 인터페이스는 FORMAT 절을 실제로 반영하는 별도의 코드 경로이며, 여기서는 HTTP를 다루지 않습니다.)
보안
전송 보안(TLS)
인증
ClientHello 메시지를 통해 수행됩니다. user 및 password 필드는 평문 문자열로 전송되므로, 전송 계층 암호화(TLS)가 전송 중 자격 증명을 보호합니다.
SSH 챌린지-응답 인증은 프로토콜 버전 54466부터 지원됩니다 — 자세한 내용은 SSH 챌린지-응답 인증을 참조하십시오.
서버 간 시크릿
Query 필드 4에는 salt, nonce, 구성된 시크릿, 그리고 쿼리를 바탕으로 계산한 32바이트 SHA-256 auth_hash가 포함되며, 수신 서버는 이를 다시 계산해 비교합니다. 이 동작은 INTERSERVER_SECRET 기능(v54441)으로 제어됩니다. 외부 클라이언트는 이 필드에 항상 빈 문자열을 보냅니다. 서버 간 인증을 참조하십시오.
버전 관리와 기능 게이트
버전 협상
기능 게이트
기능 표
| 기능 | 버전 | 영향 대상 | wire 영향 |
|---|---|---|---|
| BLOCK_INFO | all | Block | 모든 Block에 BlockInfo 접두사(is_overflows, bucket_number)를 추가합니다. |
| CLIENT_INFO | 54032 | Query | Query 본문에 ClientInfo 블록을 추가합니다. |
| TIMEZONE | 54058 | ServerHello | ServerHello에 timezone 필드를 추가합니다. |
| QUOTA_KEY_IN_CLIENT_INFO | 54060 | ClientInfo | ClientInfo에 quota_key 필드를 추가합니다. |
| DISPLAY_NAME | 54372 | ServerHello | ServerHello에 display_name 필드를 추가합니다. |
| VERSION_PATCH | 54401 | ServerHello, ClientInfo | 두 대상 모두에 version_patch 필드를 추가합니다. |
| SERVER_LOGS | 54406 | Log | send_logs_level이 설정되면 서버가 Log 패킷을 전송합니다. |
| COLUMN_DEFAULTS_METADATA | 54410 | TableColumns | 서버는 INSERT/입력 schema 블록 전에 컬럼 기본값 metadata를 담은 TableColumns 패킷(유형 11)을 보낼 수 있습니다. 이 패킷은 협상된 버전이 54410 이상이고 또한 input_format_defaults_for_omitted_fields가 활성화된 경우에만 전송됩니다. 이 버전보다 낮으면 패킷은 절대 전송되지 않으므로 클라이언트는 이를 기다리면 안 됩니다. |
| WRITE_CLIENT_INFO | 54420 | Progress | Progress에 wrote_rows와 wrote_bytes를 추가합니다. (이름과 달리 이것은 ClientInfo 블록을 제어하지 않습니다. 이를 제어하는 것은 CLIENT_INFO(v54032)입니다.) |
| SETTINGS_SERIALIZED_AS_STRINGS | 54429 | Query (settings encoding) | 항상 존재하는 settings 목록의 인코딩 방식을 변경합니다. settings 전송 여부를 제어하는 것은 아닙니다. v54429+에서는 각 setting을 (name, flags, value-as-string)으로 기록하고, 이전 peer는 플래그 없이 (name, type-specific-binary-value)로 기록합니다. Setting을 참조하십시오. |
| INTERSERVER_SECRET | 54441 | Query | Query에 서버 간 auth_hash 필드를 추가합니다. 이는 원시 시크릿이 아니라 클러스터 시크릿에 salt를 적용한 SHA-256입니다. 외부 클라이언트는 빈 문자열을 보냅니다. Inter-server authentication을 참조하십시오. |
| OPEN_TELEMETRY | 54442 | ClientInfo | ClientInfo에 OpenTelemetry 추적 컨텍스트를 추가합니다. |
| DISTRIBUTED_DEPTH | 54448 | ClientInfo | ClientInfo에 distributed_depth 필드를 추가합니다. |
| INITIAL_QUERY_START_TIME | 54449 | ClientInfo | initial_time 필드(Int64, 고정 폭)를 추가합니다. |
| PROFILE_EVENTS | 54451 | ProfileEvents | 서버가 쿼리 실행 중에 ProfileEvents 패킷을 전송합니다. |
| PARALLEL_REPLICAS | 54453 | ClientInfo | ClientInfo에 병렬 레플리카 coordination 필드를 추가합니다. |
| CUSTOM_SERIALIZATION | 54454 | Block (Column) | 각 컬럼의 유형 문자열 뒤에 has_custom_serialization 바이트를 추가합니다. |
| ADDENDUM | 54458 | Handshake | 클라이언트가 handshake 교환 후 addendum(quota_key)을 보냅니다. |
| PARAMETERS | 54459 | Query | Query 본문에 매개변수 목록을 추가합니다. |
| SERVER_QUERY_TIME_IN_PROGRESS | 54460 | Progress | Progress에 elapsed_ns 필드를 추가합니다. |
| PASSWORD_COMPLEXITY_RULES | 54461 | ServerHello | ServerHello에 password 정책 regex 패턴 목록과 사람이 읽기 쉬운 메시지를 추가합니다. |
| INTERSERVER_SECRET_V2 | 54462 | ServerHello | ServerHello에 8바이트 UInt64 nonce를 추가합니다. 서버 간 쿼리 서명에 사용되며, 외부 클라이언트는 이를 디코딩한 뒤 무시합니다. |
| TOTAL_BYTES_IN_PROGRESS | 54463 | Progress | Progress에 total_bytes_to_read(VarUInt) 필드를 total_rows와 wrote_rows 사이에 추가합니다. |
| TIMEZONE_UPDATES | 54464 | TimezoneUpdate | TimezoneUpdate 서버 패킷(유형 17)을 추가합니다. 본문: session 시간대를 담는 단일 String. input 테이블 함수 초기화기만 이를 전송하며, 입력 schema 블록 바로 뒤에 전송되므로 클라이언트가 서버의 session_timezone으로 자신이 보내는 행을 파싱할 수 있습니다. TimezoneUpdate를 참조하십시오. |
| SPARSE_SERIALIZATION | 54465 | Block (Column) | 서버는 has_custom_serialization = 1로 설정하고 희소 인코딩된 컬럼을 전송할 수 있습니다. wire 형식: 1바이트 kind(0x01 = SPARSE), 그다음 EOG로 종료되는 VarUInt 오프셋 스트림, 그리고 내부 유형으로 조밀하게 인코딩된 기본값이 아닌 값들입니다. kind_stack and sparse encoding을 참조하십시오. |
| SSH_AUTHENTICATION | 54466 | Auth flow | SSH 챌린지-응답 authentication을 추가합니다. 옵트인 방식으로 동작합니다. 이를 트리거하려면 클라이언트가 빈 password와 함께 " SSH KEY AUTHENTICATION " + <real_user> 형식의 user를 보내야 합니다. SSH challenge-response authentication을 참조하십시오. |
| TABLE_READ_ONLY_CHECK | 54467 | TablesStatusResponse | TablesStatusResponse에서 각 테이블 행에 is_readonly 플래그를 추가합니다. TablesStatusRequest를 실행하지 않는 외부 클라이언트에는 wire 변경이 없습니다. |
| SYSTEM_KEYWORDS_TABLE | 54468 | system tables | 서버가 system.keywords를 채워 정식 clickhouse-client가 keyword 자동완성을 할 수 있게 합니다. 네이티브 protocol의 wire 변경은 없습니다. |
| ROWS_BEFORE_AGGREGATION | 54469 | ProfileInfo | ProfileInfo 끝부분에 applied_aggregation(Bool)과 rows_before_aggregation(VarUInt)을 이 순서대로 추가합니다. |
| CHUNKED_PROTOCOL | 54470 | Connection framing | 패킷별 청크 framing이 모든 패킷 본문을 감쌉니다. Addendum에서 협상됩니다. ServerHello는 각 방향에 대한 서버의 선호를 담고, Addendum은 클라이언트의 최종 선택을 담습니다. chunked framing을 참조하십시오. |
| VERSIONED_PARALLEL_REPLICAS_PROTOCOL | 54471 | ServerHello, Addendum | 양측은 병렬 레플리카 조정 프로토콜 버전을 나타내는 VarUInt를 교환합니다. ServerHello의 필드는 protocol_version 바로 다음(timezone 앞)에 위치합니다. Addendum의 필드는 청크 프로토콜 문자열 뒤에 추가됩니다. 현재 값: 7 (DBMS_PARALLEL_REPLICAS_PROTOCOL_VERSION). |
| INTERSERVER_EXTERNALLY_GRANTED_ROLES | 54472 | Query | Query 본문에 String external_roles 필드가 추가되며, 위치는 settings terminator와 interserver-secret hash 사이입니다. 외부 클라이언트는 빈 역할 목록을 전송합니다(단일 바이트 0x00, 즉 String envelope 안의 VarUInt 0). |
| V2_DYNAMIC_AND_JSON_SERIALIZATION | 54473 | Column body | Server는 Dynamic 및 JSON 컬럼 타입에 대해 V2 직렬화를 내보낼 수 있으며, 어떤 state_prefix 버전을 사용할지 이를 기준으로 결정합니다. versioned types를 참조하십시오. |
| SERVER_SETTINGS | 54474 | ServerHello | Server는 ServerHello의 끝부분에서 nonce 뒤에 기본값이 아닌 설정들을 목록으로 전송합니다. 포맷은 빈 key로 종료되는 (key, flags, value) 3개 항목의 반복이며, Query packet의 settings 목록과 동일합니다. |
| QUERY_AND_LINE_NUMBERS | 54475 | ClientInfo | ClientInfo의 끝부분에 script_query_number (VarUInt)와 script_line_number (VarUInt)가 추가됩니다. clickhouse-client가 다중 statement 스크립트의 오류 위치를 추적하는 데 사용하며, 외부 클라이언트는 0, 0을 전송합니다. |
| JWT_IN_INTERSERVER | 54476 | ClientInfo | ClientInfo의 끝부분에 JWT 존재 여부를 나타내는 UInt8과 선택적 String jwt가 추가됩니다. 외부 클라이언트(JWT 없음)는 바이트 0x00을 전송합니다. (C++에서는 DBMS_MIN_REVISON_WITH_JWT_IN_INTERSERVER로 표기되며, 상수 이름에 오타가 있음을 유의하십시오.) |
| QUERY_PLAN_SERIALIZATION | 54477 | ServerHello, QueryPlan packet | ServerHello는 server settings 뒤에 VarUInt query_plan_serialization_version를 추가합니다. 또한 사전 구축된 쿼리 계획의 서버 간 전달을 위한 ClientPacket::QueryPlan (code 13)을 도입합니다. 외부 클라이언트는 이를 전송하지 않습니다. |
| PARALLEL_BLOCK_MARSHALLING | 54478 | Block (Column) | Server는 병렬 처리를 위해 컬럼을 ColumnBLOB(인라인 compressed)으로 감쌀 수 있습니다. 이는 쿼리에서 compression이 활성화되어 있고 AND rows > 1일 때만 적용되며, 그렇지 않으면 일반 컬럼 wire 형식이 적용됩니다. 송신 Query packet에서 compression을 활성화하지 않는 클라이언트는 wire 변경이 없습니다. |
| VERSIONED_CLUSTER_FUNCTION_PROTOCOL | 54479 | ServerHello | ServerHello의 끝부분에 VarUInt cluster_function_protocol_version가 추가됩니다. *Cluster 테이블 함수(s3Cluster 등)에 사용됩니다. 외부 클라이언트는 이를 디코드한 뒤 무시합니다. |
| OUT_OF_ORDER_BUCKETS_IN_AGGREGATION | 54480 | BlockInfo | BlockInfo의 필드 태그 기반 스트림에 필드 3 (out_of_order_buckets: Vec<Int32>)이 추가됩니다. [VarUInt count][Int32]*count로 디코드됩니다. 외부 클라이언트는 이를 직접 내보내지 않지만, 디코더는 server가 전송한 비어 있지 않은 목록을 읽습니다. |
| COMPRESSED_LOGS_PROFILE_EVENTS_COLUMNS | 54481 | Log, ProfileEvents, TableColumns | Server는 Log, ProfileEvents, TableColumns packet 본문을 compression frame으로 감쌀 수 있습니다. 이 버전에서는 세 본문 모두 동일한 선택적 압축 출력 경로를 통해 전송되며, 쿼리에 compression = true가 있을 때만 실제 compression frame이 됩니다. 송신 Query packet에서 compression을 활성화하지 않는 클라이언트는 wire 변경이 없습니다. |
| REPLICATED_SERIALIZATION | 54482 | Block (Column) | Server는 kind_stack 0x04 = REPLICATED를 갖는 컬럼을 내보낼 수 있습니다. 이는 반복 값에 대한 딕셔너리 스타일의 압축 포맷이며, kind_stack and sparse encoding을 참조하십시오. 이 버전보다 낮으면 writer는 전송 전에 이러한 컬럼을 펼쳐서 보냈습니다. 행마다 인덱스 조회(elements[indexes[i]])로 디코드되며, leaf 타입과 Nullable/Array/Tuple/Map/Nested/LowCardinality 내부 타입을 지원합니다. |
| NULLABLE_SPARSE_SERIALIZATION | 54483 | Block (Column) | 희소 직렬화를 Nullable(T)와 조합합니다. 이 버전보다 낮으면 writer는 전송 전에 Nullable 컬럼의 희소 표현을 펼쳐서 보냈으며, v54483+에서는 wire 데이터가 Nullable 위의 희소 표현입니다. kind_stack and sparse encoding을 참조하십시오. |
| PROGRESS_IN_ASYNC_INSERT | 54484 | Progress (INSERT) | 비동기 INSERT (async_insert = 1)에서는 삽입이 플러시되면 server가 EndOfStream 전에 추가 Progress packet 하나와 그 뒤에 삽입의 ProfileEvents를 전송합니다. 이는 협상된 버전이 54484 이상일 때만 적용되며, 그보다 낮으면 server는 이 마지막 Progress를 생략합니다. Progress wire 형식 자체는 바뀌지 않았고, 전송만 새로 추가되었습니다. 실제로는 증가분에 경과 시간이 담기며, 기록된 행 수 카운터는 함께 전송되는 ProfileEvents를 통해 보고됩니다. 이미 Progress가 중간에 섞여 들어오는 상황을 처리하는 클라이언트라면 포맷 변경은 필요 없고 packet이 하나 더 올 수 있음을 허용하면 됩니다. |
| CLIENT_AGENT_IN_CLIENT_INFO | 54485 | ClientInfo | ClientInfo의 끝부분에 client_agent String이 추가됩니다. 표준 클라이언트는 환경에서 agent 식별자(예: claude-code, cursor, gemini-cli, 또는 AGENT 변수 값)를 자동 감지하며, 감지된 값이 없는 외부 클라이언트는 빈 문자열을 전송합니다. 협상된 버전이 54485 이상이면 필수이며, 이를 생략하면 Query packet의 나머지 부분과 동기화가 어긋납니다. |
패킷 엔벌로프
VarUInt입니다. 값이 128 미만이면 VarUInt는 동일한 1바이트를 생성하지만, 앞으로 패킷 유형이 128 이상이 되더라도 호환성을 유지할 수 있도록 구현에서는 반드시 VarUInt 인코딩을 사용해야 합니다.
메시지 참고에서는 각 패킷의 본문만 문서화합니다. 즉, 패킷 유형 코드 뒤에 오는 바이트입니다. 필드 번호는 첫 번째 본문 필드를 1로 하여 시작합니다.
청크 프레이밍 (v54470+)
CHUNKED_PROTOCOL 기능이 협상되면(핸드셰이크 참조), wire 상의 모든 패킷은 청크 프레이밍으로 감싸집니다. 이 래핑은 방향별로 적용됩니다. 즉, 클라이언트→서버와 서버→클라이언트는 각각 별도로 협상되며, 서로 다른 모드(청크 방식 또는 프레이밍 없음)로 결정될 수 있습니다.
패킷별 wire 레이아웃:
VarUInt는 청크 처리된 스트림 내부에 있습니다. 즉, 프레이밍보다 앞서 별도로 전송되는 바이트가 아니라 패킷 페이로드의 첫 번째 바이트(첫 번째 청크의 첫 번째 바이트)입니다. 각 패킷의 청크 페이로드는 패킷 엔벨로프의 전체 [VarUInt packet_type_code][message body]입니다. 패킷 유형을 청크 처리된 스트림 바깥에 두는 클라이언트는 상대가 그 유형 바이트를 u32 청크 크기의 첫 번째 바이트로 읽게 만들어 연결 동기화가 깨지게 합니다.
쓰기 버퍼가 패킷 중간에 가득 차면 하나의 패킷이 여러 청크로 분할될 수 있으며, 분할은 패킷 유형의 VarUInt 내부를 포함해 어느 위치에서나 일어날 수 있습니다. 읽는 쪽은 청크 페이로드를 이어 붙이고, 마지막의 4바이트 0을 투명한 패킷 경계로 취급합니다. 즉, 이를 소비하지만 패킷 본문을 읽는 쪽에는 노출하지 않습니다.
본문이 없는 패킷도 여전히 래핑됩니다. Ping 또는 Pong 같은 1바이트 패킷은 청크 처리가 협상되면 [u32 size = 1][0x04][u32 0]이 됩니다. 이 페이지의 다른 곳에서 설명하는 “wire 상의 단일 바이트”는 모두 청크 처리 이전 형태를 의미합니다.
협상. ServerHello와 Addendum은 각각 방향별로 하나씩, 총 2개의 String 필드를 전달하며 값은 {"chunked", "notchunked", "chunked_optional", "notchunked_optional"} 중 하나입니다.
chunked/notchunked는 엄격합니다. 해당 측은 정확히 그 모드를 요구합니다._optional변형은 유연합니다. 상대가 선택한 어느 모드든 허용합니다.
| Server pref | Client pref | Agreed |
|---|---|---|
*_optional | anything | 클라이언트를 따름 (해당 측의 starts_with("chunked")) |
| anything | *_optional | 서버를 따름 |
chunked strict | chunked strict | chunked |
notchunked strict | notchunked strict | notchunked |
| strict mismatch | strict mismatch | 프로토콜 오류 — 연결을 반드시 종료해야 합니다 |
연결 수명 주기
HANDSHAKE, READY, READING_RESPONSE, 또는 종료 상태입니다. 프로토콜은 멀티플렉싱을 지원하지 않으므로, 이전 응답을 모두 수신하기 전에 클라이언트가 새 요청을 보내면 wire 상에서 바이트가 뒤섞여 스트림이 손상됩니다.
상태
HANDSHAKE → READY → READING_RESPONSE → READY로 곧바로 이어지며, Ping/Pong 자체 루프와 모든 실패 경로는 하나의 Terminated 싱크로 수렴합니다.
| 상태 | 설명 |
|---|---|
HANDSHAKE | TCP 연결이 열린 직후의 초기 상태입니다. 핸드셰이크 메시지만 유효합니다. 성공하면 READY로 전이하고, 실패하면 종료됩니다. |
READY | 유휴 상태입니다. 클라이언트는 Ping, 쿼리를 보내거나 연결을 닫을 수 있습니다. 연결은 READY 상태로 무기한 유지될 수 있습니다(idle_connection_timeout의 적용을 받음, 연결 제한 참고). |
READING_RESPONSE | 클라이언트가 Query를 보내면 진입합니다. 클라이언트는 READY로 돌아가기 전에 서버의 응답 스트림을 끝까지 모두 읽어야 합니다. 여기서 클라이언트→서버로 허용되는 유일한 패킷은 Cancel입니다(이 페이지에서는 설명하지 않음). |
| Terminated | 더 이상 사용할 수 없습니다. 클라이언트는 새 TCP 연결을 열고 핸드셰이크를 다시 시작해야 합니다. |
핸드셰이크 단계
-
클라이언트는 지원하는 최대 프로토콜 버전과 함께
ClientHello를 전송합니다. -
클라이언트는 응답을 읽고 패킷 유형에 따라 처리합니다:
패킷 유형 동작 Hello(0)ServerHello를 디코딩합니다.negotiated_version = min(client_ver, server_ver)를 계산합니다. 3단계로 진행합니다.Exception(2)Exception을 디코딩합니다. 이를 오류로 반환하고 연결을 종료합니다.그 밖의 경우 프로토콜 위반입니다. 연결을 종료합니다. -
negotiated_version ≥ 54458(ADDENDUM기능)인 경우, 클라이언트는Addendum을 전송합니다. 이 결정은 클라이언트가 선언한 버전이 아니라 협상된 버전을 기준으로 합니다.
READY로 전환되며, 오류가 발생하면 연결이 종료됩니다.
Ping 단계
READY부터 흐름은 다음과 같습니다:
-
클라이언트가
Ping을 보냅니다. -
클라이언트가 응답을 읽습니다:
패킷 유형 동작 Pong(4)liveness가 확인됩니다. READY로 돌아갑니다.Exception(2)Exception을 디코딩하여 오류로 반환합니다.그 외 모든 경우 프로토콜 위반입니다.
쿼리 단계
EndOfStream 또는 Exception으로 끝나는 패킷 시퀀스입니다.
READY부터 흐름은 다음과 같습니다:
어느 단계에서든 오류가 발생하면 서버는 EndOfStream 대신 Exception을 보내며, 이로써 쿼리가 종료됩니다.
-
클라이언트는 고유한
query_id(일반적으로 UUID)를 포함한Query를 보냅니다. -
클라이언트는 외부 테이블을 모두 보낸 다음 비어 있는 Data 마커를 보냅니다. 빈 Data 패킷은
table_name = "",num_columns = 0,num_rows = 0입니다. 서버는 이 마커를 받기 전까지 쿼리 실행을 시작하지 않습니다. -
클라이언트는
READING_RESPONSE로 전환하고 쓰기 버퍼를 플러시합니다. -
클라이언트는 루프에서 응답 패킷을 읽고, 유형에 따라 처리합니다.
Packet type Action Data(1)블록을 디코드합니다. 첫 번째 Data는 스키마 헤더이고, 그다음부터는 결과 블록(누적)이며, 빈 블록은 경계 마커입니다. num_rows == 0은 쿼리 종료를 의미하는 것이 아닙니다.Progress(3)실행 메트릭입니다. 각 패킷은 이전 패킷 이후의 증가분이므로 로컬에서 누적합니다. EndOfStream(5)쿼리가 완료되었습니다. 루프를 종료하고 READY로 돌아갑니다.ProfileInfo(6)실행 후 프로파일링 데이터입니다. Totals(7)집계 합계 블록입니다(Data와 동일한 wire 형식). Extremes(8)최소/최대값 블록입니다(Data와 동일한 wire 형식). Log(10)서버 로그 한 줄입니다. TableColumns(11)컬럼 기본값 메타데이터입니다. ProfileEvents(14)성능 카운터입니다. Exception(2)디코드한 뒤 오류로 반환합니다. 루프를 종료하고 READY로 돌아갑니다.anything else 쿼리 단계에서는 예상하지 못한 경우입니다. 연결을 종료합니다.
EndOfStream 또는 처리된 Exception이 발생하면 연결은 READY 상태로 돌아갑니다. 프로토콜 위반이나 I/O 오류가 발생하면 연결이 종료됩니다.
num_rows == 0 경우는 새 구현에서 자주 혼동을 일으킵니다. 행 수가 0인 블록은 경계 마커 또는 스키마 헤더일 뿐, 스트림 종료 신호가 아닙니다. 응답은 EndOfStream 또는 Exception이 와야만 끝납니다.INSERT 단계
INSERT 구문을 전송하면 서버는 대상 테이블을 설명하는 스키마 블록으로 응답합니다. 그런 다음 클라이언트는 행이 포함된 데이터 패킷을 스트리밍하고, 이어서 비어 있는 Data 마커를 보냅니다. 마지막으로 서버는 EndOfStream 또는 Exception으로 마무리합니다.
READY 상태에서 시작하면 SQL은 INSERT INTO <table> [(<cols>)] VALUES 형태의 INSERT이며, 행 데이터는 Data 패킷을 통해 전달되므로 인라인 VALUES (...) 리터럴은 포함되지 않습니다. 흐름은 다음과 같습니다:
- 클라이언트는
body를 INSERT SQL로 설정한Query를 전송합니다. - 클라이언트는 외부 테이블(external table)이 있으면 함께 전송합니다(INSERT에서는 드문 경우입니다). Query 단계와 달리, 여기서는 빈 Data 마커를 전송하지 않습니다.
INSERTQuery패킷은 보류 중인 데이터와 함께 전송되므로, 빈 데이터 종료 블록은 5단계로 미뤄집니다. 이를 스키마 블록보다 먼저 전송하면 서버가 이를 행 스트림의 끝으로 해석하여 행 없이 INSERT를 완료한 뒤, 첫 번째 실제 행 패킷을 상위 수준의 잘못된 패킷으로 파싱하게 됩니다. - 클라이언트는 스키마 Data 패킷을 읽을 때까지 메타데이터 패킷(TableColumns, Progress, ProfileInfo, Log, ProfileEvents)을 계속 읽어들입니다. 이 패킷은 0개의 행을 가지지만 전체 컬럼 구조(이름과 타입)를 포함하는 Block입니다. 스키마 블록은 일종의 계약입니다. 즉, 다음에 클라이언트가 전송하는 행은 이 컬럼 구조와 일치해야 합니다.
- 클라이언트는 데이터 블록을 전송합니다. 각 블록마다
VarUInt(ClientPacket::Data = 2)를 기록한 다음, 빈 외부 테이블 이름으로String("")를 기록하고, 그다음 Block을 기록합니다. 컬럼 타입은 위치 기준으로 스키마 블록의 컬럼과 일치해야 합니다. - 클라이언트는 입력 종료 표시자를 전송합니다. 즉, 빈 Block(0개 컬럼, 0개 행)을 담은 Data 패킷입니다.
- 클라이언트는
EndOfStream(성공) 또는Exception(실패)에 도달할 때까지 응답 스트림을 계속 읽어들입니다.
async_insert = 1이 포함되면 서버는 행을 큐에 넣고 배치의 일부로 플러시합니다. 협상된 버전이 54484 이상(PROGRESS_IN_ASYNC_INSERT)이면, 플러시가 완료된 후 서버는 추가 Progress 패킷을 내보내고, 그 직후 insert의 ProfileEvents, 그리고 EndOfStream을 전송합니다. 54484 미만에서는 서버가 이 마지막 Progress를 생략합니다. 이 패킷은 일반적인 Progress입니다. 서버는 쓰기 count를 반영하기 전에 쿼리 pipeline을 재설정하므로, 실제로 이 증가분에는 경과 시간만 포함되고, 기록된 행 수와 바이트 통계는 함께 전송되는 ProfileEvents를 통해 클라이언트에 전달됩니다. 이미 6단계에서 섞여 들어오는 Progress를 계속 읽어들이는 클라이언트는 패킷 하나만 더 받아들이면 됩니다.
EndOfStream 또는 처리된 Exception이 발생하면 connection은 READY 상태로 돌아갑니다. 프로토콜 위반과 I/O 오류가 발생하면 connection이 종료됩니다.
메시지 참고
유형 컬럼은 다음 의미로 사용합니다.
VarUInt— 가변 길이 부호 없는 정수(VarUInt 참조)String— 앞에 VarUInt 길이 값이 붙는 바이트열(String 참조)UInt8,Int32등 — 고정 길이 리틀 엔디언 정수Bool—0x00또는0x01인 1바이트 값
Role 컬럼은 각 필드를 누가 사용하는지 나타냅니다.
- 클라이언트 — 외부 클라이언트가 설정합니다.
- 서버 간 — 서버 간 통신에서만 의미가 있으며, 외부 클라이언트는 기본값을 씁니다.
- universal — 둘 다에서 사용합니다.
ClientHello (packet type 0)
| # | 필드 | 유형 | 역할 | 설명 |
|---|---|---|---|---|
| 1 | client_name | String | 공통 | 클라이언트 식별자(예: "clickhouse-client") |
| 2 | version_major | VarUInt | 공통 | 클라이언트 major 버전 |
| 3 | version_minor | VarUInt | 공통 | 클라이언트 minor 버전 |
| 4 | protocol_version | VarUInt | 공통 | 클라이언트가 지원하는 최대 프로토콜 버전 |
| 5 | database | String | 공통 | 기본 데이터베이스 이름 |
| 6 | user | String | 공통 | 인증에 사용할 username |
| 7 | password | String | 공통 | 비밀번호(plaintext) |
ServerHello (packet type 0)
| # | Field | Type | Role | Condition | Description |
|---|---|---|---|---|---|
| 1 | server_name | String | 공통 | 항상 | 서버 식별자 |
| 2 | version_major | VarUInt | 공통 | 항상 | 서버 메이저 버전 |
| 3 | version_minor | VarUInt | 공통 | 항상 | 서버 마이너 버전 |
| 4 | protocol_version | VarUInt | 공통 | 항상 | 서버의 프로토콜 버전 |
| 4a | parallel_replicas_protocol_version | VarUInt | 공통 | VERSIONED_PARALLEL_REPLICAS_PROTOCOL (v54471) | 서버의 병렬 레플리카 조정 프로토콜 버전입니다. Wire position: protocol_version 바로 뒤, timezone 앞입니다. 현재 값: 7. |
| 5 | timezone | String | 공통 | TIMEZONE (v54058) | 서버 시간대(예: "UTC") |
| 6 | display_name | String | 공통 | DISPLAY_NAME (v54372) | 사람이 읽기 쉬운 서버 이름 |
| 7 | version_patch | VarUInt | 공통 | VERSION_PATCH (v54401) | 서버 패치 버전 |
| 8 | proto_send_chunked_srv | String | 공통 | CHUNKED_PROTOCOL (v54470) | 서버가 선호하는 아웃바운드 청크 처리 방식입니다. "chunked", "notchunked", "chunked_optional", "notchunked_optional" 중 하나입니다. 청크 프레이밍을 참조하십시오. 버전 게이트는 더 높지만 wire에서는 password_complexity_rules보다 앞에 위치합니다. |
| 9 | proto_recv_chunked_srv | String | 공통 | CHUNKED_PROTOCOL (v54470) | 서버가 선호하는 인바운드 청크 처리 방식입니다. 값 집합은 필드 8과 같습니다. |
| 10 | password_complexity_rules | Rule[] | 공통 | PASSWORD_COMPLEXITY_RULES (v54461) | 서버의 비밀번호 정책입니다. 형식은 VarUInt count 다음에 count × Rule이 이어집니다. 아래를 참조하십시오. |
| 11 | nonce | UInt64 | 서버 간 | INTERSERVER_SECRET_V2 (v54462) | 8바이트 LE 랜덤 nonce입니다. 서버의 서버 간 쿼리 서명 scheme에서 사용합니다. 외부 클라이언트는 스트림 정렬을 유지하기 위해 반드시 이를 디코딩해야 하며, 값은 무시하는 것이 좋습니다. |
| 12 | server_settings | Setting[] | 공통 | SERVER_SETTINGS (v54474) | 서버의 비기본 설정 브로드캐스트입니다. 포맷: 비어 있는 key로 종료되는 0개 이상의 (String key, VarUInt flags, String value) 3개 항목입니다. Query packet’s settings list와 같습니다. |
| 13 | query_plan_serialization_version | VarUInt | 공통 | QUERY_PLAN_SERIALIZATION (v54477) | 서버가 지원하는 쿼리 계획 serialization version입니다. 외부 클라이언트는 디코딩만 하고 무시합니다. |
| 14 | cluster_function_protocol_version | VarUInt | 공통 | VERSIONED_CLUSTER_FUNCTION_PROTOCOL (v54479) | 서버의 *Cluster table-function 프로토콜 버전입니다. 외부 클라이언트는 디코딩만 하고 무시합니다. |
password_complexity_rules의 요소:
| # | Field | Type | Description |
|---|---|---|---|
| 1 | pattern | String | 규정을 준수하는 비밀번호가 일치해야 하는 정규 표현식 패턴입니다. |
| 2 | message | String | 비밀번호가 이 규칙을 충족하지 못했을 때 표시되는 사람이 읽기 쉬운 설명입니다. |
악의적이거나 잘못 구성된 서버에 대비해 자원 사용량을 제한하려면, 디코딩된
count는 최대 256개 항목으로 제한하고 각 pattern 및 message String은 최대 4096바이트로 제한하십시오. count가 0인 경우(뒤따르는 쌍이 없음)는 비밀번호 정책이 구성되지 않은 서버에서 일반적으로 나타나는 경우입니다.Addendum (패킷 유형 없음)
ADDENDUM (v54458)일 때만 사용됩니다. 핸드셰이크 교환이 완료된 직후 전송됩니다. 별도의 패킷 유형은 아니며, 필드는 패킷 유형 바이트 접두사 없이 wire에 원시 형태 그대로 기록됩니다.
| # | Field | Type | Role | Condition | Description |
|---|---|---|---|---|---|
| 1 | quota_key | String | 공통 | 항상 | 서버 측 키 기반 쿼터에 사용하는 리소스 쿼터 키입니다. 키 기반 쿼터를 사용하지 않는 클라이언트는 빈 문자열을 보냅니다. |
| 2 | proto_send_chunked | String | 공통 | CHUNKED_PROTOCOL (v54470) | 클라이언트가 협상한 Outbound 청크 처리 방식입니다: "chunked" 또는 "notchunked". ServerHello의 proto_recv_chunked_srv를 기준으로 계산됩니다. |
| 3 | proto_recv_chunked | String | 공통 | CHUNKED_PROTOCOL (v54470) | 클라이언트가 협상한 Inbound 청크 처리 방식입니다. proto_send_chunked_srv를 기준으로 계산됩니다. |
| 4 | parallel_replicas_protocol_version | VarUInt | 공통 | VERSIONED_PARALLEL_REPLICAS_PROTOCOL (v54471) | 클라이언트가 지원하는 병렬 레플리카 coordination 프로토콜 버전입니다. 분산 쿼리에 참여하지 않는 외부 클라이언트도 서버의 호환성 검사가 통과하도록 유효한 버전(현재 7)을 보내야 합니다. |
Ping (패킷 유형 4)
0x04로 구성됩니다. 청킹이 협상되면 이 바이트는 청크의 1바이트 payload가 됩니다(청크 프레이밍 참조).
Pong (패킷 유형 4)
0x04입니다. 청크 사용이 협상되면 이 바이트는 청크의 1바이트 payload가 됩니다(청크 프레이밍 참조).
Exception (패킷 유형 2)
| # | 필드 | 유형 | 역할 | 설명 |
|---|---|---|---|---|
| 1 | code | Int32 | 공통 | 오류 코드 |
| 2 | name | String | 공통 | Exception 클래스(예: "DB::Exception") |
| 3 | message | String | 공통 | 사람이 읽을 수 있는 오류 메시지 |
| 4 | stack_trace | String | 공통 | 서버 측 stack trace |
| 5 | has_nested (obsolete) | Bool | 공통 | 더 이상 사용되지 않는 호환성용 바이트입니다. 서버는 항상 false를 기록합니다. |
Query (패킷 유형 1)
| # | Field | Type | Role | Condition | Description |
|---|---|---|---|---|---|
| 1 | query_id | String | 공통 | always | 고유한 쿼리 식별자(UUID) |
| 2 | client_info | ClientInfo | 공통 | CLIENT_INFO (v54032) | ClientInfo 참조 |
| 3 | settings | Setting[] | 공통 | always | Setting 참조. 항상 포함됩니다(빈 키로 종료). 설정별 인코딩만 버전에 따라 제한되며, 자세한 내용은 Setting의 인코딩 참고를 참조하십시오. 협상된 버전이 54429 미만이면 클라이언트는 이 필드를 생략해서는 안 됩니다. |
| 3a | external_roles | String | 공통 | INTERSERVER_EXTERNALLY_GRANTED_ROLES (v54472) | 외부에서 부여된 역할 이름 목록을 직렬화한 값입니다. 빈 목록은 String 래퍼로 감싼 바이트 0x00(VarUInt 0)이며, wire상에서는 [VarUInt 1][0x00]입니다. 외부 클라이언트는 항상 빈 값을 전송합니다. |
| 4 | auth_hash | String | 서버 간 | INTERSERVER_SECRET (v54441) | 서버 간 인증 해시이며, 원시 클러스터 시크릿은 아닙니다. 자세한 내용은 아래의 Inter-server authentication을 참조하십시오. 외부 클라이언트(및 모든 InitialQuery)는 빈 문자열을 전송합니다. |
| 5 | stage | VarUInt | 공통 | always | 쿼리 처리 단계입니다. 0 = FetchColumns, 1 = WithMergeableState, 2 = Complete, 3 = WithMergeableStateAfterAggregation, 4 = WithMergeableStateAfterAggregationAndLimit, 7 = QueryPlan. 값 3/4는 분산 쿼리에서 나타나며, 7은 직렬화된 쿼리 계획과 함께 사용됩니다. 외부 클라이언트는 일반적으로 2를 전송합니다. |
| 6 | compression | VarUInt | 공통 | always | 0 = 비활성화, 1 = 활성화 |
| 7 | query_body | String | 공통 | always | SQL 텍스트 |
| 8 | parameters | Parameter[] | 클라이언트 | PARAMETERS (v54459) | Parameter 참조. 빈 키로 종료됩니다. |
ClientInfo (Query에 포함됨)
CLIENT_INFO (v54032)로 제어됩니다. (ClientInfo 내부의 일부 field는 아래에 각 field별로 명시된 것처럼 이후 버전에서 제어됩니다.)
| # | Field | 유형 | 역할 | 조건 | 설명 |
|---|---|---|---|---|---|
| 1 | query_kind | UInt8 | 공통 | 항상 | 0 = NoQuery, 1 = InitialQuery, 2 = SecondaryQuery. 외부 클라이언트는 1을 보냅니다. |
| 2 | initial_user | String | 공통 | 항상 | 쿼리를 시작한 사용자 |
| 3 | initial_query_id | String | 공통 | 항상 | 원본 쿼리 ID |
| 4 | initial_address | String | 공통 | 항상 | host:port 포맷의 원본 클라이언트 소켓 주소 |
| 5 | initial_time | Int64 | 클라이언트 | INITIAL_QUERY_START_TIME (v54449) | 쿼리 시작 시간(마이크로초)입니다. VarUInt가 아니라 고정 길이 8바이트입니다. |
| 6 | query_interface | UInt8 | 공통 | 항상 | 1 = TCP, 2 = HTTP |
| 7 | os_user | String | 클라이언트 | 인터페이스 = TCP인 경우 | OS 사용자 이름 |
| 8 | client_hostname | String | 클라이언트 | 인터페이스 = TCP인 경우 | 클라이언트 머신의 호스트명 |
| 9 | client_name | String | 클라이언트 | 인터페이스 = TCP인 경우 | 클라이언트 애플리케이션 이름 |
| 10 | version_major | VarUInt | 공통 | 인터페이스 = TCP인 경우 | 클라이언트 메이저 버전 |
| 11 | version_minor | VarUInt | 공통 | 인터페이스 = TCP인 경우 | 클라이언트 마이너 버전 |
| 12 | protocol_version | VarUInt | 공통 | 인터페이스 = TCP인 경우 | 원본 클라이언트 자체의 TCP 프로토콜 버전(DBMS_TCP_PROTOCOL_VERSION)이며, 협상된 버전이 아닙니다. 피어 리비전은 어떤 필드가 포함되는지만 결정합니다. 이 값은 initiator에 컴파일된 버전이므로, 더 최신 클라이언트가 더 오래된 server와 통신하는 경우 협상된/server 리비전보다 더 클 수 있습니다. |
| 13 | quota_key | String | 공통 | QUOTA_KEY_IN_CLIENT_INFO (v54060) | 서버 측 키 기반 쿼터에 사용하는 리소스 쿼터 키입니다. 키 기반 쿼터를 사용하지 않는 클라이언트는 빈 문자열을 보냅니다. |
| 14 | distributed_depth | VarUInt | 서버 간 | DISTRIBUTED_DEPTH (v54448) | 분산 쿼리의 중첩 깊이입니다. 외부 클라이언트는 0을 보냅니다. |
| 15 | version_patch | VarUInt | 공통 | VERSION_PATCH (v54401), TCP only | 클라이언트 패치 버전 |
| 16 | open_telemetry | (below) | 클라이언트 | OPEN_TELEMETRY (v54442) | 추적 컨텍스트입니다. tracing을 사용하지 않는 클라이언트는 0을 보냅니다. |
| 17 | collaborate_with_initiator | VarUInt | 서버 간 | PARALLEL_REPLICAS (v54453) | Bool 값을 VarUInt로 표현합니다. 외부 클라이언트는 0을 보냅니다. |
| 18 | count_participating_replicas | VarUInt | 서버 간 | PARALLEL_REPLICAS (v54453) | 외부 클라이언트는 0을 보냅니다. |
| 19 | number_of_current_replica | VarUInt | 서버 간 | PARALLEL_REPLICAS (v54453) | 외부 클라이언트는 0을 보냅니다. |
| 20 | script_query_number | VarUInt | 클라이언트 | QUERY_AND_LINE_NUMBERS (v54475) | 여러 statement로 구성된 스크립트에서 1부터 시작하는 statement 위치입니다. 외부 클라이언트는 0을 보냅니다. |
| 21 | script_line_number | VarUInt | 클라이언트 | QUERY_AND_LINE_NUMBERS (v54475) | 소스 스크립트 내에서 1부터 시작하는 줄 번호입니다. 외부 클라이언트는 0을 보냅니다. |
| 22 | jwt_present | UInt8 | 서버 간 | JWT_IN_INTERSERVER (v54476) | 0 = JWT 없음, 1 = 뒤이어 JWT가 옵니다. JWT 인증을 사용하지 않는 외부 클라이언트는 0을 보냅니다. |
| 23 | jwt | String | 서버 간 | JWT_IN_INTERSERVER (v54476), if jwt_present=1 | JWT Bearer token이며, 필드 22가 1일 때만 포함됩니다. |
| 24 | client_agent | String | 클라이언트 | CLIENT_AGENT_IN_CLIENT_INFO (v54485) | 마지막 필드입니다. 환경에서 자동 감지된 클라이언트 도구/agent의 식별자입니다(예: claude-code, cursor, gemini-cli 또는 AGENT env var). 감지된 agent가 없는 외부 클라이언트는 빈 문자열을 보냅니다. 협상된 버전이 54485 이상이면 일반 Query 경로에 포함됩니다(TCP뿐 아니라 모든 인터페이스에서 전송됨). |
인터페이스에 따른 레이아웃(필드 7–12)위의 필드 7–12는 TCP 브랜치입니다.
query_interface(필드 6)가 TCP가 아닌 경우, 이 필드들은 단순히 선택적으로 생략되는 것이 아니라 다른 wire 레이아웃으로 대체됩니다. 따라서 디코더는 필드 6을 기준으로 분기해야 합니다.query_interface = 2(HTTP): 서버가 전달한 HTTP 요청 정보가 대신 기록됩니다. 즉,http_method(UInt8),http_user_agent(String), 그리고forwarded_for(String,X_FORWARDED_FOR_IN_CLIENT_INFOv54443일 때만 포함) 및http_referer(String,REFERER_IN_CLIENT_INFOv54447일 때만 포함) 순서입니다.os_user/client_hostname/client_name/version_*/protocol_version필드는 존재하지 않습니다.- 그 밖의 모든 interface: TCP 필드(7–12)도 HTTP 필드도 기록되지 않으며, 스트림은 바로
quota_key로 이어집니다.
quota_key(필드 13)와 distributed_depth(필드 14)는 모든 interface에 대해 뒤따르며, version_patch(필드 15)는 TCP에서만 기록됩니다.이 브랜치는 주로 서버 간 트래픽에서 중요합니다. 이 경우 시작 서버는 원래 HTTP로 들어온 쿼리를 전달합니다. TCP 필드를 항상 읽도록 구현된 디코더는 이런 패킷을 잘못 해석하여 http_method나 http_user_agent를 quota_key로 처리하게 됩니다.서버 간 인증
auth_hash)는 wire 상에서 공유되는 클러스터 시크릿이 아닙니다. 원본 시크릿을 그대로 보내면 인증에 실패할 뿐 아니라 시크릿도 노출됩니다. 대신, 서버 간 클라이언트로 동작하는 서버는 솔트가 적용된 SHA-256 해시를 사용해 시크릿을 알고 있음을 증명합니다.
- 서버 간 모드로 진입합니다. 연결하는 서버는
ClientHello안에서 이를 알립니다.user필드는 서버 간 마커이고password는 비어 있습니다. 그런 다음 동일한ClientHello패킷의 일부로user/password필드 바로 뒤에 클러스터 이름과 새로 생성한 32바이트salt(임의 값의encodeSHA256)라는 두 개의 문자열을 추가합니다. 서버는ServerHello를 보내기 전에 이 두 문자열을 읽으므로 클라이언트는 이를 미리 써야 합니다. 먼저ServerHello를 기다리면 교착 상태(deadlock)가 발생하는데, 서버가 이 값을 읽으려고 대기하고 있기 때문입니다. - nonce를 가져옵니다.
INTERSERVER_SECRET_V2(v54462)가 협상되면ServerHello에는 8바이트UInt64nonce가 포함됩니다. - 해시를 계산합니다. 모든 non-
InitialQueryQuery 패킷에 대해 클라이언트는 필드 4에encodeSHA256(salt + nonce + cluster_secret + query + query_id + initial_user + external_roles)를 씁니다. 이는 32바이트 다이제스트입니다. (nonce는 10진수 문자열 형식이며 v54462 이상이 협상된 경우에만 포함됩니다.external_roles는INTERSERVER_EXTERNALLY_GRANTED_ROLES(v54472)가 협상된 경우에만 추가됩니다.)InitialQuery이거나 클러스터 시크릿이 구성되지 않은 경우에는 대신 빈 문자열을 씁니다. - 검증합니다. 서버는 필드 4를 32바이트 cap으로 읽고 자체 클러스터 시크릿 사본을 사용해 같은 연결 문자열을 다시 계산합니다. 다이제스트가 다르면 연결이 거부됩니다.
auth_hash를 보냅니다.
설정
VarUInt 0으로 끝납니다. 설정별 인코딩만 협상된 버전에 따라 달라지며, SETTINGS_SERIALIZED_AS_STRINGS(v54429)로 구분됩니다.
v54429+ (STRINGS_WITH_FLAGS) — 각 setting은 다음과 같은 3개 요소로 구성됩니다:
| # | Field | Type | Role | Description |
|---|---|---|---|---|
| 1 | key | String | universal | Setting 이름입니다. 비어 있으면 목록의 끝입니다. |
| 2 | flags | VarUInt | universal | 메타데이터 비트 플래그입니다. 아래를 참조하십시오. |
| 3 | value | String | universal | 문자열 형식의 Setting 값입니다. |
key가 비어 있으면 field 2와 3은 존재하지 않습니다.
Pre-54429 (BINARY) — 각 setting은 [String key][type-specific binary value] 형식입니다. 즉, flags field는 기록되지 않으며, 값은 10진수/텍스트 문자열이 아니라 설정의 네이티브 binary form(예: 고정 폭 정수 또는 길이 접두 문자열)으로 인코딩됩니다. 목록은 여전히 빈 key로 종료됩니다. 협상된 버전이 54429 미만인 클라이언트는 위의 3개 요소 형식이 아니라 이 binary form을 읽고 써야 합니다. (사용자 정의 설정은 예외입니다. 두 인코딩 모두에서 항상 flags와 문자열 값을 가집니다.)
flags field에는 다음 정보가 담깁니다:
0x01— Important: 이 setting은 쿼리 결과에 영향을 주므로, 오래된 peer가 이를 조용히 무시해서는 안 됩니다.0x02— Custom: 사용자 정의 설정입니다.0x0c— 독립적인 플래그가 아니라 2-bit tier field입니다:0x00= Production,0x04= Obsolete,0x08= Experimental,0x0c= Beta. 2비트 전체(flags & 0x0c)를 읽어야 합니다. 단순히flags & 0x04로 검사하면 Beta(0x0c)를 Obsolete로 잘못 분류하게 됩니다.0x80— HotReload(재시작 없이 구성 다시 로드, flags enum에 정의되어 있으며 주로 coordination settings에서 확인됨).
매개변수
SELECT {x:UInt64}와 같은 매개변수화된 쿼리에서 사용하는 쿼리 매개변수입니다. Custom 플래그(0x02)가 설정된 Setting과 동일한 방식으로 인코딩되며, 마찬가지로 빈 key로 종료됩니다.
| # | 필드 | 유형 | 역할 | 설명 |
|---|---|---|---|---|
| 1 | key | String | 클라이언트 | 매개변수 이름입니다. 비어 있으면 목록의 끝을 의미합니다. |
| 2 | flags | VarUInt | 클라이언트 | 항상 0x02(Custom)입니다. |
| 3 | value | String | 클라이언트 | 문자열 형식의 매개변수 값입니다. 따옴표 처리에 대해서는 아래 참고를 확인하십시오. |
매개변수 값은 원시 리터럴이 아니라 값의 SQL 표현입니다. String 유형 매개변수는 반드시 이미 작은따옴표로 감싼 상태로 전달해야 합니다(예:
{name:String}의 값은 Alice가 아니라 'Alice'입니다). 그렇지 않으면 server의 값 parser가 이를 거부합니다.데이터 (패킷 유형 1 server→client, 패킷 유형 2 client→server)
table_name 접두사가 포함됩니다. 다른 점은 패킷 유형 바이트뿐입니다.
| Field | Type | Role | Description |
|---|---|---|---|
| table_name | String | 범용 | 외부 테이블(External Table) 이름입니다. 빈 값("")이 일반적인 경우이며, 주 테이블, 쿼리 결과, INSERT 행 스트림에서 사용됩니다. table_name만 비어 있다고 해서 데이터 종료 마커를 의미하지는 않습니다(일반적인 INSERT 행 패킷에도 ""가 포함됩니다). |
| Block body | — | — | Block & column structure를 참조하십시오. |
table_name과 관계없이 Block이 비어 있는 패킷, 즉 0개 컬럼과 0개 행을 가진 패킷입니다. 서버는 디코딩된 블록이 비어 있을 때(block.empty())에만 클라이언트 Data 패킷을 종료자로 처리합니다. table_name = ""이고 비어 있지 않은 블록을 가진 패킷은 종료자가 아니라 일반적인 행 패킷입니다. 따라서 INSERT 행 스트림은 비어 있지 않은 Data 블록의 시퀀스로 이루어지며, 마지막은 이를 끝내는 하나의 비어 있는 Data 블록입니다.
블록 변형과 각 의미는 Block variants에 설명되어 있습니다.
Progress (패킷 유형 3)
Progress 패킷 이후의 증가분이 담기며 누적 합계는 포함되지 않습니다. 전송 전에 서버는 카운터를 읽은 뒤 이를 원자적으로 0으로 재설정하고, 마지막 전송 이후의 시간 차이로 elapsed_ns를 계산합니다. 따라서 실행 중 합계를 얻으려면 클라이언트가 연속해서 도착하는 패킷을 로컬에서 반드시 누적해야 합니다. 패킷을 절대값으로 취급하면 패킷이 2개 이상 도착했을 때 진행률 표시가 뒤로 되돌아가거나 실제보다 적게 집계될 수 있습니다.
| # | 필드 | 유형 | 역할 | 조건 | 설명 |
|---|---|---|---|---|---|
| 1 | rows | VarUInt | universal | always | 이전 패킷 이후 읽은 행 수(실행 중 합계에 더함) |
| 2 | bytes | VarUInt | universal | always | 이전 패킷 이후 읽은 바이트 수(실행 중 합계에 더함) |
| 3 | total_rows | VarUInt | universal | always | 읽을 것으로 추정되는 전체 행 수의 증가분입니다. 누적해야 합니다(특정 패킷에서는 0일 수 있음) |
| 4 | total_bytes | VarUInt | universal | TOTAL_BYTES_IN_PROGRESS (v54463) | 읽을 것으로 추정되는 전체 바이트 수의 증가분입니다. 누적해야 합니다. wire 상에서 total_rows와 wrote_rows 사이에 위치합니다. |
| 5 | wrote_rows | VarUInt | universal | WRITE_CLIENT_INFO (v54420) | 이전 패킷 이후 기록된 행 수(INSERT용)이며, 누적해야 합니다 |
| 6 | wrote_bytes | VarUInt | universal | WRITE_CLIENT_INFO (v54420) | 이전 패킷 이후 기록된 바이트 수(INSERT용)이며, 누적해야 합니다 |
| 7 | elapsed_ns | VarUInt | universal | SERVER_QUERY_TIME_IN_PROGRESS (v54460) | 이전 패킷 이후 경과한 나노초 수입니다(전체 쿼리 시간이 아니라 증가분). 누적해야 합니다 |
ProfileInfo (패킷 유형 6)
| # | Field | Type | Role | Condition | Description |
|---|---|---|---|---|---|
| 1 | rows | VarUInt | universal | always | 처리된 총 행 수 |
| 2 | blocks | VarUInt | universal | always | 처리된 총 블록 수 |
| 3 | bytes | VarUInt | universal | always | 처리된 총 바이트 수 |
| 4 | applied_limit | Bool | universal | always | LIMIT 절이 적용되었는지 여부 |
| 5 | rows_before_limit | VarUInt | universal | always | LIMIT 적용 전 행 수 |
| 6 | obsolete | Bool | universal | always | 사용되지 않는 호환성 바이트입니다. server는 이 위치에 항상 true를 기록하고 client는 읽을 때 이를 버립니다. 이는 “rows_before_limit가 계산되었음”을 나타내는 flag가 아닙니다. 실제로 의미가 있는 limit 상태는 필드 4(applied_limit)와 필드 5를 함께 봐야 합니다. 읽고 무시하십시오. |
| 7 | applied_aggregation | Bool | universal | ROWS_BEFORE_AGGREGATION (v54469) | GROUP BY가 적용되었는지 여부 |
| 8 | rows_before_aggregation | VarUInt | universal | ROWS_BEFORE_AGGREGATION (v54469) | 집계 전 행 수 |
합계 (패킷 유형 7)
WITH TOTALS가 포함된 쿼리에 대해 전송됩니다. wire 형식은 데이터와 동일합니다. 즉, table_name 문자열(항상 비어 있음) 다음에 Block이 옵니다. 다른 점은 패킷 유형 바이트뿐입니다.
극값 (패킷 유형 8)
extremes 설정이 활성화되면 전송됩니다. wire 형식은 데이터와 동일합니다. 이 블록에는 정확히 2개의 행이 있으며, 0번 행에는 각 컬럼의 최솟값이, 1번 행에는 최댓값이 들어 있습니다.
Log (패킷 유형 10)
send_logs_level 설정, 로그 스트리밍 참조).
데이터와 동일한 envelope 및 body 포맷을 사용합니다. 블록은 고정된 num_columns = 8과 사전 정의된 스키마를 가집니다. 각 로그 라인은 8개 컬럼 전체에 걸친 하나의 행이며, 하나의 Log packet에 여러 행이 담길 수 있습니다.
| # | 이름 | 유형 | 설명 |
|---|---|---|---|
| 1 | event_time | DateTime | 이벤트 타임스탬프(epoch 이후 초) |
| 2 | event_time_microseconds | UInt32 | 마이크로초 구성 요소 |
| 3 | host_name | String | 로그를 출력하는 server의 호스트명 |
| 4 | query_id | String | 해당 로그가 속한 쿼리 ID |
| 5 | thread_id | UInt64 | OS thread ID |
| 6 | priority | Int8 | 로그 레벨 (Poco priority: 1 = Fatal, … 8 = Trace) |
| 7 | source | String | 로거 이름 |
| 8 | text | String | 로그 메시지 텍스트 |
ProfileEvents (패킷 유형 14)
num_columns는 6으로 고정되어 있으며, 스키마는 미리 정의되어 있습니다. 각 이벤트는 하나의 행입니다.
| # | Name | 유형 | 설명 |
|---|---|---|---|
| 1 | host_name | String | 서버 호스트명 |
| 2 | current_time | DateTime | 이벤트 타임스탬프 |
| 3 | thread_id | UInt64 | 스레드 ID |
| 4 | type | Enum8 | 이벤트 유형: 1 = Increment (counter), 2 = Gauge. 기본 저장 형식은 부호 있는 1바이트입니다. |
| 5 | name | String | 이벤트 이름(예: "Query", "NetworkReceiveBytes") |
| 6 | value | Int64 | 카운터 값 또는 게이지 값 |
value 컬럼의 타입은 패킷마다 고정되어 있지 않습니다 — 이전 서버는 UInt64를 내보내고, 최신 서버는 Int64를 내보냅니다. 하나의 비트 폭으로 단정하지 말고 블록 헤더에서 컬럼의 타입 문자열을 읽으십시오.TableColumns (패킷 유형 11)
COLUMN_DEFAULTS_METADATA (v54410) 조건에서만 전송됩니다. 서버는 컬럼 기본값 메타데이터를 전달하기 위해 INSERT 스키마 블록 앞에 이를 전송하지만, 협상된 버전이 54410 이상이고 또한 input_format_defaults_for_omitted_fields 설정이 활성화된 경우에만 전송합니다. 54410 미만에서는 이 패킷이 전혀 전송되지 않으므로, 구버전 클라이언트는 이를 기다리지 않아야 합니다 — 스키마 Data 블록이 바로 이어집니다. v54410+ 클라이언트는 어느 순서든 처리할 수 있어야 합니다. 즉, 선택적 TableColumns 다음에 스키마 블록이 올 수 있습니다.
| # | Field | 유형 | Role | 설명 |
|---|---|---|---|---|
| 1 | external_table | String | universal | 외부 테이블 이름입니다. 비어 있으면 기본 테이블을 의미합니다. |
| 2 | columns_description | String | universal | 텍스트 형태의 컬럼 정의입니다. 예: "id Int32, name String DEFAULT ''". 자유 형식 텍스트이므로 문자열로 해석하십시오. |
v54481+에서 압축된 body협상된 버전이 54481 이상(
COMPRESSED_LOGS_PROFILE_EVENTS_COLUMNS)이면 서버는 두 필드를 동일한 선택적 압축 출력 경로를 통해 기록합니다. 따라서 쿼리에 compression = true가 설정된 경우 전체 TableColumns body(external_table + columns_description)가 compression frame 내부에 포함되며, 클라이언트는 이에 대응하는 압축 해제 스트림을 통해 이를 읽습니다. 쿼리에 압축이 없으면 body는 위 표와 정확히 동일한 형태로 압축되지 않은 채 wire에 기록됩니다. 이는 INSERT 스키마 응답에서 중요합니다. Log와 ProfileEvents에 대해서만 압축 처리를 전환하고 TableColumns에는 적용하지 않는 클라이언트는 쿼리 압축이 활성화된 경우 응답을 잘못 읽게 됩니다.TimezoneUpdate (패킷 유형 17)
TIMEZONE_UPDATES(v54464)로 제어됩니다. 이 패킷은 정확히 한 곳에서만 전송됩니다. 바로 input 테이블 함수의 초기화기입니다(INSERT INTO <table> SELECT ... FROM input('<structure>') 형태의 쿼리로, 클라이언트에서 행을 스트리밍함). 서버가 입력 스키마 Data 블록을 보낸 직후(INSERT 단계 참조), 쿼리 Context의 현재 session_timezone을 담은 TimezoneUpdate를 전송하여 클라이언트가 곧 보낼 행을 동일한 시간대로 parse하도록 합니다. 서버는 쿼리 도중 임의로 수행된 SET session_timezone 변경에 대해서는 이 패킷을 전송하지 않으며, 이후 결과 블록을 어떤 형식으로 포맷해야 하는지 클라이언트에 알리기 위해서도 전송하지 않습니다.
| # | Field | Type | Role | Description |
|---|---|---|---|---|
| 1 | timezone | String | universal | 새 세션 기본 시간대(예: "UTC", "Europe/Berlin"). |
TimezoneUpdate를 무시하는 디코더도 wire 정렬을 유지하려면 뒤따르는 String은 반드시 소비해야 합니다.
SSH 챌린지-응답 인증(패킷 유형 11, 12, 18)
SSH_AUTHENTICATION(v54466)으로 제어되며, 명시적으로 활성화한 경우에만 사용할 수 있습니다. ClientHello가 user = " SSH KEY AUTHENTICATION " + <real_user>(앞뒤 공백 포함)와 password = ""를 보내면 연결은 SSH 흐름으로 진입합니다. 서버는 접두사를 읽고 이를 제거해 실제 사용자를 복원한 다음 챌린지-응답 방식으로 전환합니다.
| Packet | Code | Direction | Body |
|---|---|---|---|
| SSHChallengeRequest | 11 | Client → Server | (본문 없음) |
| SSHChallenge | 18 | Server → Client | String challenge — 임의의 바이트이며, 서명할 문자열을 구성하는 요소 중 하나입니다(아래 참조) |
| SSHChallengeResponse | 12 | Client → Server | String signature — 아래에 정의된 연결 결과에 대한 SSH 서명이며, 원시 challenge 자체에 대한 서명은 아닙니다 |
- 클라이언트는 SSH 마커 접두사와 빈 password를 포함한 ClientHello를 보냅니다.
-
클라이언트는
SSHChallengeRequest(packet 11)를 보냅니다. 서버는 아직 ServerHello를 보내지 않은 상태이며, 먼저 인증을 처리한 뒤 이 packet를 기다리면서 여기서 대기합니다. -
서버는 임의의 바이트를 담은
SSHChallenge(packet 18)로 응답합니다. -
클라이언트는 서명할 문자열을 구성하고 원시 challenge가 아니라 그 문자열 자체에 서명한 다음, 서명을 담은
SSHChallengeResponse(packet 12)를 보냅니다. 서명되는 메시지는 아래 네 부분을 정확히 이 순서대로, 구분자 없이 바이트 단위로 이어 붙인 값입니다.Part Source decimal(protocol_version)클라이언트의 protocol version을 10진수 ASCII 문자열로 표현한 값입니다(예: "54466"). 즉, 버전 번호를 문자열로 나타낸 것이며 VarUInt나 고정 폭 정수가 아닙니다. 서버는ClientHello에서 받은 동일한 protocol version을 사용해 검증합니다.default_databaseClientHello의database필드입니다(없으면 빈 문자열).user" SSH KEY AUTHENTICATION "마커 접두사를 제거한 실제 사용자 이름입니다. 즉, 서버가 접두사를 제거한 뒤 복원하는 것과 동일한 이름입니다.challengeSSHChallengepacket의 원시challenge바이트입니다. -
서버는 사용자의 등록된 public key를 기준으로 서명을 검증하며, 동일한
decimal(protocol_version) + default_database + user + challenge문자열을 다시 구성합니다. 성공하면ServerHello를 보냅니다. 이는 비밀번호 흐름에서와 동일한 응답이며, 이후 핸드셰이크는 정상적으로 계속됩니다(Addendum 등). 실패하면Exception을 반환하고 연결을 종료합니다. 원시 challenge 바이트에만 서명하는 클라이언트는 인증에 실패합니다.
이는 password 핸드셰이크와 반대로, ClientHello 직후에 ServerHello가 바로 이어집니다. SSH 인증에서는 서명이 검증될 때까지 ServerHello가 전송되지 않으므로, ServerHello가 나타나기 전에 SSH 챌린지-응답이 핸드셰이크 중간에 끼어들어 처리됩니다.
패킷 유형 참고
클라이언트 → 서버
| Code | Name | Body format | Description |
|---|---|---|---|
| 0 | Hello | ClientHello | 핸드셰이크 시작 |
| 1 | Query | Query | 쿼리 실행 요청 |
| 2 | Data | Data | 데이터 블록 (INSERT 데이터, 외부 테이블(external table), 데이터 종료 마커) |
| 3 | Cancel | (본문 없음) | 실행 중인 쿼리 취소 |
| 4 | Ping | Ping | 연결 상태 확인 |
| 5 | TablesStatusRequest | 지정되지 않음 | 테이블 상태 확인 |
| 6 | KeepAlive | 지정되지 않음 | 연결 유지 |
| 7 | Scalar | 지정되지 않음 | 스칼라 데이터 블록 |
| 8 | IgnoredPartUUIDs | 지정되지 않음 | 쿼리에서 제외할 파트 |
| 9 | ReadTaskResponse | 지정되지 않음 | S3 클러스터 읽기 응답 |
| 10 | MergeTreeReadTaskResponse | 지정되지 않음 | 병렬 읽기 작업 응답 |
| 11 | SSHChallengeRequest | SSH 인증 | SSH 인증 챌린지 요청 |
| 12 | SSHChallengeResponse | SSH 인증 | SSH 인증 챌린지 응답 |
| 13 | QueryPlan | 지정되지 않음 | 쿼리 계획 |
서버 → 클라이언트
| 코드 | 이름 | 본문 포맷 | 설명 |
|---|---|---|---|
| 0 | Hello | ServerHello | 핸드셰이크 응답 |
| 1 | Data | Data | 결과 데이터 블록 |
| 2 | Exception | Exception | 오류 |
| 3 | Progress | Progress | 쿼리 실행 진행 상황 |
| 4 | Pong | Pong | 상태 확인 응답 |
| 5 | EndOfStream | (본문 없음) | 쿼리 완료 |
| 6 | ProfileInfo | ProfileInfo | 실행 후 프로파일링 데이터 |
| 7 | Totals | Totals | GROUP BY WITH TOTALS 행 |
| 8 | Extremes | Extremes | 최솟값/최댓값(2행 블록) |
| 9 | TablesStatusResponse | 지정되지 않음 | 테이블 상태 응답 |
| 10 | Log | Log | 쿼리 실행 로그 라인 |
| 11 | TableColumns | TableColumns | 기본값에 대한 컬럼 설명 |
| 12 | PartUUIDs | 지정되지 않음 | 고유 파트 ID |
| 13 | ReadTaskRequest | 지정되지 않음 | cluster 읽기 작업 요청 |
| 14 | ProfileEvents | ProfileEvents | 성능 카운터 |
| 15 | MergeTreeAllRangesAnnouncement | 지정되지 않음 | 병렬 읽기 초기화 |
| 16 | MergeTreeReadTaskRequest | 지정되지 않음 | 병렬 읽기 작업 할당 |
| 17 | TimezoneUpdate | TimezoneUpdate | 서버 시간대 업데이트 |
| 18 | SSHChallenge | SSH 인증 | SSH 인증 챌린지 |
구성
- 전송 계층 설정 — TCP socket 옵션과 timeout으로, TCP 연결 자체의 동작 방식에 영향을 줍니다.
- 애플리케이션 계층 설정 — Query packet의 설정 목록에 포함되는 쿼리별 조정 항목으로, 서버가 wire 상으로 무엇을 보내는지 또는 데이터가 어떻게 프레이밍되는지에 영향을 줍니다.
- 범위 밖의 설정 — 프로토콜 설정과 자주 혼동되지만, 실제로는 SQL 실행이나 storage를 제어하는 설정입니다.
전송 계층 설정
소켓 옵션
| Option | Default | Side | Description |
|---|---|---|---|
TCP_NODELAY | 켜짐 | 양쪽 | Nagle 알고리즘이 비활성화됩니다. 작은 패킷이 즉시 전송됩니다. |
SO_KEEPALIVE | 켜짐 (클라이언트), OS 기본값 (서버) | 비대칭 | 커널 수준의 TCP keepalive 프로브입니다. tcp_keep_alive_timeout > 0일 때 클라이언트가 이를 명시적으로 활성화합니다. 서버는 OS 기본값을 따릅니다. |
SO_RCVBUF / SO_SNDBUF | OS 기본값 | — | 소켓 버퍼 크기입니다. protocol에서 별도로 조정하지 않습니다. |
타임아웃
| Setting | Default | Unit | Side | Description |
|---|---|---|---|---|
connect_timeout | 10 | 초 | 클라이언트 | 초기 TCP 연결을 설정하는 타임아웃입니다. |
handshake_timeout_ms | 10000 | 밀리초 | 클라이언트 | 핸드셰이크 중 ServerHello를 수신하는 타임아웃입니다. |
send_timeout | 300 | 초 | 양측 | 이 인터벌 내에 바이트를 전혀 쓸 수 없으면 연결에서 예외가 발생합니다. |
receive_timeout | 300 | 초 | 양측 | 이 인터벌 내에 바이트를 전혀 읽을 수 없으면 연결에서 예외가 발생합니다. |
tcp_keep_alive_timeout | 290 | 초 | 클라이언트 | OS가 첫 번째 TCP keepalive probe를 보내기 전까지의 유휴 시간입니다. |
receive_data_timeout_ms | 2000 | 밀리초 | 클라이언트 | 레플리카로부터 첫 번째 데이터 패킷을 수신하는 타임아웃입니다. |
connect_timeout_with_failover_ms | 1000 | 밀리초 | 클라이언트 | 레플리카를 순회할 때 각 시도에 적용되는 연결 타임아웃입니다. |
connect_timeout_with_failover_secure_ms | 1000 | 밀리초 | 클라이언트 | TLS를 통해 레플리카를 순회할 때 각 시도에 적용되는 연결 타임아웃입니다. |
hedged_connection_timeout_ms | 50 | 밀리초 | 클라이언트 | 헤지드 요청의 각 시도에 적용되는 연결 타임아웃입니다. |
poll_interval | 10 | 초 | 서버 | 서버의 유휴 연결 및 종료 확인 루프의 세분화 수준입니다. |
연결 제한
| 설정 | 기본값 | 단위 | 측 | 설명 |
|---|---|---|---|---|
max_connections | 4096 | 개수 | 서버 | 최대 동시 TCP 연결 수입니다. |
idle_connection_timeout | 3600 | 초 | 서버 | 유휴 연결을 열린 상태로 유지할 수 있는 최대 시간입니다. |
tcp_close_connection_after_queries_num | 0 (무제한) | 개수 | 서버 | 강제로 종료되기 전 연결당 허용되는 최대 쿼리 수입니다. |
tcp_close_connection_after_queries_seconds | 0 (무제한) | 초 | 서버 | 활동 여부와 관계없이 연결이 유지될 수 있는 최대 총 시간입니다. |
애플리케이션 계층 설정
압축
| 설정 | 기본값 | 단위 | 설명 |
|---|---|---|---|
network_compression_method | "LZ4" | 문자열 | Query 패킷의 compression 플래그가 설정된 경우 사용하는 압축 코덱입니다. 값: "LZ4", "LZ4HC", "ZSTD", "NONE". |
network_zstd_compression_level | 1 | 1–15 | network_compression_method == "ZSTD"일 때의 ZSTD 수준입니다. |
compression 플래그(필드 6)는 압축을 켜거나 끄며, 이 설정은 압축이 켜져 있을 때 사용할 코덱을 선택합니다.
로그 스트리밍
| 설정 | 기본값 | 단위 | 설명 |
|---|---|---|---|
send_logs_level | "fatal" | string | 최소 로그 레벨입니다. 값: "none", "fatal", "error", "warning", "information", "debug", "trace". |
send_logs_source_regexp | "" | string | 로거 소스에 적용되는 Regex 필터입니다. 빈 값이면 모든 소스가 통과합니다. |
send_logs_level을 "none" 이외의 값으로 설정하면 서버가 쿼리 실행 중 Log 패킷을 전송합니다.
Progress 보고
| Setting | Default | Unit | Description |
|---|---|---|---|
interactive_delay | 100000 | microseconds | 연속된 Progress 패킷 사이의 목표 최소 인터벌입니다. |
결과 엔벌로프
| Setting | Default | Unit | Description |
|---|---|---|---|
extremes | false | bool | true이면 서버가 컬럼별 최소/최대값이 포함된 극값 패킷을 전송합니다. |
max_result_rows | 0 (unlimited) | count | 전송되는 행 수 제한입니다. 동작은 result_overflow_mode로 제어됩니다. |
max_result_bytes | 0 (unlimited) | uncompressed bytes | 전송되는 비압축 바이트 양 제한입니다. 동작은 result_overflow_mode로 제어됩니다. |
result_overflow_mode | "throw" | string | "throw"는 Exception을 발생시키며 스트림을 종료하고, "break"는 부분 결과를 전송한 뒤 EndOfStream을 보냅니다. |
비동기 INSERT
| Setting | Default | Unit | Description |
|---|---|---|---|
async_insert | true | bool | true이면 INSERT 데이터가 서버 측에서 큐에 저장되어 배치로 처리됩니다. |
wait_for_async_insert | true | bool | true이면 (async_insert가 활성화된 경우) 서버는 큐에 있는 데이터가 플러시될 때까지 응답을 보류합니다. |
wait_for_async_insert_timeout | 120 | seconds | 서버가 반환하기 전에 플러시를 기다리는 최대 시간입니다. |
분산 추적
| 설정 | 기본값 | 단위 | 설명 |
|---|---|---|---|
opentelemetry_start_trace_probability | 0.0 | 0–1 확률 | 응답 텔레메트리에 OpenTelemetry 컨텍스트를 연결할 서버 측 확률입니다. |
범위 밖의 설정
wire 동작이 아니라 SQL 실행, 스토리지 또는 CPU 사용을 제어합니다. 프로토콜 구현에서 이를 특별히 처리할 필요는 없습니다.
max_threads— 쿼리 실행 내 병렬성.max_memory_usage— 쿼리별 메모리 상한.max_block_size,preferred_block_size_bytes— 쿼리 처리 중 내부 블록 크기를 결정하며,wire블록은 이들과 독립적입니다.compile_expressions— JIT 컴파일로, CPU에만 영향을 줍니다.async_insert_max_data_size— 서버 측 큐 버퍼.input_format_*및output_format_*설정 중input_format_native_*/output_format_native_*계열을 제외한 모든 설정 — 비-native설정은 다른 포맷을 선택하거나 조정하며(예: HTTP를 통해), 네이티브 프로토콜의Data블록은 변경하지 않습니다.
*_native_* 설정은 예외입니다. 이 설정은 네이티브 TCP Data 블록 내부의 바이트를 변경하므로, 프로토콜 구현에서 이를 반영해야 합니다. output_format_native_encode_types_in_binary_format은 컬럼 type 필드를 텍스트 문자열에서 바이너리 타입 인코딩으로 전환하고, output_format_native_write_json_as_string은 JSON 컬럼을 String으로 내보내며, output_format_native_use_flattened_dynamic_and_json_serialization은 FLATTENED Dynamic/JSON 레이아웃을 선택합니다. 이러한 설정은 패킷 엔벌로프가 아니라 블록 본문에 영향을 주므로, Native 형식 사양에 정의되어 있습니다. 자세한 내용은 컬럼 wire 레이아웃 및 버전 지정 타입을 참조하십시오.
용어집
- 일반 쿼리(
SELECT등): “더 이상 외부 데이터가 없음”을 알리기 위해 Query 패킷과 외부 테이블 Data 패킷 뒤에 전송됩니다. 그러면 서버가 실행을 시작합니다. INSERT: 클라이언트는 스키마 이전 마커를 보내지 않습니다. 먼저 서버가 스키마 블록을 보내고, 그다음 클라이언트가 행 Data 블록을 스트리밍한 뒤, 마지막에 빈 Data 패킷을 보내 행 스트림을 종료합니다. 스키마 블록보다 먼저 빈 마커를 보내면 즉시 행 종료로 해석되어 데이터가 유실됩니다.
min(client_version, server_version)입니다. 연결의 수명 주기 동안 어떤 기능이 활성화되는지 결정합니다.
패킷 — wire 메시지입니다. VarUInt 패킷 유형 코드 뒤에, 유형에 따라 포맷이 달라지는 본문이 이어집니다. 패킷 엔벌로프를 참조하십시오.
패킷 유형 코드 — 패킷 포맷을 식별하는, 패킷 맨 앞의 VarUInt입니다. 현재 0–18 값이 할당되어 있습니다. 패킷 유형 참고를 참조하십시오.
응답 스트림 — 쿼리 중에 서버가 내보내는 패킷 시퀀스입니다. 길이가 고정되어 있지 않으며, 정확히 하나의 EndOfStream(성공) 또는 Exception(실패)으로 종료됩니다. 쿼리 단계를 참조하십시오.
스키마 블록 — 클라이언트가 데이터를 보내기 전에 예상되는 컬럼 형태를 알리기 위해 서버가 INSERT 단계에서 보내는 헤더 블록(컬럼은 있지만 행은 0개인 Block)입니다.
설정 목록 — Query 본문에 있는 (key, flags, value) 튜플의 시퀀스이며, 빈 key로 종료됩니다. 쿼리별 애플리케이션 계층 구성을 전달합니다. Setting을 참조하십시오.
단계 — 서버가 쿼리를 어디까지 실행할지 제어하는 Query 패킷의 VarUInt 필드(필드 5)입니다. 외부 클라이언트는 일반적으로 2(Complete)를 보내며, 분산 쿼리와 직렬화된 쿼리 계획은 더 높은 값을 사용합니다. 전체 wire 값 집합은 Query 필드 5를 참조하십시오.
종결자 — 스트림을 끝내는 패킷입니다. Query 응답은 EndOfStream(성공) 또는 Exception(실패)으로 끝납니다. 클라이언트의 입력 스트림은 빈 Data 마커로 끝납니다.