네이티브 프로토콜은 ClickHouse 클라이언트와 서버가 TCP를 통해 통신할 때 사용하는 바이너리 기반의 연결 지향 프로토콜입니다. 이 프로토콜은 SQL 쿼리, 결과 데이터, INSERT 페이로드, 실행 텔레메트리, 그리고 오류 신호를 전달합니다. command-line client와 C++, 그리고 대부분의 서드파티 네이티브 드라이버가 사용하는 기반 프로토콜이기도 합니다.
이 페이지에서는 프로토콜 자체를 다룹니다. 구체적으로는 패킷 프레이밍, 연결 state machine, 버전 협상, 그리고 Block이 아닌 모든 메시지의 본문을 설명합니다. Data 계열 패킷 내부의 바이트(Block, 그 안의 컬럼, 그리고 유형별 인코딩)는 별개의 주제이며, Native 형식 명세에서 문서화되어 있습니다.
몇 가지 특성은 전체에 걸쳐 공통적으로 적용됩니다. 이 프로토콜은 바이너리이며 위치 기반입니다. BlockInfo 내부를 제외하면 필드 태그가 없으므로, 바이트 하나만 어긋나도 그 뒤에 오는 모든 내용의 동기화가 깨집니다. 또한 상태를 유지하는 프로토콜이며, 각 TCP 연결은 한 번에 하나의 쿼리만 처리합니다. 즉, 멀티플렉싱은 없습니다. 고정 폭 정수는 리틀 엔디언입니다.
개요
| Property | Value |
|---|---|
| 전송 방식 | TCP, 필요에 따라 TLS로 래핑 가능 |
| 바이트 순서 | 고정 폭 정수에는 리틀 엔디언 사용 |
| 인코딩 | 바이너리 및 위치 기반(BlockInfo를 제외하면 필드 태그 없음) |
| 연결 모델 | 상태 유지형, 한 번에 하나의 쿼리, 멀티플렉싱 없음 |
| 버전 관리 | 핸드셰이크에서 협상되며, 개별 기능은 버전에 따라 제한됨 |
| 데이터 포맷 | 모든 테이블 형식 데이터에 Native 형식 사용 |
wire를 통해 전송되는 모든 메시지는 VarUInt 패킷 유형 코드로 시작하며, 그 뒤에는 해당 코드와 협상된 프로토콜 버전에 따라 형식이 달라지는 본문이 이어집니다.
연결은 세 단계로 진행됩니다. 먼저 일회성 핸드셰이크를 수행한 다음, Ping 또는 Query 교환을 원하는 만큼 수행하고, 마지막으로 종료됩니다:
sequenceDiagram
autonumber
participant C as Client
participant S as Server
C->>S: TCP connect
rect rgb(220, 235, 255)
Note over C,S: Handshake
C->>S: ClientHello (name, version, db, user, password)
S->>C: ServerHello (server_name, version, [timezone, display_name, ...])
Note over C,S: negotiated_version = min(client, server)
opt negotiated_version ≥ 54458
C->>S: Addendum (quota_key)
end
end
rect rgb(220, 245, 225)
Note over C,S: Query phase
C->>S: Query packet (ClientInfo, settings, params, SQL)
C->>S: External-table Data packets (0 or more)
C->>S: Empty Data marker — the "go" signal
loop until EndOfStream or Exception
S->>C: Data / Progress / Log / ProfileInfo / Totals / ...
end
S->>C: EndOfStream
end
네이티브 TCP 프로토콜은 SQL의 FORMAT 절과 관계없이 항상 표 형식 데이터를 Native 형식으로 전달합니다. RowBinary, CSV, JSON 등으로 다시 포맷하는 작업은 클라이언트의 역할이며, 클라이언트가 Native 블록을 디코딩한 후 수행합니다. (HTTP 인터페이스는 FORMAT 절을 실제로 반영하는 별도의 코드 경로이며, 여기서는 HTTP를 다루지 않습니다.)
보안
전송 보안(TLS)
TLS는 프로토콜 아래에 있는 전송 계층에서 동작합니다. TLS를 활성화하면 전체 TCP 스트림이 암호화되며, TLS 사용 여부와 관계없이 프로토콜 메시지는 바이트 단위까지 동일합니다.
인증
인증은 핸드셰이크 과정에서 ClientHello 메시지를 통해 수행됩니다. user 및 password 필드는 평문 문자열로 전송되므로, 전송 계층 암호화(TLS)가 전송 중 자격 증명을 보호합니다.
비어 있는 user 필드는 서버에서 구성한 기본 세션 사용자, 즉 default_session_user 서버 설정(기본값은 default)으로 해석되며, protocols 섹션에서 리스너별로 재정의될 수 있습니다. 서버의 기본 세션 사용자가 비어 있도록 구성되어 있거나 서버 버전이 26.8보다 이전인 경우, 비어 있는 user 필드는 예외와 함께 거부됩니다. clickhouse-client는 비어 있는 사용자 이름을 전송하지 않으며, 클라이언트 측에서 default로 대체합니다.
SSH 챌린지-응답 인증은 프로토콜 버전 54466부터 지원됩니다 — 자세한 내용은 SSH 챌린지-응답 인증을 참조하십시오.
서버 간 시크릿
분산 쿼리 실행에서는 서버가 시크릿을 wire에 노출하지 않고도 공유된 시크릿을 알고 있음을 증명하여 서로 인증합니다. 각 Query의 Query 필드 4에는 salt, nonce, 구성된 시크릿, 그리고 쿼리를 바탕으로 계산한 32바이트 SHA-256 auth_hash가 포함되며, 수신 서버는 이를 다시 계산해 비교합니다. 이 동작은 INTERSERVER_SECRET 기능(v54441)으로 제어됩니다. 외부 클라이언트는 이 필드에 항상 빈 문자열을 보냅니다. 서버 간 인증을 참조하십시오.
버전 관리와 기능 게이트
버전 협상
클라이언트와 서버는 핸드셰이크 중에 각각 지원하는 최대 프로토콜 버전을 선언합니다. 협상된 버전은 두 버전 중 더 작은 값입니다:
negotiated_version = min(client_version, server_version)그 이후의 모든 메시지에서는 협상된 버전에 따라 wire에 어떤 필드가 포함될지가 결정됩니다.
기능 게이트
기능은 해당 기능이 도입된 프로토콜 버전으로 식별되며, 협상된 버전이 그 번호와 같거나 크면 활성 상태가 됩니다.
기능 표
| 기능 | 버전 | 영향 대상 | 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의 필드는 청크 프로토콜 문자열 뒤에 추가됩니다. 현재 값: 8 (DBMS_PARALLEL_REPLICAS_PROTOCOL_VERSION). 버전 8은 MergeTreeAllRangesAnnouncementResponse(클라이언트 패킷 14)를 추가합니다. 협상된 병렬 레플리카 버전이 ≥ 8이면 initiator는 Default 모드가 아닌 모든 팔로워 announcement에 대해 해당 스트림의 권한 있는 parts 목록으로 응답하고, 팔로워는 읽기 요청을 보내기 전에 이를 기다립니다. 8 미만에서는 announcement가 fire-and-forget 방식입니다. |
| 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 등)에 사용됩니다. 현재 값은 8 (DBMS_CLUSTER_PROCESSING_PROTOCOL_VERSION)이며, 버전 7은 비공개 저장소 기능(Iceberg compaction)을 위해 예약되어 있고, 8에서는 서버 간 클러스터 읽기 작업 payload(ReadTaskResponse 본문이며, 여기서는 구체적으로 명시하지 않음 — 아래 참조)에 선택적 read_source_index가 추가됩니다. 외부 클라이언트는 이를 디코드한 뒤 무시합니다. |
| 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의 나머지 부분과 동기화가 어긋납니다. |
| INTERNAL_QUERY_FLAG | 54486 | ClientInfo | ClientInfo의 끝부분에 is_internal UInt8이 추가됩니다. 서버 내부 쿼리(사용자가 실행한 쿼리가 아님)에는 1이며, 원격 쿼리로 전파되어 해당 system.query_log 행에 internal로 표시됩니다. 외부 클라이언트는 0을 전송합니다. 협상된 버전이 54486 이상이면 필수이며, 이를 생략하면 Query packet의 나머지 부분과 동기화가 어긋납니다. |
| INTERSERVER_CURRENT_ROLES | 54488 | ClientInfo | ClientInfo의 끝부분에 선택적 String 목록인 current_roles가 추가됩니다([UInt8 present], 그다음 1이면 [VarUInt count][String]*count). 또한 서버 간 쿼리에서는 auth_hash가 직렬화된 목록까지 포함하도록 확장됩니다. initiator의 활성화된 역할 이름을 전달하여 secondary 노드가 사용자의 기본 역할로 대체하지 않고 동일한 방식으로 행 정책의 범위를 지정합니다. 서버 간 쿼리에서만 채워지며 push_external_roles_in_interserver_queries = 1일 때만 채워집니다. 외부 클라이언트는 0(없음)을 전송합니다. 협상된 버전이 54488 이상이면 필수이며, 존재 여부 바이트를 생략하면 Query packet의 나머지 부분과 동기화가 어긋납니다. |
| CURRENT_AGGREGATION_VARIANT_SELECTION_METHOD | 54489 | 집계(2단계 버킷) | 단일 String 키의 집계 메서드는 enable_packed_string_keys_in_aggregation 설정을 따릅니다. 이 리비전보다 낮은 peer는 항상 packed 메서드를 사용하며 이 설정을 알지 못하므로, 이와 2단계 버킷을 교환하면 동일한 키가 서로 다른 버킷에 배치됩니다. initiator는 이러한 peer에 대해 안전하게 실패합니다. 즉, 해당 peer의 2단계 임계값을 0으로 설정하고 단일 단계 블록을 자체적으로 다시 버킷화합니다. 메서드는 단일 릴리스 내에서도 변경될 수 있으므로 버전이 아니라 peer의 리비전과 비교합니다. 네이티브 protocol의 wire 변경은 없습니다. |
| HTTP_HANDLER_IN_CLIENT_INFO | 54490 | ClientInfo | ClientInfo의 HTTP 분기에서 http_referer 바로 뒤에 http_handler_name 및 http_request_url(둘 다 String)이 추가됩니다. 이 필드는 일치하는 SQL로 정의된 HTTP handler의 이름과 요청 URL을 전달하므로, 분산 handler 쿼리의 원격 shard에서도 currentHandler/currentRequestURL 및 http_handler_name/http_request_url system.query_log 컬럼이 계속 채워집니다. query_interface = HTTP일 때만 기록됩니다(handler가 아닌 HTTP 쿼리에서는 빈 문자열). 협상된 버전이 54490 이상이면 필수이며, 이를 생략하면 Query packet의 나머지 부분과 동기화가 어긋납니다. |
| QUANTILE_DETERMINISTIC_SKIP_DEGREE | 54491 | Block (Column) | AggregateFunction(quantileDeterministic, ...)(및 quantiles/median 표기)의 상태 버전을 0에서 1로 올리고, 각 직렬화된 상태 끝에 UInt8 skip_degree를 추가합니다. 변경되는 것은 해당 집계 함수 하나의 상태 payload뿐이며, 컬럼 framing은 그대로이고 다른 모든 집계 함수는 각자 자체 버전을 유지합니다. 이 리비전보다 낮으면 writer가 버전 0 상태를 내보내므로 이전 peer에는 영향이 없습니다. |
| STRING_WITH_SIZE_STREAM_SERIALIZATION | 54492 | Block (Column) | String 컬럼은 값별 길이 접두사 레이아웃에서 누적 바이트 오프셋의 별도 스트림(Array가 사용하는 것과 동일한 오프셋 레이아웃)으로 전환되며, [UInt64 × num_rows offsets][data blob] 형식 그대로 전송됩니다. 협상된 리비전에 따라 적용되며, 54492 미만에서는 writer가 값별 길이 접두사를 사용합니다. wire에는 이를 위한 컬럼별 마커가 없으며, 양측은 리비전만으로 이를 결정합니다. String column layout을 참조하십시오. |
패킷 엔벌로프
wire로 전송되는 모든 메시지는 양방향에서 동일한 외부 구조를 가집니다:
[VarUInt: packet_type_code] always encoded as VarUInt
[message body] format depends on packet_type_code전체 패킷 유형 표는 패킷 유형 참고에 있습니다.
패킷 유형은 고정 폭 바이트가 아니라 VarUInt입니다. 값이 128 미만이면 VarUInt는 동일한 1바이트를 생성하지만, 앞으로 패킷 유형이 128 이상이 되더라도 호환성을 유지할 수 있도록 구현에서는 반드시 VarUInt 인코딩을 사용해야 합니다.
메시지 참고에서는 각 패킷의 본문만 문서화합니다. 즉, 패킷 유형 코드 뒤에 오는 바이트입니다. 필드 번호는 첫 번째 본문 필드를 1로 하여 시작합니다.
청크 프레이밍 (v54470+)
CHUNKED_PROTOCOL 기능이 협상되면(핸드셰이크 참조), wire 상의 모든 패킷은 청크 프레이밍으로 감싸집니다. 이 래핑은 방향별로 적용됩니다. 즉, 클라이언트→서버와 서버→클라이언트는 각각 별도로 협상되며, 서로 다른 모드(청크 방식 또는 프레이밍 없음)로 결정될 수 있습니다.
패킷별 wire 레이아웃:
<chunk>... one or more chunks; their payloads concatenated form the whole packet
[u32 LE = 0] zero-size terminator marking end of packet청크별 wire 레이아웃:
[u32 LE: chunk_size] chunk_size in [1, UINT32_MAX]
[chunk_size bytes] packet bytes (see note below)패킷 유형 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 | 프로토콜 오류 — 연결을 반드시 종료해야 합니다 |
클라이언트 측에서는 클라이언트의 SEND 선호도가 서버의 RECV 선호도와 협상되며, 그 반대도 동일합니다.
시점. 협상 문자열은 프레이밍되지 않은 wire로 전송됩니다. ClientHello → ServerHello (서버 선호도) → Addendum (클라이언트의 협상된 값) 순서입니다. 프레이밍 전환은 Addendum이 플러시된 이후 전송되는 모든 바이트에 적용됩니다. Addendum 자체, ClientHello, ServerHello는 항상 프레이밍되지 않습니다.
연결 수명 주기
어느 시점이든 연결은 정확히 네 가지 상태 중 하나입니다: HANDSHAKE, READY, READING_RESPONSE, 또는 종료 상태입니다. 프로토콜은 멀티플렉싱을 지원하지 않으므로, 이전 응답을 모두 수신하기 전에 클라이언트가 새 요청을 보내면 wire 상에서 바이트가 뒤섞여 스트림이 손상됩니다.
상태
stateDiagram-v2
[*] --> HANDSHAKE: TCP connect
HANDSHAKE --> READY: handshake ok
READY --> READING_RESPONSE: send Query
READING_RESPONSE --> READY: EndOfStream / Exception
READY --> READY: Ping / Pong
HANDSHAKE --> Terminated: handshake error
READING_RESPONSE --> Terminated: protocol violation / I/O error
READY --> Terminated: close
Terminated --> [*]
정상 경로는 HANDSHAKE → READY → READING_RESPONSE → READY로 곧바로 이어지며, Ping/Pong 자체 루프와 모든 실패 경로는 하나의 Terminated 싱크로 수렴합니다.
| 상태 | 설명 |
|---|---|
HANDSHAKE |
TCP 연결이 열린 직후의 초기 상태입니다. 핸드셰이크 메시지만 유효합니다. 성공하면 READY로 전이하고, 실패하면 종료됩니다. |
READY |
유휴 상태입니다. 클라이언트는 Ping, 쿼리를 보내거나 연결을 닫을 수 있습니다. 연결은 READY 상태로 무기한 유지될 수 있습니다(idle_connection_timeout의 적용을 받음, 연결 제한 참고). |
READING_RESPONSE |
클라이언트가 Query를 보내면 진입합니다. 클라이언트는 READY로 돌아가기 전에 서버의 응답 스트림을 끝까지 모두 읽어야 합니다. 여기서 클라이언트→서버로 허용되는 유일한 패킷은 Cancel입니다(이 페이지에서는 설명하지 않음). |
| Terminated | 더 이상 사용할 수 없습니다. 클라이언트는 새 TCP 연결을 열고 핸드셰이크를 다시 시작해야 합니다. |
핸드셰이크 단계
인증을 수행하고 프로토콜 버전을 협상합니다. 이 과정은 각 connection마다 다른 어떤 작업보다 먼저 정확히 한 번만 발생합니다.
TCP connection이 막 열렸고 아직 어떤 메시지도 오가지 않은 상태입니다. 흐름은 다음과 같습니다:
sequenceDiagram
autonumber
participant C as Client
participant S as Server
C->>S: ClientHello
alt success
S->>C: ServerHello
else error
S->>C: Exception — connection terminates
end
Note over C,S: negotiated_version = min(client, server)
opt negotiated_version ≥ 54458
C->>S: Addendum
end
-
클라이언트는 지원하는 최대 프로토콜 버전과 함께
ClientHello를 전송합니다. -
클라이언트는 응답을 읽고 패킷 유형에 따라 처리합니다:
패킷 유형 동작 Hello(0)ServerHello를 디코딩합니다.negotiated_version = min(client_ver, server_ver)를 계산합니다. 3단계로 진행합니다.Exception(2)Exception을 디코딩합니다. 이를 오류로 반환하고 연결을 종료합니다.그 밖의 경우 프로토콜 위반입니다. 연결을 종료합니다. -
negotiated_version ≥ 54458(ADDENDUM기능)인 경우, 클라이언트는Addendum을 전송합니다. 이 결정은 클라이언트가 선언한 버전이 아니라 협상된 버전을 기준으로 합니다.
성공하면 연결 상태가 READY로 전환되며, 오류가 발생하면 연결이 종료됩니다.
Ping 단계
TCP keepalive와는 별도의 애플리케이션 수준 liveness 확인입니다. Ping/Pong 왕복이 성공하면 TCP 연결이 양방향 모두에서 살아 있으며 서버가 응답 가능함을 확인할 수 있습니다. Ping은 무상태(stateless)이며 어떤 쿼리와도 연관되지 않으므로 여러 번 연속으로 보내는 Ping도 서로 독립적입니다.
READY부터 흐름은 다음과 같습니다:
sequenceDiagram
autonumber
participant C as Client
participant S as Server
C->>S: Ping (0x04)
alt responsive
S->>C: Pong (0x04)
else error
S->>C: Exception
end
-
클라이언트가
Ping을 보냅니다. -
클라이언트가 응답을 읽습니다:
패킷 유형 동작 Pong(4)liveness가 확인됩니다. READY로 돌아갑니다.Exception(2)Exception을 디코딩하여 오류로 반환합니다.그 외 모든 경우 프로토콜 위반입니다.
쿼리 단계
클라이언트가 SQL 문을 전송하면 서버는 결과 블록과 실행 텔레메트리를 스트리밍하여 반환합니다. 응답은 정확히 하나의 EndOfStream 또는 Exception으로 끝나는 패킷 시퀀스입니다.
READY부터 흐름은 다음과 같습니다:
sequenceDiagram
autonumber
participant C as Client
participant S as Server
C->>S: Query message
C->>S: External-table Data packets (0 or more)
C->>S: Empty Data marker — end-of-client-data (required)
S->>C: Data — header block (N cols, 0 rows)
loop until EndOfStream or Exception
S->>C: Progress / Log (interleaved)
S->>C: Data — result block (N cols, M rows)
S->>C: Totals / Extremes (aggregation queries)
S->>C: ProfileInfo / ProfileEvents (profiling)
end
S->>C: Data — empty block (boundary marker)
S->>C: Progress — final updates
S->>C: EndOfStream — authoritative end of query
어느 단계에서든 오류가 발생하면 서버는 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 오류가 발생하면 연결이 종료됩니다.
INSERT 단계
INSERT 단계는 쿼리 단계에 2번의 추가 통신이 더해진 형태입니다. 클라이언트가 INSERT 구문을 전송하면 서버는 대상 테이블을 설명하는 스키마 블록으로 응답합니다. 그런 다음 클라이언트는 행이 포함된 데이터 패킷을 스트리밍하고, 이어서 비어 있는 Data 마커를 보냅니다. 마지막으로 서버는 EndOfStream 또는 Exception으로 마무리합니다.
READY 상태에서 시작하면 SQL은 INSERT INTO <table> [(<cols>)] VALUES 형태의 INSERT이며, 행 데이터는 Data 패킷을 통해 전달되므로 인라인 VALUES (...) 리터럴은 포함되지 않습니다. 흐름은 다음과 같습니다:
sequenceDiagram
autonumber
participant C as Client
participant S as Server
C->>S: Query packet (INSERT body)
C->>S: External-table Data packets (0 or more)
Note over C,S: No empty Data marker here —<br/>it would end the row stream before it starts
opt metadata before schema
S->>C: TableColumns / Progress / ...
end
S->>C: Data packet — schema block (columns, 0 rows)
Note over C,S: Schema block is the contract:<br/>rows sent next must match these column shapes
loop one or more blocks
C->>S: Data packet (rows N)
end
C->>S: Data packet — empty block (0 rows), end-of-input terminator
loop until EndOfStream or Exception
S->>C: Progress / ProfileInfo / Log / ProfileEvents
end
opt async_insert = 1 and negotiated_version ≥ 54484
S->>C: trailing Progress, then insert ProfileEvents
end
S->>C: EndOfStream
- 클라이언트는
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(실패)에 도달할 때까지 응답 스트림을 계속 읽어들입니다.
비동기 INSERT(v54484+). 쿼리에 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이 종료됩니다.
메시지 참고
필드는 wire 순서대로 나열됩니다. 유형 컬럼은 다음 의미로 사용합니다.
VarUInt— 가변 길이 부호 없는 정수(VarUInt 참조)String— 앞에 VarUInt 길이 값이 붙는 바이트열(String 참조)UInt8,Int32등 — 고정 길이 리틀 엔디언 정수Bool—0x00또는0x01인 1바이트 값
Role 컬럼은 각 필드를 누가 사용하는지 나타냅니다.
- 클라이언트 — 외부 클라이언트가 설정합니다.
- 서버 간 — 서버 간 통신에서만 의미가 있으며, 외부 클라이언트는 기본값을 씁니다.
- universal — 둘 다에서 사용합니다.
이 표는 패킷 유형 코드 다음에 오는 각 패킷의 본문만 설명합니다.
ClientHello (패킷 유형 0)
클라이언트 → 서버. TCP connection이 열린 후 전송되는 첫 번째 메시지입니다.
| # | 필드 | 유형 | 역할 | 설명 |
|---|---|---|---|---|
| 1 | client_name | String | 공통 | 클라이언트 식별자(예: "clickhouse-client") |
| 2 | version_major | VarUInt | 공통 | 클라이언트 메이저 버전 |
| 3 | version_minor | VarUInt | 공통 | 클라이언트 마이너 버전 |
| 4 | protocol_version | VarUInt | 공통 | 클라이언트가 지원하는 최대 프로토콜 버전 |
| 5 | database | String | 공통 | 기본 데이터베이스 이름 |
| 6 | user | String | 공통 | 인증에 사용할 username입니다. 비어 있으면 서버의 기본 세션 사용자(default_session_user 서버 설정이며, 26.8 이전 서버에서는 거부됨)를 의미합니다. 인증을 참조하십시오. |
| 7 | password | String | 공통 | 비밀번호(plaintext) |
ServerHello (패킷 유형 0)
Server → Client. 인증이 성공했을 때 ClientHello에 대한 응답입니다.
| # | 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) | 서버의 병렬 레플리카 coordination 프로토콜 버전입니다. Wire position: protocol_version 바로 뒤, timezone 앞입니다. 현재 값: 8. |
| 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 프로토콜 버전입니다. 현재 값은 8입니다. 이 값은 서버 간 cluster read-task payload(별도로 명시되지 않은 ReadTaskResponse 본문)의 추가 필드를 제어하며, 버전 7은 private-repository 기능(Iceberg compaction)을 위해 예약되어 있고 8에서는 선택적 read_source_index가 추가됩니다. 외부 클라이언트는 cluster read에 참여하지 않으므로 이 필드를 디코딩만 하고 무시합니다. |
Rule — password_complexity_rules의 요소:
| # | Field | Type | Description |
|---|---|---|---|
| 1 | pattern | String | 규정을 준수하는 비밀번호가 일치해야 하는 정규 표현식 패턴입니다. |
| 2 | message | String | 비밀번호가 이 규칙을 충족하지 못했을 때 표시되는 사람이 읽기 쉬운 설명입니다. |
이 목록은 서버 운영자의 비밀번호 정책 구성을 반영하며, 순수한 권고 정보입니다. 서버는 핸드셰이크 중에 이 규칙을 강제하지 않습니다. 비밀번호 변경 또는 설정 기능을 제공하는 클라이언트는 규정을 준수하지 않는 비밀번호를 서버에 보내기 전에 이 규칙을 사용해 오류를 표시할 수 있습니다.
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 프로토콜 버전입니다. 분산 쿼리에 참여하지 않는 외부 클라이언트도 서버의 호환성 검사가 통과하도록 유효한 버전(현재 8)을 보내야 합니다. |
청크 프레이밍 전환은 이 Addendum이 플러시된 후에 적용되며, Addendum 자체는 프레이밍되지 않습니다.
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에 포함됨)
클라이언트 → 서버, Query 본문(field 2)에 포함됩니다. 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 | 공통 | 항상 | 원본 클라이언트 소켓 주소입니다. 서버는 이 값을 절대 확인하지 않습니다(호스트명 또는 서비스 이름 조회 없음). SECONDARY_QUERY의 경우(이 값이 유지되어 사용되며, 예를 들어 system.query_log 및 서버 간 인증에 사용됨) 허용되는 문법은 IPv4 a.b.c.d:port 또는 대괄호로 감싼 IPv6 [addr]:port이며, 이때 host는 IP 리터럴이고 port는 0..65535 범위의 10진수여야 합니다. 그 외 형식(예: localhost:9000, host:http, :9000, 또는 /tmp/ch.sock 같은 UNIX socket 경로)은 INCORRECT_DATA와 함께 거부됩니다. INITIAL_QUERY의 경우 서버가 이 필드를 실제 피어 주소로 덮어쓰므로 어떤 값이든 허용됩니다(단순한 ip:port가 아닌 값은 기본값 0.0.0.0:0으로 대체됩니다). 외부 클라이언트는 자체 ip: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뿐 아니라 모든 인터페이스에서 전송됨). |
| 25 | is_internal | UInt8 | 클라이언트 | INTERNAL_QUERY_FLAG (v54486) | 마지막 필드입니다. 서버 내부 쿼리(사용자가 실행한 쿼리가 아님)인 경우 1이며, 원격 쿼리로 전파되어 system.query_log에서 내부 쿼리로 표시됩니다. query_kind(필드 1)와는 별개입니다. 외부 클라이언트는 0을 보냅니다. 협상된 버전이 54486 이상이면 포함됩니다(TCP뿐 아니라 모든 인터페이스에서 전송됨). |
| 26 | current_roles | UInt8 [+ String 목록] | 서버 간 | INTERSERVER_CURRENT_ROLES (v54488) | 마지막 필드입니다. [UInt8 present] 뒤에 present = 1인 경우 initiator의 활성화된(enabled) 역할 이름 목록 [VarUInt count][String]*count가 옵니다. secondary 노드가 사용자의 기본 역할 대신 initiator의 역할을 기준으로 행 정책을 적용할 수 있습니다. 서버 간 쿼리에만, 그리고 push_external_roles_in_interserver_queries = 1인 경우에만 채워집니다. 그 외의 경우와 외부 클라이언트에서는 present = 0입니다. 협상된 버전이 54488 이상이면 존재 여부 바이트가 필요합니다(TCP뿐 아니라 모든 인터페이스에서 전송됨). 서버 간 쿼리에서는 직렬화된 목록도 auth_hash에 포함됩니다(서버 간 인증 참조). |
OpenTelemetry 인코딩(필드 16):
[UInt8: has_trace] 0 = no trace data follows, 1 = trace data follows
If has_trace == 1:
[16 bytes: trace_id] byte-swapped per-8-bytes
[8 bytes: span_id] byte-swapped
[String: trace_state] W3C trace state
[UInt8: trace_flags] W3C trace flags서버 간 인증
Query 필드 4(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 + current_roles)를 씁니다. 이는 32바이트 다이제스트입니다. (nonce는 10진수 문자열 형식이며 v54462 이상이 협상된 경우에만 포함됩니다.external_roles는INTERSERVER_EXTERNALLY_GRANTED_ROLES(v54472)가 협상된 경우에만 추가됩니다.current_roles는 존재 여부 바이트 없이[VarUInt count][String]*count형식으로 직렬화된 역할 이름 목록이며,INTERSERVER_CURRENT_ROLES(v54488)가 협상되고 목록이 존재하는 경우에만 추가됩니다.)InitialQuery이거나 클러스터 시크릿이 구성되지 않은 경우에는 대신 빈 문자열을 씁니다. - 검증합니다. 서버는 필드 4를 32바이트 cap으로 읽고 자체 클러스터 시크릿 사본을 사용해 같은 연결 문자열을 다시 계산합니다. 다이제스트가 다르면 연결이 거부됩니다.
외부(비 서버 간) 클라이언트는 이 모드로 절대 진입하지 않으며 항상 빈 auth_hash를 보냅니다.
설정
Query 본문의 설정 목록(Query 패킷, field 3)에 인라인 인코딩됩니다. 이 목록은 협상된 버전과 관계없이 항상 존재하며, key가 빈 Setting으로 종료됩니다. 즉, 뒤에 flags나 value 없이 단일 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 | 클라이언트 | 문자열 형식의 매개변수 값입니다. 따옴표 처리에 대해서는 아래 참고를 확인하십시오. |
데이터 (패킷 유형 1 server→client, 패킷 유형 2 client→server)
양방향 모두에 사용됩니다. 결과 블록, INSERT 데이터, 외부 테이블, 그리고 데이터 종료 마커를 전달합니다.
wire 형식은 대칭적이며, 양방향 모두 Block 앞에 table_name 접두사가 포함됩니다. 다른 점은 패킷 유형 바이트뿐입니다.
[VarUInt: packet_type] 1 (server→client) or 2 (client→server)
[String: table_name] External table name; empty in most cases
[Block] See the Native Format spec for the Block layout| 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)
서버 → 클라이언트. 쿼리 실행 중 주기적으로 전송됩니다. 모든 필드는 VarUInt이며, 각 패킷에는 이전 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)
Server → Client. 각 쿼리마다 한 번씩, 실행이 끝날 무렵 전송됩니다.
| # | 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이 옵니다. 다른 점은 패킷 유형 바이트뿐입니다.
[VarUInt: 7] packet type
[String: table_name] always empty
[Block] see the Native Format spec극값 (패킷 유형 8)
서버 → 클라이언트. extremes 설정이 활성화되면 전송됩니다. wire 형식은 데이터와 동일합니다. 이 블록에는 정확히 2개의 행이 있으며, 0번 행에는 각 컬럼의 최솟값이, 1번 행에는 최댓값이 들어 있습니다.
[VarUInt: 8] packet type
[String: table_name] always empty
[Block] num_rows = 2Log (패킷 유형 10)
서버 → 클라이언트. 쿼리에 활성화된 로그 큐가 있을 때 전송됩니다(send_logs_level 설정, 로그 스트리밍 참조).
데이터와 동일한 엔벌로프 및 본문 포맷을 사용합니다. 블록은 고정된 num_columns = 8과 미리 정의된 스키마를 가집니다. 각 로그 라인은 8개 컬럼 전체에 걸친 하나의 행이며, 하나의 Log packet에 여러 행이 담길 수 있습니다.
[VarUInt: 10] packet type
[String: table_name] always empty
[Block] num_columns = 8, num_rows = number of log lines다음은 정확히 이 순서의 8개 컬럼입니다:
| # | 이름 | 유형 | 설명 |
|---|---|---|---|
| 1 | event_time | DateTime | 이벤트 타임스탬프(epoch 이후 초) |
| 2 | event_time_microseconds | UInt32 | 마이크로초 구성 요소 |
| 3 | host_name | String | 로그를 출력하는 서버 호스트명 |
| 4 | query_id | String | 해당 로그가 속한 쿼리 ID |
| 5 | thread_id | UInt64 | OS 스레드 ID |
| 6 | priority | Int8 | 로그 레벨 (Poco priority: 1 = Fatal, … 8 = Trace, 9 = Test) |
| 7 | source | String | 로거 이름 |
| 8 | text | String | 로그 메시지 텍스트 |
ProfileEvents (패킷 유형 14)
Server → Client. 쿼리별 성능 카운터를 전달합니다.
데이터와 동일한 엔벌로프 및 본문 포맷입니다. 블록의 num_columns는 6으로 고정되어 있으며, 스키마는 미리 정의되어 있습니다. 각 이벤트는 하나의 행입니다.
[VarUInt: 14] packet type
[String: table_name] always empty
[Block] num_columns = 6, num_rows = number of events6개의 컬럼:
| # | 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 | 카운터 값 또는 게이지 값 |
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 ''". 자유 형식 텍스트이므로 문자열로 해석하십시오. |
TimezoneUpdate (패킷 유형 17)
Server → Client, 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 자체에 대한 서명은 아닙니다 |
이 흐름은 비밀번호 인증을 대체하여 수행되며, 챌린지-응답 교환은 ServerHello 이전에 일어납니다. 즉, 서버는 인증이 성공할 때까지 Hello 응답을 보류합니다.
-
클라이언트는 SSH 마커 접두사와 빈 password를 포함한 ClientHello를 보냅니다.
-
클라이언트는
SSHChallengeRequest(packet 11)를 보냅니다. 서버는 아직 ServerHello를 보내지 않은 상태이며, 먼저 인증을 처리한 뒤 이 packet를 기다리면서 여기서 대기합니다. -
서버는 임의의 바이트를 담은
SSHChallenge(packet 18)로 응답합니다. -
클라이언트는 서명할 문자열을 구성하고 원시 challenge가 아니라 그 문자열 자체에 서명한 다음, 서명을 담은
SSHChallengeResponse(packet 12)를 보냅니다. 서명되는 메시지는 아래 네 부분을 정확히 이 순서대로, 구분자 없이 바이트 단위로 이어 붙인 값입니다.to_sign = decimal(protocol_version) + default_database + user + challengePart 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 바이트에만 서명하는 클라이언트는 인증에 실패합니다.
sequenceDiagram
autonumber
participant C as Client
participant S as Server
C->>S: ClientHello (SSH marker user, empty password)
C->>S: SSHChallengeRequest (11)
Note over C,S: Server has NOT sent ServerHello yet —<br/>it authenticates first and blocks here
S->>C: SSHChallenge (18) — random bytes to sign
C->>S: SSHChallengeResponse (12) — signature over version+db+user+challenge
alt signature verifies
S->>C: ServerHello — handshake continues normally
else verification fails
S->>C: Exception — connection terminates
end
SSH 인증을 사용하지 않는 외부 클라이언트는 패킷 11, 12, 18을 전혀 보지 않습니다 — 사용자가 username 접두사를 통해 명시적으로 선택하지 않는 한, 이러한 패킷은 wire에 나타나지 않습니다.
MergeTreeAllRangesAnnouncementResponse (패킷 유형 14)
클라이언트 → 서버, 서버 간 전용입니다. parallel_replicas_protocol_version ≥ 8일 때만 적용됩니다(VERSIONED_PARALLEL_REPLICAS_PROTOCOL 참조). 외부 클라이언트는 이 패킷을 전송하지 않습니다.
협상된 parallel-replicas 버전이 ≥ 8이면, 팔로워의 MergeTreeAllRangesAnnouncement(패킷 유형 15, 서버→클라이언트 방향)에 대한 이니시에이터의 요청/응답 사이클이 다음과 같이 변경됩니다:
- 팔로워가 읽기 파이프라인을 열고
MergeTreeAllRangesAnnouncement를 이니시에이터에 전송합니다. - announcement의
mode가 non-Default인 경우에만 (WithOrder = 1또는ReverseOrder = 2, 둘 다 순서 보장 병렬 읽기에 사용됨) 이니시에이터가MergeTreeAllRangesAnnouncementResponse로 응답합니다.mode = Default = 0인 경우 이니시에이터는 응답하지 않으며 팔로워도 기다리지 않습니다 —Default모드에서는 각MergeTreeReadTaskRequest와 함께 범위를 할당하므로, 미리 파트 목록이 필요하지 않습니다. - 팔로워는 첫 번째
MergeTreeReadTaskRequest(서버 패킷16— 팔로워→이니시에이터로 전송되며, 이니시에이터는MergeTreeReadTaskResponse클라이언트 패킷10으로 응답함)를 보내기 전에, 응답이 예상되는 경우 해당 응답을 기다립니다. 그리고 반환된 파트 목록을 사용해 소스 구성을 자신의#split_i스트림이 소유한 파트로만 정확히 필터링합니다.
버전 8 미만에서는 mode와 관계없이 announcement가 fire-and-forget 방식으로 처리되며, 팔로워는 로컬에서 알고 있는 모든 파트에 대해 소스를 구성합니다(기존 방식).
본문
| # | 필드 | 유형 | 설명 |
|---|---|---|---|
| 1 | version | Int64 (리틀 엔디언) | 발신자의 병렬 레플리카 프로토콜 버전입니다. 수신자의 TCP revision이 DBMS_MIN_REVISION_WITH_VERSIONED_PARALLEL_REPLICAS_PROTOCOL (54471) 이상이면 DBMS_PARALLEL_REPLICAS_PROTOCOL_VERSION(현재 8)과 같고, 그렇지 않으면 DBMS_MIN_SUPPORTED_PARALLEL_REPLICAS_PROTOCOL_VERSION (3)을 사용합니다. 수신기는 DBMS_MIN_SUPPORTED_PARALLEL_REPLICAS_PROTOCOL_VERSION보다 작은 값은 모두 거부합니다. |
| 2 | parts | RangesInDataPartsDescription | coordinator가 해당 announcement의 스트림에 대해 등록한 기준 파트 집합입니다. 빈 목록은 해당 스트림이 coordinator에 존재하지 않음을 의미합니다(예: 팔로워가 initiator가 생성한 split 수보다 더 많이 announcement한 경우). 이 경우 해당 스트림에 대한 팔로워의 풀은 즉시 완료 상태로 표시됩니다. |
| 3 | stream_id | String | 이 응답이 대한 announcement의 stream_id를 그대로 반환합니다(split 토폴로지를 사용하는 경우 테이블 이름에 #split_i suffix가 붙음). |
RangesInDataPartsDescription 본문
| # | 필드 | 유형 | 설명 |
|---|---|---|---|
| 1 | count | VarUInt | 뒤에 오는 파트 디스크립터의 개수입니다. decoder는 100'000'000'000을 초과하는 값을 잘못된 형식으로 판단해 거부합니다. |
| 2 | parts | RangesInDataPartDescription count회 반복 |
coordinator의 등록 순서에 따른 디스크립터입니다. |
RangesInDataPartDescription 본문
| # | Field | 유형 | Gate | Description |
|---|---|---|---|---|
| 1 | info | MergeTreePartInfo | 공통 | 파트 식별 정보(파티션, 블록 범위, 수준, mutation)입니다. |
| 2 | ranges | MarkRanges | 공통 | 이 스트림이 제공할 수 있는 info 내의 마크 범위입니다. 빈 목록은 파트가 등록되어 있지만 현재 할당된 작업이 없음을 의미합니다. |
| 3 | rows | VarUInt | 공통 | ranges가 포함하는 총 행 수입니다. |
| 4 | projection_name | String | DBMS_PARALLEL_REPLICAS_MIN_VERSION_WITH_PROJECTION (PR v5) |
프라이머리 파트의 행에는 비어 있으며, 그 외에는 projection의 이름입니다. |
| 5 | min_marks_per_task | VarUInt | DBMS_PARALLEL_REPLICAS_MIN_VERSION_WITH_MIN_MARKS_PER_TASK (PR v6) |
이 파트에 대해 팔로워의 풀이 단일 읽기 작업으로 묶어 처리해야 하는 마크 수의 하한입니다. |
MergeTreePartInfo 본문
| # | 필드 | 유형 | 설명 |
|---|---|---|---|
| 1 | version | Int64 (리틀 엔디언) | 항상 DBMS_MERGE_TREE_PART_INFO_VERSION(1)입니다. 디코더는 다른 값은 모두 거부합니다. |
| 2 | partition_id | String | 파티션 식별자입니다(예: 파티션되지 않은 테이블에서는 "all"이고, 또는 파티션 키 튜플 표현식의 문자열로 변환된 값입니다). |
| 3 | min_block | Int64 (리틀 엔디언) | 파트의 블록 범위에서 첫 번째 블록 번호입니다. |
| 4 | max_block | Int64 (리틀 엔디언) | 파트의 블록 범위에서 마지막 블록 번호입니다(포함). |
| 5 | level | UInt32 (리틀 엔디언) | 머지 수준입니다. |
| 6 | mutation | Int64 (리틀 엔디언) | 이 파트를 생성한 mutation 버전입니다(mutation이 적용되지 않은 경우 0). |
| 7 | use_legacy_max_level | Bool (텍스트) | 단일 ASCII 바이트('1' 또는 '0')로 인코딩되며, 파트 이름 포맷의 과거 호환성을 위한 플래그입니다. |
MarkRanges 본문
| # | 필드 | 유형 | 설명 |
|---|---|---|---|
| 1 | size | UInt64 (리틀 엔디언) | 뒤에 오는 mark-range 쌍의 개수입니다. 참고: 리틀 엔디언 고정 길이 형식이며, VarUInt가 아닙니다. |
| 2 | ranges | 각각 리틀 엔디언인 (UInt64 begin, UInt64 end)를 size회 반복 |
반열린 [begin, end) mark 범위입니다. |
패킷 유형 참고
클라이언트 → 서버
| 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 | 지정되지 않음 | 연결 keepalive |
| 7 | Scalar | 지정되지 않음 | 스칼라 데이터 블록 |
| 8 | IgnoredPartUUIDs | 지정되지 않음 | 쿼리에서 제외할 파트 |
| 9 | ReadTaskResponse | 지정되지 않음 | S3 cluster 읽기 응답 |
| 10 | MergeTreeReadTaskResponse | 지정되지 않음 | 병렬 읽기 작업 응답 |
| 11 | SSHChallengeRequest | SSH 인증 | SSH 인증 챌린지 요청 |
| 12 | SSHChallengeResponse | SSH 인증 | SSH 인증 챌린지 응답 |
| 13 | QueryPlan | 지정되지 않음 | 쿼리 계획 |
| 14 | MergeTreeAllRangesAnnouncementResponse | MergeTreeAllRangesAnnouncementResponse | 팔로워의 MergeTreeAllRangesAnnouncement에 대한 이니시에이터의 응답(parallel_replicas_protocol_version ≥ 8에서만 사용 — VERSIONED_PARALLEL_REPLICAS_PROTOCOL 참고). 서버 간 전용이며 외부 클라이언트는 전송하지 않습니다. |
서버 → 클라이언트
| 코드 | 이름 | 본문 포맷 | 설명 |
|---|---|---|---|
| 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를 제어하는 설정입니다.
아래 기본값은 최신 서버 release를 반영한 것이며, 버전과 배포에 따라 다를 수 있습니다.
전송 계층 설정
소켓 옵션
| 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 | 초 | 서버 | 서버의 유휴 연결 및 종료 확인 루프의 세분화 수준입니다. |
타임아웃은 다음과 같이 중첩됩니다:
tcp_keep_alive_timeout (290s)
< receive_timeout (300s)
< idle_connection_timeout (3600s)
< tcp_close_connection_after_queries_seconds (0 = unlimited by default)OS keepalive가 먼저 동작하며, 커널 수준에서 응답이 없는 피어를 조용히 감지할 수 있습니다. 애플리케이션의 receive timeout은 그다음 방어선입니다. idle timeout은 오랫동안 사용되지 않은 연결을 정리하는 최후의 수단입니다.
연결 제한
| 설정 | 기본값 | 단위 | 측 | 설명 |
|---|---|---|---|---|
max_connections |
4096 | 개수 | 서버 | 최대 동시 TCP 연결 수입니다. |
idle_connection_timeout |
3600 | 초 | 서버 | 유휴 연결을 열린 상태로 유지할 수 있는 최대 시간입니다. |
tcp_close_connection_after_queries_num |
0 (무제한) | 개수 | 서버 | 강제로 종료되기 전 연결당 허용되는 최대 쿼리 수입니다. |
tcp_close_connection_after_queries_seconds |
0 (무제한) | 초 | 서버 | 활동 여부와 관계없이 연결이 유지될 수 있는 최대 총 시간입니다. |
정기적으로 쿼리를 실행하는 연결은 무기한 유지될 수 있습니다. 1시간 후 정리되는 것은 유휴 연결뿐이며, 기본 최대 연결 유지 시간은 없습니다.
애플리케이션 계층 설정
이 설정은 Query 패킷의 설정 목록을 통해 쿼리별로 전달됩니다. 이 설정은 서버가 wire로 전송하는 내용이나 해당 데이터의 프레이밍 방식을 변경합니다.
압축
| 설정 | 기본값 | 단위 | 설명 |
|---|---|---|---|
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", "test". |
send_logs_source_regexp |
"" |
string | 로거 소스에 적용되는 Regex 필터입니다. 빈 값이면 모든 소스가 통과합니다. |
send_logs_level을 "none" 이외의 값으로 설정하면 서버가 쿼리 실행 중 Log 패킷을 전송합니다.
Progress 보고
| Setting | Default | Unit | Description |
|---|---|---|---|
interactive_delay |
100000 | microseconds | 연속된 Progress 패킷 사이의 목표 최소 인터벌입니다. |
이는 엄격한 최대값이 아니라 목표 최소값입니다. 쿼리가 충분히 빠르게 처리되지 않으면 서버가 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 레이아웃 및 버전 지정 타입을 참조하십시오.
용어집
Cancel — 실행 중인 쿼리를 중단하는 클라이언트 시작 패킷(유형 3)입니다. 이 페이지에서는 자세히 설명하지 않습니다.
클라이언트 데이터 종료 마커 — 클라이언트가 입력 스트림을 닫기 위해 보내는 빈 Data 패킷(0 컬럼, 0 행)입니다. 전송 위치는 쿼리 종류에 따라 다릅니다.
- 일반 쿼리(
SELECT등): "더 이상 외부 데이터가 없음"을 알리기 위해 Query 패킷과 외부 테이블 Data 패킷 뒤에 전송됩니다. 그러면 서버가 실행을 시작합니다. INSERT: 클라이언트는 스키마 이전 마커를 보내지 않습니다. 먼저 서버가 스키마 블록을 보내고, 그다음 클라이언트가 행 Data 블록을 스트리밍한 뒤, 마지막에 빈 Data 패킷을 보내 행 스트림을 종료합니다. 스키마 블록보다 먼저 빈 마커를 보내면 즉시 행 종료로 해석되어 데이터가 유실됩니다.
기능 — 특정 프로토콜 버전에서 도입된 wire 포맷 변경 사항입니다. 협상된 버전이 해당 기능의 버전 이상이면 활성화됩니다. 버전 관리와 기능 게이트를 참조하십시오.
서버 간 — 서버 간 분산 쿼리에서만 의미를 갖는 필드의 역할 레이블입니다. 외부 클라이언트는 기본값(보통 빈 문자열, 0 또는 false)을 씁니다.
협상된 버전 — 핸드셰이크 중 계산되는 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 마커로 끝납니다.