INSERTのペイロード、実行テレメトリー、エラー信号がやり取りされます。コマンドラインクライアントや、C++および大半のサードパーティ製ネイティブドライバーを支えるプロトコルでもあります。
このページでは、プロトコルそのもの、つまりパケットのフレーミング、接続の状態遷移、バージョンネゴシエーション、そしてBlock以外のすべてのメッセージのボディを扱います。Data系パケット内のバイト列 (Block本体、そのカラム、型ごとのエンコーディング) については別のトピックであり、Native Format仕様で説明されています。
対応する仕様このページは対になる仕様の一方であり、対応するNative Format仕様とあわせて公開されています。2つの仕様は役割が明確に分かれています。このページが扱うのはパケット層とトランスポート層であり、Native Format仕様が扱うのは
Data系パケット内のバイト列です。BlockInfo内を除いてフィールドタグは存在しないため、1バイトでもずれると、それ以降のすべてが同期しなくなります。また、これはステートフルなプロトコルであり、各TCP接続は一度に1つのクエリだけを処理します。多重化はありません。固定幅整数はリトルエンディアンです。
概要
wire 上のすべてのメッセージは
VarUInt のパケットタイプコードで始まり、その後に、そのコードとネゴシエートされたプロトコルバージョンに応じて構造が決まるボディが続きます。
接続は 3 つのフェーズで進行します。最初に一回限りのハンドシェイクがあり、その後に任意回数の Ping または Query のやり取りが続き、最後に切断されます:
ネイティブTCPプロトコルでは、SQL 内の FORMAT 句に関係なく、表形式のデータは常に Nativeフォーマットで運ばれます。RowBinary、CSV、JSON などへの再フォーマットはクライアント側の役割であり、Nativeブロックをデコードした後に行われます。 (HTTPインターフェイスはこれとは別のコードパスで、FORMAT 句を実際に尊重します。HTTP はここでの対象外です。)
セキュリティ
トランスポートセキュリティ (TLS)
認証
ClientHello メッセージで行われます。user フィールドと password フィールドは平文の文字列として送信されるため、転送中の認証情報を保護するのはトランスポート層の暗号化 (TLS) です。
SSH チャレンジレスポンス認証は、プロトコルバージョン 54466 以降で利用できます。詳しくは、SSH チャレンジレスポンス認証 を参照してください。
サーバー間シークレット
auth_hash を Query のフィールド 4 に含み、受信側のサーバーはこれを再計算して照合します。これは INTERSERVER_SECRET 機能 (v54441) によって有効化されます。外部クライアントは、ここには常に空文字列を送信します。サーバー間認証を参照してください。
バージョン管理とフィーチャーゲート
バージョン交渉
機能ゲート
機能一覧
パケットのエンベロープ
VarUInt です。値が128未満の場合、VarUInt でも同じ1バイトになりますが、将来パケット型が128以上になっても互換性を保てるよう、実装では VarUInt エンコーディングを使用する必要があります。
message referenceでは、各パケットのボディ、つまりパケット型コードの後に続くバイト列のみを記載しています。フィールド番号は、最初のボディフィールドを1として始まります。
チャンク化フレーミング (v54470+)
CHUNKED_PROTOCOL 機能の使用がネゴシエートされると (ハンドシェイクを参照) 、wire 上のすべてのパケットはチャンク化フレーミングで包まれます。この包み方は方向ごとです。つまり、client→server と server→client は個別にネゴシエートされるため、異なるモード (チャンク化または非フレーム) になる場合があります。
パケットごとの wire レイアウト:
VarUInt は chunk 化されたストリームの内側にあります。つまり、フレーミングの前に別個のバイトとして送られるのではなく、パケットペイロードの先頭バイト (最初の chunk の先頭バイト) です。各パケットの chunk ペイロードは、packet envelope の完全な [VarUInt packet_type_code][message body] 全体です。パケット型を chunk 化されたストリームの外側に置くクライアントは、その型バイトを u32 の chunk サイズの先頭バイトとして相手に読ませてしまい、接続の同期が崩れます。
書き込み側のバッファがパケット途中でいっぱいになると、1 つのパケットが複数の chunk に分割されることがあります。分割位置はどこでもあり得るため、パケット型の VarUInt の途中で分割されることもあります。読み取り側は chunk ペイロードを連結し、末尾の 4 バイトのゼロを透過的なパケット境界として扱います。つまり、それ自体は消費しますが、パケットボディを読む側には見せません。
ボディを持たないパケットも引き続きラップされます。Ping や Pong のような 1 バイトのパケットは、chunk 化がネゴシエートされると [u32 size = 1][0x04][u32 0] になります。このページの他の箇所で「wire 上では単一バイト」と説明しているものは、いずれも chunk 化前の形式を指します。
ネゴシエーション。 ServerHello と Addendum はそれぞれ、方向ごとに 1 つずつ、合計 2 つの String フィールドを持ち、その値は {"chunked", "notchunked", "chunked_optional", "notchunked_optional"} から取られます。
chunked/notchunkedは厳格です。その側はそのモードを厳密に要求します。_optionalバリアントは柔軟で、相手側が選んだどちらのモードも受け入れます。
クライアント側では、クライアントの SEND 設定がサーバーの RECV 設定とネゴシエートされ、逆方向も同様です。
タイミング。 ネゴシエーション文字列はフレーム化されていない wire 上を流れます: ClientHello → ServerHello (server prefs) → Addendum (client のネゴシエート済みの値) 。フレーミングへの切り替えは、Addendum が flush された後に送信されるすべてのバイトに適用されます。Addendum 自体、ClientHello、ServerHello は常にフレーム化されません。
接続ライフサイクル
HANDSHAKE、READY、READING_RESPONSE、または終了済みの4つの状態のいずれか1つにあります。プロトコルは多重化を行わないため、前のレスポンスを読み切る前にクライアントが新しいリクエストを送信すると、転送中のバイト列が交錯してストリームが破損します。
状態
HANDSHAKE → READY → READING_RESPONSE → READY — Ping/Pong の自己ループがあり、すべての失敗エッジは単一の Terminated シンクに集約されます。
ハンドシェイクフェーズ
-
クライアントは、サポートする最大のプロトコルバージョンを指定して
ClientHelloを送信します。 -
クライアントは応答を読み取り、パケットタイプに応じて次のように処理を振り分けます。
-
negotiated_version ≥ 54458(ADDENDUM機能) の場合、クライアントはAddendumを送信します。この判断は、クライアントが宣言したバージョンではなく、ネゴシエートされたバージョンに基づきます。
READY に移行し、何らかのエラーが発生した場合は終了します。
Ping フェーズ
READY からのフローは次のとおりです。
-
クライアントは
Pingを送信します。 -
クライアントはレスポンスを読み取ります。
クエリフェーズ
EndOfStream または Exception で終わります。
READY から始まるフローは次のとおりです。
いずれかの時点でエラーが発生した場合、サーバーは EndOfStream の代わりに Exception を送信し、それによってクエリは終了します。
-
クライアントは一意の
query_id(通常は UUID) を付けてQueryを送信します。 -
クライアントは任意の 外部テーブル を送信し、その後に空の Data マーカーを送信します。空の Data パケットは
table_name = "",num_columns = 0,num_rows = 0です。サーバーは、このマーカーを受信するまでクエリの実行を開始しません。 -
クライアントは
READING_RESPONSEに移行し、書き込みバッファを flush します。 -
クライアントは応答パケットをループで読み取り、type ごとに処理を振り分けます。
EndOfStream または処理済みの Exception を受け取ると、connection は READY に戻ります。プロトコル違反または I/O error が発生した場合は終了します。
num_rows == 0 のケースは、新しい実装でよくつまずくポイントです。行数 0 の block は境界マーカーまたはスキーマヘッダーであり、ストリーム終端を示すシグナルではありません。応答を終了させるのは EndOfStream または Exception だけです。INSERT フェーズ
INSERT ステートメントを送信すると、サーバーはターゲットテーブルを示す スキーマブロック を返します。続いてクライアントは行を含む Data packets をストリーミングし、その後に空の Data マーカーを送信します。最後に、サーバーは EndOfStream または Exception を返して終了します。
READY から開始する場合、SQL は INSERT INTO <table> [(<cols>)] VALUES 形式の INSERT です。行データは Data packets を通じて送られるため、インラインの VALUES (...) リテラルは含みません。フローは次のとおりです。
- クライアントは、
bodyを INSERT SQL に設定したQueryを送信します。 - クライアントは、外部テーブル (INSERT ではまれ) もあれば送信します。クエリフェーズ とは異なり、ここでは空の Data マーカーは送信しません。
INSERTのQueryパケットは後続のデータを伴って送信されるため、データ終端を示す空の block は手順 5 まで送られません。これをスキーマ block より前に送ると、server はそれを行ストリームの終端として読み取り、行が 1 つもないまま INSERT を完了し、その後に到着する最初の実データの行パケットを、場違いなトップレベルパケットとして parse してしまいます。 - クライアントは、スキーマ Data パケットを読み取るまで、メタデータ パケット (TableColumns、Progress、ProfileInfo、Log、ProfileEvents) を順に受信します。これは 0 行ですが、完全なカラム構造 (名前と型) を持つ Block です。スキーマ block は契約です。次にクライアントが送信する行は、これらのカラムの shape に一致している必要があります。
- クライアントは data block を送信します。各 block について、
VarUInt(ClientPacket::Data = 2)、次に空の external-table 名としてString("")、その後に Block を書き込みます。カラム型は、スキーマ block のカラムと位置で一致している必要があります。 - クライアントは入力終端を送信します。これは空の Block (0 カラム、0 行) を持つ Data パケットです。
- クライアントは、
EndOfStream(成功) またはException(失敗) に達するまでレスポンスストリームを順に受信します。
async_insert = 1 が含まれている場合、server は行をキューに入れ、バッチの一部として flush します。ネゴシエートされた バージョン が 54484 以上 (PROGRESS_IN_ASYNC_INSERT) では、flush が完了すると server は追加の Progress パケットを送信し、その直後にその insert の ProfileEvents、続いて EndOfStream を送信します。54484 未満では、server はこの末尾の Progress を送信しません。このパケットは通常の Progress です。server は書き込み件数を反映する前にクエリ パイプラインをリセットするため、実際にはこの増分には経過時間しか含まれず、書き込まれた行数とバイト数の統計は付随する ProfileEvents を通じてクライアントに渡されます。手順 6 ですでに Progress の割り込み受信に対応しているクライアントであれば、追加の 1 パケットを受け取れるようにするだけで十分です。
接続は、EndOfStream または処理済みの Exception を受け取ると READY に戻ります。プロトコル違反や I/O エラーが発生した場合、接続は終了します。
メッセージリファレンス
Type カラムでは次を使用します。
VarUInt— 可変長の符号なし整数 (VarUIntを参照) 。String— VarUInt プレフィックス付きのバイト列 (Stringを参照) 。UInt8、Int32など — 固定幅の little-endian 整数。Bool— 1 バイトで、0x00または0x01です。
Role カラムは、各フィールドを誰が使用するかを示します。
- client — 外部クライアントが設定します。
- inter-server — サーバー間通信でのみ意味があります。外部クライアントはデフォルト値を書き込みます。
- universal — 両方で使用されます。
ClientHello (パケットタイプ 0)
ServerHello (パケットタイプ 0)
Rule —
password_complexity_rules の要素:
このリストはサーバー運用者のパスワードポリシー設定を反映したもので、あくまで参考情報です。サーバーはハンドシェイク中にこれらのルールを強制しません。パスワードの変更または設定機能を提供するクライアントは、準拠していないパスワードをサーバーに送信する前に、これらのルールを使ってエラーを示すことができます。
悪意のある、または誤設定されたサーバーに対する resource 使用量を抑えるため、デコードする
count は 256 エントリ、各 pattern と message の String は 4096 バイトを上限にしてください。パスワードポリシーが設定されていないサーバーでは、count が 0 (後続の組なし) であるのが一般的です。追補 (パケットタイプなし)
ADDENDUM (v54458) によって有効化され、ハンドシェイクのやり取りが完了した直後に送信されます。これは独立したパケットタイプではなく、各フィールドはパケットタイプを示すバイト接頭辞なしで、生のまま wire に載せられます。
chunked-framing への切り替えは、この Addendum が flush された後 に適用されます。つまり、Addendum 自体はフレーム化されません。
Ping (パケットタイプ 4)
0x04 のみです。chunking がネゴシエートされると、このバイトは chunk の 1 バイトのペイロードになります (チャンク化フレーミング を参照) 。
Pong (packet type 4)
0x04 のみです。チャンク化がネゴシエートされると、このバイトは chunk の 1 バイトのペイロードになります (chunked framingを参照) 。
Exception (packet type 2)
クエリ (パケットタイプ 1)
ClientInfo (Query に埋め込み)
CLIENT_INFO (v54032) で制御されます。 (ClientInfo 内の一部のフィールドは、以下の各フィールドの注記にあるとおり、以降のバージョンで制御されます。)
インターフェイス依存のレイアウト (フィールド 7–12) 上記のフィールド 7–12 は TCP 分岐です。
query_interface (フィールド 6) が TCP でない場合、これらのフィールドは別の wire レイアウトに置き換えられ、単に省略可能なだけではありません。したがって、デコーダーはフィールド 6 に基づいて分岐する必要があります。query_interface = 2(HTTP) : 代わりに、server によって転送された HTTP request の情報が書き込まれます。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フィールドは存在しません。- その他のインターフェイス: TCP フィールド (7–12) も HTTP フィールドも書き込まれず、ストリームはそのまま
quota_keyに進みます。
quota_key (フィールド 13) と distributed_depth (フィールド 14) はすべてのインターフェイスで続き、その後 version_patch (フィールド 15) は TCP の場合にのみ書き込まれます。この分岐が主に重要になるのは、開始側の server がもともと HTTP 経由で到着したクエリを転送する inter-server traffic の場合です。常に TCP フィールドを読むデコーダーは、そのようなパケットを誤って解釈し、http_method や http_user_agent を quota_key として扱ってしまいます。サーバー間認証
auth_hash) は、通信上でやり取りされる共有クラスターシークレット ではありません。生のシークレットを送信すると、認証に失敗するだけでなく、シークレット自体も漏えいしてしまいます。代わりに、サーバー間クライアントとして動作するサーバーは、ソルト付き SHA-256 ハッシュを使ってシークレットを知っていることを証明します。
- サーバー間モードに入ります。 接続するサーバーは、
ClientHello内でそのことを通知します。userフィールドにはサーバー間マーカーが入り、passwordは空です。続いて、同じClientHelloパケットの一部として、user/passwordフィールドの直後に 2 つの文字列、つまりクラスター名と新たに生成した 32 バイトのsalt(ランダムな値のencodeSHA256) を追加します。サーバーはServerHelloを送信する前にこの 2 つの文字列を読み取るため、クライアントはそれらを先に書き込む必要があります。先にServerHelloを待つとデッドロックになります。サーバーがそれらの読み取りで待機しているためです。 - nonce を取得します。
INTERSERVER_SECRET_V2(v54462) がネゴシエートされると、ServerHelloには 8 バイトのUInt64nonce が含まれます。 - ハッシュを計算します。
InitialQueryではないすべての Query パケットについて、クライアントはフィールド 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 バイトで読み取り、自身が保持するクラスターシークレットのコピーを使って同じ連結内容を再計算します。ダイジェストが一致しない場合、接続は拒否されます。
auth_hash を送信します。
設定
VarUInt 0 で終端されます。setting ごとのエンコードだけが、SETTINGS_SERIALIZED_AS_STRINGS (v54429) を境に、ネゴシエーション済みバージョンに応じて変わります。
v54429+ (STRINGS_WITH_FLAGS) — 各 setting は、ここに示す 3 つ組です。
key が空の場合、フィールド 2 と 3 は存在しません。
Pre-54429 (BINARY) — 各 setting は [String key][type-specific binary value] です。flags フィールドは書き込まれず、値は 10 進数やテキスト文字列ではなく、setting 固有のネイティブなバイナリ形式 (たとえば固定幅整数や長さプレフィックス付き文字列) でエンコードされます。リストは引き続き空の key で終端されます。ネゴシエーションされたバージョンが 54429 未満のクライアントは、上記の 3 つ組ではなく、このバイナリ形式を読み書きしなければなりません。 (ただし、ユーザー定義の custom setting は例外で、どちらのエンコーディングでも常に flags と文字列値を持ちます。)
flags フィールドには次が格納されます。
0x01— Important: この setting はクエリ結果に影響するため、古い peer によって黙って無視されてはなりません。0x02— Custom: ユーザー定義の custom setting。0x0c— 独立したフラグではなく 2-bit tier フィールドです:0x00= Production、0x04= 廃止された、0x08= Experimental、0x0c= ベータ。2 ビットすべて (flags & 0x0c) を読んでください。単純にflags & 0x04で判定すると、ベータ (0x0c) を廃止されたものと誤分類してしまいます。0x80— HotReload (再起動なしでの config 再読み込み。flags enum で定義されており、主に 協調 settings で見られます) 。
パラメータ
SELECT {x:UInt64} のようなパラメータ化クエリで使用するクエリパラメータです。Custom フラグ (0x02) が設定された Setting と同じ形式でエンコードされ、同様に空の key で終端されます。
パラメータ値は生のリテラルではなく、値の SQL 表現です。文字列型のパラメータは、単一引用符で囲んだ状態で渡す必要があります (たとえば、
{name:String} の値は Alice ではなく 'Alice' です) 。そうでない場合、サーバー側の値パーサーに拒否されます。Data (パケットタイプ 1 server→client、パケットタイプ 2 client→server)
table_name プレフィックスが含まれます。異なるのは パケットタイプ byte のみです。
データ終端マーカーは、
table_name に関係なく、Block が空、つまり 0 カラムかつ 0 行のパケットです。サーバーは、デコードされたブロックが空 (block.empty()) の場合にのみ、クライアントの Data パケットを終端として扱います。table_name = "" でブロックが空でないパケットは、終端ではなく通常の行パケットです。したがって、INSERT の行ストリームは、空でない Data ブロックが連続し、その最後にストリームを終了する 1 つの空の Data ブロックが続く形になります。
ブロックの各種バリアントとその意味については、Block variants を参照してください。
Progress (パケットタイプ 3)
Progress パケット以降の増分であり、累計値ではありません。送信前に、サーバーは counters を読み取ってアトミックに 0 にリセットし、elapsed_ns は前回送信からの経過時間の差分として計算します。したがってクライアントは、連続して届くパケットをローカルで必ず加算していく必要があります。パケットを絶対値として扱うと、複数のパケットが到着した時点で進捗表示が後戻りしたり、過少計上されたりします。
ProfileInfo (パケットタイプ 6)
Totals (パケットタイプ 7)
WITH TOTALS を指定したクエリに対して送信されます。ワイヤ形式は Data と同一で、table_name 文字列 (常に空) の後に Block が続きます。異なるのはパケットタイプのバイトだけです。
Extremes (パケットタイプ 8)
extremes 設定が有効な場合に送信されます。ワイヤ形式は Data と同一です。ブロックにはちょうど 2 行が含まれます。行 0 には各カラムの最小値が、行 1 には最大値が入ります。
Log (パケットタイプ 10)
send_logs_level 設定。詳細はログストリーミングを参照) 。
Data と同じエンベロープおよびボディのフォーマットです。この block は固定の num_columns = 8 と、あらかじめ定義されたスキーマを持ちます。各ログ行は 8 つのカラムすべてにまたがる 1 行で、1 つの Log パケットに複数の行が含まれる場合があります。
ProfileEvents (パケットタイプ 14)
num_columns = 6 を持ち、スキーマは事前定義されています。各イベントは 1 行です。
value カラムの要素型はパケットごとに固定ではありません。古いサーバーは UInt64 を、新しいサーバーは Int64 を出力します。ビット幅を決め打ちせず、block header からカラムの型文字列を読み取ってください。TableColumns (パケットタイプ 11)
COLUMN_DEFAULTS_METADATA (v54410) によって制御されます。サーバーは、カラムのデフォルト値に関するメタデータを渡すために、INSERT のスキーマブロックの前にこれを送信します。ただし、ネゴシエートされたバージョンが 54410 以上 かつ input_format_defaults_for_omitted_fields 設定が有効な場合に限られます。54410 未満ではこのパケットは一切送信されないため、古い client はこれを待ってはなりません — スキーマ Data block が直接続きます。v54410+ の client は、どちらの順序にも対応できるようにしておく必要があります。つまり、オプションの TableColumns があり、その後にスキーマブロックが続く場合です。
v54481+ での圧縮されたボディネゴシエートされたバージョンが 54481 以上 (
COMPRESSED_LOGS_PROFILE_EVENTS_COLUMNS) の場合、サーバーは 両方の フィールドを同じオプションの圧縮出力パス経由で書き込みます。そのため、クエリで compression = true の場合、TableColumns のボディ全体 (external_table + columns_description) は compression frame 内に含まれ、client は対応する展開済み stream を通してこれを読み取ります。クエリで圧縮が有効でない場合、ボディは上の table に示したとおり、wire 上でそのまま非圧縮で送られます。これは INSERT のスキーマ応答では重要です。Log と ProfileEvents では圧縮処理を切り替えていても、TableColumns では切り替えない client は、クエリ圧縮が有効なときに応答を誤って読み取ります。TimezoneUpdate (パケット型 17)
TIMEZONE_UPDATES (v54464) で有効になります。送信されるのは厳密に 1 か所だけで、input table function の初期化時です (INSERT INTO <table> SELECT ... FROM input('<structure>') 形式のクエリで、client から行をストリーミングします) 。server は入力スキーマの Data block (INSERT phase を参照) を送信した直後に、クエリコンテキストにおける現在の session_timezone を含む TimezoneUpdate を送出します。これにより、client はこれから送信する行を同じ timezone で parse できます。server は、クエリの途中で任意に実行された SET session_timezone の変更に対してこのパケットを送出することはなく、後続の結果 block をどのような timezone でフォーマットすべきかを client に伝えるために送出することもありません。
このパケットは 1 回だけ到着し、入力スキーマ block の直後、client が行 block の送信を開始する前に届きます。
TimezoneUpdate を無視するデコーダーでも、wire の整合性を保つために末尾の String は必ず消費しなければなりません。
SSH チャレンジレスポンス認証 (パケットタイプ 11、12、18)
SSH_AUTHENTICATION (v54466) で有効になり、明示的に有効化した場合にのみ使用されます。ClientHello が user = " SSH KEY AUTHENTICATION " + <real_user> (先頭と末尾のスペースを含む) および password = "" を送信すると、接続は SSH フローに入ります。サーバーはこのプレフィックスを読み取り、取り除いて実際のユーザー名を復元し、チャレンジレスポンス方式に切り替えます。
このフローはパスワード認証の代わりに実行され、チャレンジレスポンスのやり取りは ServerHello より前に行われます。サーバーは認証が成功するまで Hello 応答を保留します。
- Client は、SSH マーカープレフィックスと空の password を含む ClientHello を送信します。
-
Client は
SSHChallengeRequest(パケット 11) を送信します。サーバーはまだ ServerHello を送信しておらず、先に認証を処理するため、このパケットを待ってここでブロックします。 -
サーバーは、ランダムなバイト列を含む
SSHChallenge(パケット 18) を返します。 -
Client は署名対象の文字列を構築し、生の challenge ではなくその文字列に署名してから、署名を含む
SSHChallengeResponse(パケット 12) を送信します。署名対象のメッセージは、次の 4 つの部分をこの厳密な順序で、区切り文字なしにバイト単位で連結したものです。 -
サーバーは、ユーザーに登録された公開鍵に対して署名を検証し、同じ
decimal(protocol_version) + default_database + user + challenge文字列を再構築します。成功するとServerHelloを送信します。これはパスワードフローと同じ応答で、その後ハンドシェイクは通常どおり継続します (Addendum など) 。失敗した場合はExceptionを返して接続を終了します。生の challenge バイト列だけに署名した Client は認証に失敗します。
これは password handshake とは逆で、ClientHello の直後に ServerHello が続きます。SSH 認証では、署名が検証されるまで ServerHello は返されないため、ServerHello が現れる前に SSH の challenge-response が handshake に差し挟まれます。
パケットタイプ リファレンス
Client → Server
サーバー → クライアント
設定
- トランスポート層の設定 — TCP ソケットのオプションとタイムアウトで、TCP 接続自体の動作に影響します。
- アプリケーション層の設定 — Query パケットの設定リストで渡されるクエリごとの調整項目で、サーバーが通信上で送信する内容やそのフレーミング方法に影響します。
- 対象外の設定 — プロトコル設定と混同されがちですが、実際には SQL の実行やストレージを制御する設定です。
トランスポート層の設定
ソケットオプション
タイムアウト
タイムアウトは次のように入れ子になります:
接続の上限
定期的にクエリを発行する接続は、無期限に維持できます。1 時間後に回収されるのはアイドル接続のみで、デフォルトでは接続の最大寿命は設定されていません。
アプリケーション層の設定
圧縮
Query パケット (フィールド 6) の
compression フラグは、圧縮のオン/オフを切り替えます。これらの設定では、オンのときに使用するコーデックを選択します。
ログストリーミング
send_logs_level を "none" 以外に設定すると、サーバーはクエリ実行中に Log パケットを出力します。
Progress レポート
これは目標となる最小値であり、厳密な最大値ではありません。クエリの処理の進みが十分に速くない場合、サーバーは Progress パケットの送信頻度をさらに下げることがあります。
結果エンベロープ
非同期 INSERT
分散トレーシング
対象外の設定
max_threads— クエリ実行時の並列度。max_memory_usage— クエリごとのメモリ上限。max_block_size,preferred_block_size_bytes— クエリ処理中の内部的な block サイズを指定します。ワイヤ上の block はこれらとは独立しています。compile_expressions— JIT コンパイル。CPU にのみ影響します。async_insert_max_data_size— server-side の queue バッファ。input_format_*とoutput_format_*のすべての設定 (input_format_native_*/output_format_native_*ファミリーは除く) —native以外のものは、別のフォーマット (たとえば HTTP 経由) を選択または調整するためのものであり、ネイティブプロトコルのDatablock は変更しません。
*_native_* 設定です。これらはネイティブ TCP の Data block 内のバイト列を変更するため、プロトコル実装ではそれを考慮する必要があります。output_format_native_encode_types_in_binary_format は column の type フィールドをテキスト文字列からバイナリの型エンコーディングに切り替え、output_format_native_write_json_as_string は JSON カラムを String として出力し、output_format_native_use_flattened_dynamic_and_json_serialization は FLATTENED Dynamic/JSON レイアウトを選択します。これらはパケットのエンベロープではなく block のボディに影響するため、Native Format の仕様で規定されています。詳しくは、column wire layout と versioned types を参照してください。
用語集
- 通常のクエリ (
SELECTなど) :Queryパケットと外部テーブル用の Data パケット群の後に送信し、「外部データはこれ以上ない」ことを示します。その後、サーバーが実行を開始します。 INSERT: クライアントはスキーマ前のマーカーを送信しません。最初にサーバーがスキーマブロックを送信し、次にクライアントが行の Data ブロックをストリーミングし、最後に行ストリームを終了するための空の Data パケットを送信します。スキーマブロックの前に空マーカーを送ると、行がそこで終了したものとして即座に解釈され、データが失われます。
min(client_version, server_version) です。handshake 中に計算されます。これにより、connection の存続期間中にどの features が有効かが決まります。
Packet — ワイヤメッセージです。先頭に VarUInt のパケット type code があり、その後に type に応じたフォーマットのボディが続きます。packet envelope を参照してください。
Packet type code — パケット先頭の VarUInt で、そのフォーマットを識別します。現在は 0–18 の値が割り当てられています。packet type reference を参照してください。
レスポンスストリーム — クエリ実行中にサーバーが出力するパケット列です。長さは未定で、終端は EndOfStream (成功) または Exception (失敗) のどちらか 1 つだけです。query phase を参照してください。
スキーマブロック — INSERT phase 中にサーバーが送信する header block (カラムはあるが 0 行の Block) で、クライアントがデータを送信する前に、想定されるカラム shape を通知するものです。
設定リスト — Query ボディ内の (key, flags, value) tuple の並びで、空の key で終端します。クエリごとのアプリケーション層 configuration を保持します。Setting を参照してください。
Stage — Query パケット内の VarUInt フィールド (field 5) で、サーバーがクエリをどこまで実行するかを制御します。外部クライアントは通常 2 (Complete) を送信し、distributed queries やシリアライズ済みのクエリプランではより大きい値を使用します。wire 値の完全な一覧は Query の field 5 を参照してください。
Terminator — ストリームを終了するパケットです。Query のレスポンスは EndOfStream (成功) または Exception (失敗) で終わります。クライアントの入力ストリームは空の Data マーカーで終わります。