-
ClickHouseClient(推奨) :単一のインスタンスを共有して使うことを想定した、高水準でスレッドセーフなクライアントです。クエリと一括挿入のためのシンプルな非同期 API を提供し、ほとんどのアプリケーションに適しています。 -
ADO.NET (
ClickHouseDataSource、ClickHouseConnection、ClickHouseCommand) :標準的な .NET のデータベース抽象化機能です。ORM とのインテグレーション (Dapper、Linq2db) や ADO.NET との互換性が必要な場合に使用します。ClickHouseBulkCopyは、ADO.NET 接続を使用してデータを効率的に挿入するための補助クラスです。ClickHouseBulkCopyは非推奨であり、将来のリリースで削除されます。代わりにClickHouseClient.InsertBinaryAsyncを使用してください。
移行ガイド
.csprojファイルでパッケージ名をClickHouse.Driverに変更し、バージョンを NuGet で公開されている最新版 に更新してください。- コードベース内の
ClickHouse.Clientへの参照をすべてClickHouse.Driverに変更してください。
サポート対象の .NET バージョン
ClickHouse.Driver は、次の .NET バージョンをサポートしています。
- .NET 6.0
- .NET 8.0
- .NET 9.0
- .NET 10.0
サポート対象の ClickHouse バージョン
このクライアントは、直近の3つのリリースと直近の2つのLTSリリースを公式にサポートしています。インストール
NuGet からパッケージをインストールします:クイックスタート
設定
ClickHouse への接続は、次の 2 つの方法で設定できます。- 接続文字列: ホスト、認証情報、その他の接続オプションを指定する、セミコロン区切りのキーと値のペア。
ClickHouseClientSettingsオブジェクト: 設定ファイルから読み込むか、コード内で設定できる、厳密に型付けされた設定オブジェクト。
接続設定
データフォーマットとシリアライゼーション
セッション管理
UseSession フラグを指定するとサーバーセッションが永続化され、SET ステートメントや一時テーブルを使用できるようになります。セッションは 60 秒間操作がないとリセットされます (デフォルトのタイムアウト) 。セッションの有効期間は、ClickHouse の文またはサーバー設定でセッション設定を指定することで延長できます。ClickHouseConnection クラスは通常、並列動作が可能です (複数のスレッドから同時にクエリを実行できます) 。ただし、UseSession フラグを有効にすると、1 つの接続で同時に実行できるアクティブなクエリは常に 1 つに制限されます (これはサーバー側の制限です) 。セキュリティ
HTTP クライアントの設定
ロギングとデバッグ
カスタム設定とロール
接続文字列でカスタム設定を指定する場合は、
set_ プレフィックスを付けます (例: “set_max_threads=4”) 。ClickHouseClientSettings オブジェクトを使用する場合は、set_ プレフィックスを付けないでください。利用可能な設定の一覧は、こちらを参照してください。接続文字列の例
基本的な接続
カスタム ClickHouse 設定を使用する場合
QueryOptions
QueryOptions を使用すると、クライアントレベルの設定をクエリ単位で上書きできます。すべてのプロパティは任意であり、指定した場合にのみクライアントのデフォルト値を上書きします。
例:
InsertOptions
InsertOptions は QueryOptions を拡張し、InsertBinaryAsync による一括挿入操作向けの設定を追加します。
QueryOptions のすべてのプロパティは InsertOptions でも使用できます。
例:
スキーマ確認クエリの省略
デフォルトでは、InsertBinaryAsync は挿入のたびに、カラム型を調べるための SELECT ... WHERE 1=0 クエリを送信します。大量のデータを処理する場合は、次の2つの方法でこの余分な処理を省けます。
方法1: カラム型を明示的に指定する
コンパイル時にテーブルのスキーマが分かっている場合は、ColumnTypes でカラム型を直接指定します。これにより、スキーマ確認クエリは一切送信されません。
UseSchemaCache = true を設定します。これにより、スキーマの取得に必要なクエリが最初の一度だけ実行され、同じ ClickHouseClient インスタンスでの以降の挿入時には取得済みのスキーマが再利用されます。
ColumnTypesはUseSchemaCacheよりも優先されます。両方が設定されている場合は、明示的に指定した型が使用されます。- スキーマキャッシュは
ALTER TABLEによる変更を検出しません。テーブルスキーマを変更した場合は、新しいClickHouseClientを作成するか、そのテーブルではUseSchemaCacheを使用しないでください。 - キャッシュは
ClickHouseClientインスタンスごとに保持され、(データベース, テーブル) の組をキーとして管理されます。同じテーブルに対して異なるカラムのサブセットを指定した場合でも、キャッシュされた 1 つのスキーマが共有されます。
ClickHouseClient
ClickHouseClient は、ClickHouse とやり取りするための推奨 API です。スレッドセーフで、シングルトンとしての利用を前提に設計されており、HTTP 接続プーリングを内部で管理します。
クライアントの作成
接続文字列またはClickHouseClientSettings オブジェクトを使用して ClickHouseClient を作成します。指定可能なオプションについては、設定 セクションを参照してください。
ClickHouse Cloud サービスの詳細は、ClickHouse Cloud コンソールで確認できます。
サービスを選択し、Connect をクリックします。
C# を選択すると、その下に接続情報が表示されます。
セルフマネージドの ClickHouse を使用している場合は、ClickHouse 管理者が設定した接続情報を使用してください。
接続文字列を使用する場合:
ClickHouseClientSettingsを使用します:
IHttpClientFactory を使用します。
ClickHouseClient は、アプリケーション全体で共有し、長期間使い続けることを想定して設計されています。インスタンスは一度だけ作成し (通常はシングルトンとして) 、すべてのデータベース操作で再利用してください。HTTP 接続プーリングはクライアントが内部で管理します。クエリの実行
結果を返さないステートメントには、ExecuteNonQueryAsync を使用します:
ExecuteScalarAsync を使用します。
データの挿入
パラメータを使用したデータの挿入
ExecuteNonQueryAsync でパラメータ化クエリを実行し、データを挿入します。パラメータの型は、SQL 内で {name:Type} 構文を使って指定する必要があります。
一括挿入
大量の行を効率よく挿入するには、InsertBinaryAsync を使用します。ClickHouse のネイティブな行バイナリフォーマットを使ってデータをストリーミングし、バッチの並列アップロードにも対応しています。また、パラメータ化されたクエリで発生し得る「URL が長すぎます」エラーも回避できます。
InsertOptions でバッチ処理と並列度を設定します。
- クライアントは挿入前に
SELECT * FROM <table> WHERE 1=0を実行し、テーブル構造を自動的に取得します。指定した値は、挿入先のカラム型と一致している必要があります。このクエリを省略するには、InsertOptions.ColumnTypesまたはInsertOptions.UseSchemaCacheを使用してください。 MaxDegreeOfParallelism > 1の場合、バッチは並列にアップロードされます。セッションと並列挿入は併用できないため、セッションを無効にするか、MaxDegreeOfParallelism = 1に設定してください。- 値を指定していないカラムにサーバーが DEFAULT 値を適用するようにするには、
InsertOptions.FormatにRowBinaryFormat.RowBinaryWithDefaultsを指定してください。
POCO を使った挿入
object[] 配列を組み立てる代わりに、厳密に型付けされた POCO オブジェクトをそのまま挿入できます。型を一度登録すれば、あとは IEnumerable<T> を渡すだけです。
マッピングされたプロパティのすべてで
Typeが明示的に指定されている場合、スキーマ確認用クエリは実行されません。一部のプロパティでのみ型が明示的に指定されている場合、ドライバーはすべてのカラムについてスキーマ確認用クエリを実行します。
InsertBinaryAsync<T>は、object[]オーバーロードと同じInsertOptions (バッチ処理、並列度、スキーマのキャッシュ) をサポートします。
object[]オーバーロードとは異なり、InsertBinaryAsync<T>ではカラムリストを明示的に指定できません。対象のカラムは、登録された型のマッピング済みプロパティによって決まります。挿入対象のカラムを制御するには、[ClickHouseNotMapped]でプロパティを除外するか、[ClickHouseColumn(Name = "...")]でカラム名を変更してください。InsertOptionsでColumnTypesを設定した場合は、その設定がPOCOの属性より優先されます。スキーマの進化
型の登録後にターゲットテーブルへカラムを追加しても、POCO による挿入は問題なく動作します。ドライバーは POCO にマッピングされたカラムのみを挿入するため、追加されたカラムにDEFAULT (またはその他のデフォルト式) が設定されていれば、その値はサーバーによって自動的に設定されます。コードの変更や再登録は不要です。
INSERT クエリの配置
バイナリ形式で挿入する場合、INSERT INTO ... FORMAT ... ステートメントはリクエストボディの先頭行に、データ行より前に書き込まれます。ボディはデフォルトで圧縮されるため、URL だけを調べるルーティングやログにはステートメントが表示されません。ステートメントを query URL パラメータとして送信し、ボディにはデータ行だけを含めるには、InsertOptions.QueryPlacement を InsertQueryPlacement.Url に設定します。
query パラメータを基にリクエストを振り分けたり検査したりする場合や、ステートメントをアクセスログやオブザーバビリティツールに記録したい場合に使用します。ステートメントの長さも URL の長さに含まれるため、この設定は明示的に有効化する必要があります。適用される上限は、.NET ランタイム、中継コンポーネント、サーバーがそれぞれ設ける上限のうち、最も低い値です。.NET 6 から .NET 9 では、System.Uri により、エンコードされたリクエスト URI 全体が 65,519 文字に制限されます。この上限を超えると、ドライバーは InsertQueryPlacement.Body に戻すよう案内する InvalidOperationException をスローします。ClickHouse の http_max_uri_size のデフォルト値は 1 MiB ですが、中継コンポーネントによっては、さらに低い上限が適用される場合があります。ボディモードでは、ステートメントと行にこの URL の長さの制限は適用されません。ただし、ほかのリクエストオプションが URL に含まれる場合はあります。
この設定は Compressor とは独立しており、どちらのモードでもボディは同じ方法でエンコードされます。
データの読み取り
SELECT クエリを実行するにはExecuteReaderAsync を使用します。返される ClickHouseDataReader では、GetInt64()、GetString()、GetFieldValue<T>() などのメソッドを使って、結果のカラムに型に応じた方法でアクセスできます。
次の行に進むには Read() を呼び出します。行が残っていない場合は false を返します。カラムには、0 始まりのインデックスまたはカラム名でアクセスできます。
POCO への読み込み
カラムを位置や名前で指定して読み込む代わりに、クエリ結果を独自のクラスに直接ストリーミングできます。型をクライアントに一度登録すれば、QueryAsync<T> を使用できます。
RegisterPocoType<T>() は挿入用と読み取り用のマッピングを設定し、両方を事前に検証します。RegisterBinaryInsertType<T>() は変更されておらず、後方互換性を維持するため、引き続き挿入専用です。
登録する型は、次の条件を満たす必要があります。
- 引数なしの public コンストラクターを持つこと。
- public かつ
initではないセッターを持つ public プロパティが少なくとも 1 つあること。requiredプロパティにも対応しています。
InvalidOperationException がスローされます。したがって、object 型のプロパティにはどのカラムも代入できます。
QueryAsync<T> は、以下の各カラムを対応するプロパティに直接読み込みます。
どの行でも、カラムが
Nullable(...) かどうかにかかわらず、対応するプロパティ型の null 許容形式 (long?、DateOnly? など) を使用できます。
Nullable(T) カラムに null 非許容の値型プロパティを指定しても登録はできますが、
NULL を読み込むと例外がスローされます。
LowCardinality(T)、SimpleAggregateFunction(f, T)、Object(T) などのラッパーは、T と同じようにマッピングされます。
複合カラムもサポートされており、読み込み型のリファレンスに示された
フレームワーク型に対応します。Array(T) は T[]、Tuple(...) は System.Tuple<...>、
Nested(...) は Tuple<...>[]、JSON は JsonObject (JsonReadMode=String では string) 、
Variant/Dynamic は object に対応します。
Map(K, V) カラムは例外です。List<KeyValuePair<K, V>> または KeyValuePair<K, V>[]
型のプロパティなら、どちらの MapReadMode でもボックス化せずに読み込めます。
その際、wire上の順序と重複するキーは保持されます。Dictionary<K, V> 型のプロパティを使用できるのはデフォルトモードだけです。
キーと値の型はそれぞれ完全に一致する必要があるため、
Map(String, Nullable(Int32)) には KeyValuePair<string, int?> が必要です。
カラムに複数のプロパティ型を使用できる場合 (DateTime カラムなら DateTime、
DateTimeOffset、DateOnly、String カラムなら string、byte[]) 、宣言したプロパティ型によって読み込み時の表現が決まります。これらの代替表現は
POCO を使った読み込みでのみ利用できるため、QueryAsync<T> では使用できますが、MapTo<T> では使用できません。
リーダーを手動で反復処理する場合は、ClickHouseDataReader.MapTo<T>()を使用すると、リーダーを進めずに現在の行を登録済みのPOCOとしてマテリアライズできます。
MapTo<T> を使用します。このメソッドはリーダーから
ボックス化された値を介して行を読み取るため、上記の代替プロパティ型は利用できず、QueryAsync<T> より多くのメモリを割り当てます。
行の取得だけが目的なら、QueryAsync<T> を優先してください。性能比較の数値については、
マテリアライズ方法の選択を参照してください。
クライアント全体またはクエリごとに設定した読み取り値コンバーターは、どちらの読み取りパスにも適用されますが、
ボックス化を伴わない読み取りは無効になりません。ドライバーは、カラムを読み取った方法に応じてオーバーロードを使い分けます。ボックス化を伴わない
カラムには型付きの ConvertValue<T>、複合カラムにはボックス化された値を扱う ConvertValue を使用します。2 つのオーバーロードは
結果が一致するように実装してください。そうしないと、同じカラムでも読み取りパスによって結果が異なります。
LoggerFactory が設定されている場合、RegisterPocoType<T>() と RegisterBinaryInsertType<T>() は Debug レベルのログ (カテゴリ ClickHouse.Driver.Client) を出力します。このログには、各プロパティがどのカラムに対応付けられたか、スキップされたプロパティとその理由が記録されます。詳しくはログと診断を参照してください。
SQLパラメータ
ClickHouseでは、SQLクエリのクエリパラメータは通常、{parameter_name:DataType}の形式で指定します。
例:
SQL の ‘bind’ パラメータは HTTP URI のクエリパラメータとして渡されるため、数が多すぎると「URL too long」例外が発生する場合があります。この制限を回避するには、大量のデータを挿入する際に
InsertBinaryAsync を使用してください。ADO スタイルの @name プレースホルダー
ドライバーは、Dapper などの ORM が生成する @name プレースホルダーにも対応しています。これは
クライアント側の利便性を高めるための機能です。リクエストの送信前に各プレースホルダーが
{name:ResolvedType} に書き換えられるため、サーバーに @ が渡されることはありません。型の決定方法については
型の解決を参照してください。可能な場合は、型を明示する
{name:Type} 形式を使用してください。
対応するパラメータがない @name はそのまま残され、サーバーによって拒否されます。照合では
大文字と小文字が区別されるため、@ID は id という名前のパラメータには対応しません。
書き換えを無効にするには、ドライバーを初めて使用する前に
ClickHouse.Driver.DisableReplacingParameters AppContext スイッチを
設定してください。無効になるのはテキストの書き換えだけです。パラメータは引き続き送信されるため、
ネイティブの {name:Type} 構文で記述したクエリは引き続き動作します。Identifier パラメータ
Identifier パラメータ型を使うと、引用符で囲まれた文字列リテラルとしてではなく、データベース名、テーブル名、カラム名を安全にバインドできます。SQL で {name:Identifier} 構文を使用するか、ClickHouseDbParameter.ClickHouseType = "Identifier" を設定します。
クエリ ID
各クエリには一意のquery_id が割り当てられます。この ID を使って、system.query_log テーブルからデータを取得したり、実行時間の長いクエリをキャンセルしたりできます。QueryOptions を使って、カスタムのクエリ ID を指定できます。
カスタムパラメータ型対応
@ 形式のパラメータ (例: WHERE id = @id) を使用すると、ドライバーは .NET の値の型から ClickHouse の型を自動的に推論します。たとえば、int は Int32 に対応します。
このデフォルトの動作を変更するには、ClickHouseClientSettings の ParameterTypeResolver を設定します。個々のパラメータに ClickHouseType を設定することなく、すべての DateTime パラメータにミリ秒精度の DateTime64(3) を使用したり、すべての Decimal 値に特定の小数点以下の桁数を指定したりする場合に便利です。
単純な型マッピングに DictionaryParameterTypeResolver を使用する場合:
IParameterTypeResolver:
値や名前に基づいて型を判定する場合は、IParameterTypeResolver インターフェイスを直接実装してください。null を返すと、デフォルトの型推論にフォールバックします。
QueryOptions.ParameterTypeResolver を使って、クエリごとにリゾルバを設定することもできます。設定すると、クライアントレベルのリゾルバより優先されます。
型解決の優先順位:
リゾルバは、型解決の優先順位を決めるチェーンの一要素です。優先順位の高い順に示します。
- パラメータに明示的に設定された
ClickHouseType - クエリ内の
{name:Type}構文で指定された SQL 型ヒント IParameterTypeResolver(QueryOptions.ParameterTypeResolverが未設定の場合はClickHouseClientSettings.ParameterTypeResolverを使用)- 組み込みの型推論 (
TypeConverter.ToClickHouseType)
ClickHouseConnection を使用する場合にも機能します。クライアントから作成された接続には、設定が引き継がれます。
カスタムパラメータ値のフォーマット
IParameterFormatter は、パラメータ値をどのようにシリアル化するかを決めるフックです。組み込みのフォーマット (DateTime の精度、小数のカルチャ設定、文字列のエスケープ、数値の表現など) が、スキーマや後続のツールで期待される形式と異なる場合に使用します。
すべてのパラメータ化クエリにフォーマッタを適用するには、ClickHouseClientSettings の ParameterFormatter を設定します。フォーマッタは値、解決済みの ClickHouse の型名、パラメータ名を受け取り、サーバーに送信する文字列表現を返します。null を返すと、デフォルトのフォーマッタで処理されます。
DictionaryParameterFormatter を使用して CLR 型ごとに簡単なフォーマットを指定する場合:
IParameterFormatter:
QueryOptions.ParameterFormatter を使用すると、クエリごとにフォーマッタを設定することもできます。設定したフォーマッタは、クライアント全体に設定されたフォーマッタより優先されます。
複合値:
フォーマッタは、最上位のコレクションパラメータだけでなく、複合値 (Array、Tuple、Map、Nullable、LowCardinality、Variant) 内の各要素にも適用されます。たとえば、typeof(int) のマッピングは、Array(Int32) 内の各 Int32 要素を個別にフォーマットします。
複合値内でのシングルクォートによる囲み:
複合リテラルに含まれる文字列系の ClickHouse 型 (String、FixedString、Enum8、Enum16、IPv4、IPv6、UUID) については、ドライバーがフォーマッタの出力をシングルクォートで囲みますが、出力内容はエスケープしません。返された文字列にエスケープされていないシングルクォートやバックスラッシュが含まれると、複合リテラルが不正な形式になり、サーバーはクエリを拒否します。
最上位の文字列パラメータ (複合値に含まれないもの) は、シングルクォートで囲まずにそのまま使用されるため、エスケープは不要です。
フォーマッタの優先順位:
IParameterFormatter(QueryOptions.ParameterFormatterが未設定の場合はClickHouseClientSettings.ParameterFormatterを使用) 。非 null の値を返した場合は、その値が使用されます。HttpParameterFormatterに組み込まれた型ごとのフォーマット処理。
null または DBNull の値にはフォーマッタは適用されず、常に ClickHouse の null を表す値 (\N) としてシリアル化されます。
読み取り値のカスタム変換
IReadValueConverter を使用すると、データリーダーがデシリアライゼーション後に返す値を、CLR 型を変えずに変換できます。一般的な用途として、タイムゾーンのない DateTime カラムに対する DateTime.Kind = Utc の設定、文字列のトリミングや正規化、JSON カラムの値をアプリケーションコードに渡す前の後処理などがあります。
すべての読み取りにコンバーターを適用するには、ClickHouseClientSettings の ReadValueConverter を設定します。コンバーターは、ボックス化された値を取得する経路 (GetValue) とジェネリックの経路 (GetFieldValue<T>) のどちらでも、行ごと、カラムごとに1回呼び出されます。コンバーターを設定しなければ追加の処理負荷はなく、リーダーは値を直接返します。
CLR 型ごとの単純な変換に DictionaryReadValueConverter を使用する:
For<T> に登録されていない値は、変換されずにそのまま渡されます。振り分けは CLR 型の完全一致で行われるため、リーダーが実際に生成する型を登録してください (例:JsonReadMode.Binary の JSON カラムには For<JsonObject>) 。
高度な用途向けのカスタム IReadValueConverter:
ClickHouse 側の型文字列に基づいて振り分ける必要がある場合 (たとえば、DateTime と DateTime('UTC') はどちらも同じ CLR 型として扱われるため、両者を区別したい場合) は、IReadValueConverter を直接実装してください:
GetFieldType、GetSchemaTable) はコンバーターを経由しないため、返される値と整合していなければなりません。
QueryOptions.ReadValueConverter を使えば、クエリごとにコンバーターを設定することもできます。設定した場合は、クライアントレベルのコンバーターより優先されます。
コンバーターの適用範囲:
コンバーターは、デシリアライズされたセルの値全体を受け取り、カラムごとに1回呼び出されます。複合コンテナーの内部には再帰的に適用されません。Array(Int32) カラムでは int[]、Tuple(Int32, String) カラムでは ITuple が渡されます。
呼び出されるオーバーロード:
ドライバーがどちらを呼び出すかは、呼び出し元によるカラムの読み取り方法によって決まるため、両方のオーバーロードで一貫した結果を返す必要があります。
ConvertValue<T>— 型付きアクセサのGetByte、GetSByte、GetInt16/32/64、GetUInt16/32/64、GetFloat、GetDouble、GetGuid、GetDateTime、GetIPAddress、GetBigInteger、GetFieldValue<T>、および POCO の読み取りパスでボックス化されないすべてのカラム。ConvertValue(ボックス化された値) —GetValue、GetValues、インデクサー、GetChar、GetTuple、およびGetBoolean、GetDecimal、GetStringで型変換を行うパス。
IsDBNull はコンバーターを呼び出しません。null フラグを直接読み取るため、コンバーターで
値が null とみなされるかどうかを変えることはできません。TryGetEnumOrdinal もコンバーターを経由しません。詳しくは
enum の序数の読み取りを参照してください。
このコンバーターは ADO.NET の ClickHouseConnection パスでも機能します。クライアントから作成された接続には、そのクライアントの設定が引き継がれます。
生のストリーミング
ExecuteRawResultAsync を使用すると、データリーダーを介さずに、クエリ結果を指定したフォーマットで直接ストリーミングできます。データをファイルにエクスポートしたり、他のシステムにそのまま渡したりする場合に便利です:
JSONEachRow、CSV、TSV、Parquet、Native。利用可能なすべてのフォーマットについては、フォーマットのドキュメントを参照してください。
クエリ単位の転送時圧縮
デフォルトでは、Compression=true (接続文字列のデフォルト値) の場合、クライアントは zstd, lz4, gzip, deflate をネゴシエートし、ストリームを透過的にデコードします。
生データのエクスポート (Parquet、Arrow、Native など) では、接続全体の設定を変えずに CPU 使用量と帯域幅のトレードオフを調整するため、別のコーデック (zstd や lz4 など) を指定できます。QueryOptions.AcceptEncoding と ClickHouseCommand.AcceptEncoding は、単一のリクエストに対して HTTP Accept-Encoding ヘッダーを設定し、既定のヘッダーがあれば置き換えます。また、URL に enable_http_compression=1 を強制的に設定します (ClickHouse が Accept-Encoding に従うには、この設定が必要です) 。
HttpClient の設定
設定は不要です。ドライバーが生成するHttpClient では、AutomaticDecompression は DecompressionMethods.None のままで、レスポンスはドライバー自身がデコードします。そのため、Content-Encoding が意図せず削除されることはなく、生のボディをサーバーが送信したとおりに受け取れます。
エラーボディ
enable_http_compression=1 が設定されている状態でサーバーが 4xx/5xx を返すと、正常な応答に使用する場合と同じコーデックでエラーボディを圧縮します。ドライバーは、対応しているすべてのコーデック (lz4、zstd、gzip、deflate、br/brotli) で圧縮されたエラーボディをデコードするため、ClickHouseServerException のメッセージを読めます。非対応のコーデック (snappy など) の場合は、コーデック名を示し、元のエラーメッセージが記録されている system.query_log を参照するよう案内するプレースホルダーを返します。
レスポンスの解凍
Accept-Encoding はサーバーにレスポンスの圧縮を要求するだけで、受け取ったレスポンスのデコードは別途必要です。ドライバーがレスポンスの Content-Encoding に基づいて自動的にデコードするため、通常の読み取り API (ExecuteReaderAsync、ExecuteScalarAsync、ExecuteNonQueryAsync、QueryAsync<T>、Dapper、EF Core、linq2db) は追加設定なしで圧縮されたレスポンスを扱えます。lz4、zstd、gzip、deflate、br に対応していますが、snappy には対応していません。
デフォルトでは、ドライバーは zstd, lz4, gzip, deflate に対応していることをサーバーに伝え、ClickHouse は zstd で圧縮したレスポンスを返します。別の方式を選ぶには、Accept-Encoding を明示的に設定します。クライアント全体に設定する場合は次のとおりです。
ClickHouseClientSettingsを直接操作しないORMユーザーは、接続文字列で指定できます:
enable_http_compression=1 も強制的に有効になります。ClickHouse がヘッダーを認識するには、この指定が必要です。コーデックを明示的に指定すると圧縮を要求したと見なされるため、UseCompression が false の場合も同様です。値が未設定で UseCompression=false の場合は、Accept-Encoding は送信されません。
Accept-Encoding は次の 4 か所で設定できます。この順序で、最初にコーデックを指定した値が優先されます。
QueryOptions.AcceptEncoding(またはClickHouseCommand.AcceptEncoding)- クエリの
CustomHeaders["Accept-Encoding"] - クライアントの
CustomHeaders["Accept-Encoding"] ClickHouseClientSettings.AcceptEncoding、または接続文字列のキーワードAcceptEncoding
identity を使用します。
コーデックを選ぶのはクライアントではなくサーバーです。 ClickHouse は独自の固定された優先順位 (zstd > br > lz4 > snappy > gzip > deflate) に従って Accept-Encoding 内のトークンを確認し、記載順序と q 値はどちらも無視します。つまり、このヘッダーは特定のコーデックを要求するものではなく、対応可能なコーデックを通知するものです。選択を誘導するには、候補からトークンを除外するしかありません。デフォルトのリストには zstd が含まれるため、デフォルト設定のクエリへの応答には zstd が使用されます。残りのトークンは代替候補となります。br はデコードできますが、デフォルトでは通知されません。
ペイロードサイズ、サーバーとクライアントの CPU 使用量の面で各コーデックがどう異なるかは、データ、接続速度、サーバーの http_zlib_compression_level (初期設定値: 3) によって変わります。圧縮の調整を参照してください。
http_zlib_compression_level。 この設定はすべての HTTP コーデックに適用され、デフォルト値は 3 です。データ、接続速度、CPU 使用量に応じて調整してください。- 高速な接続を使用する CPU 処理がボトルネックのクライアント。 ドライバーは呼び出し元のスレッドでレスポンスボディをデコードするため、ネットワークがボトルネックでない場合は、クライアント側のデコード速度が制約になる可能性があります。
Content-Encoding の値に応じてボディがデコードされます。Content-Encoding がない場合や値が identity の場合はボディをそのまま渡し、サポートされているコーデックが指定されていればデコードします。それ以外の値では、その値を示すエラーが発生します。二重にデコードされることはありません。呼び出し元が指定したハンドラーの AutomaticDecompression がすでにボディをデコードしている場合は、Content-Encoding も削除されるため、ドライバーは平文のボディをそのまま扱います。
生の結果ではコーデックを要求しません。 ExecuteRawResultAsync (および公開メソッドの PostStreamAsync / InsertRawStreamAsync) はボディをそのまま渡します。そのため、明示的にコーデックを指定しない限り、コーデックは要求されません。ドライバーはこのボディをデコードしないため、ここでコーデックを要求すると、エクスポート結果が気づかないうちに圧縮ファイルになってしまいます。したがって、HttpClient の設定に関係なく、原則は単純です。ボディはサーバーが送信したとおりに届き、コーデックを要求しない限り、サーバーは平文を送信します。 意図的に圧縮されたバイト列をエクスポートするには、クライアント全体またはクエリごとにコーデックを指定します。
明示的に指定した AcceptEncoding は、クライアント全体とクエリ単位のどちらで指定した場合も生のリクエストに適用されます。結果をデコードしたい場合は、ClickHouseRawResult.ReadDecompressedStreamAsync() を使用できます。ReadAsStreamAsync、ReadAsByteArrayAsync、ReadAsStringAsync、CopyToAsync は、常に受信したバイト列をそのまま返します。
leaveOpen を指定して作成されたデコーダが返されるため、デコーダを破棄してもレスポンスは閉じられません。圧縮されていない場合は HTTP コンテンツストリーム自体が返されるため、ストリームを破棄するとボディも閉じられます。いずれの場合も、レスポンスを管理するのは ClickHouseRawResult です。ストリームを破棄した後は、ClickHouseRawResult のほかの読み取りメンバーを呼び出さないでください。ClickHouseRawResult は必ず破棄する必要があり、それだけでレスポンスと、ここで作成されたデコーダの両方が解放されます (デコーダはプールされたバッファを保持します) 。したがって、上記の await using は必須ではありませんが、そのまま残しても問題ありません。順番に繰り返し呼び出すと、同じストリームが返されます。この型を複数の処理から同時に使用することはできません。
実行可能な例については、Select_007_ResponseCompression.cs を参照してください。
Insert (リクエスト) の圧縮
挿入時のデフォルトのコーデックは Zstd です。InsertOptions.Compressor の初期値は ZstdCompressor.Default で、
圧縮レベル 3 の zstd を使用します。コーデックを変更するには別のコンプレッサーを指定し、ボディを非圧縮で送信するには
null を指定します。
Defaultインスタンスと、圧縮レベル
および書き込みバッファのサイズを指定できるコンストラクターがあります。
圧縮器のインスタンスは共有してください。 各
Default は共有インスタンスで、4 種類の圧縮器はいずれも
複数のスレッドから同時に安全に使用できます。InsertOptions.MaxDegreeOfParallelism が 1 を超えると、
1 回の挿入でバッチごとに 1 つの圧縮器を使用するため、このような同時利用が発生します。
いずれも IDisposable を実装していません。Default と同様に、
独自のインスタンスも一度だけ作成し、再利用してください。IClickHouseCompressor は公開されており、実装に必要なメンバーは次の 2 つだけです。
Content-Encoding を受け付ける必要があります。残りのメンバー —
Decompress、MethodByte、MaxEncodedLength、Encode、Decode — には、既定で
NotSupportedException をスローする実装が用意されているため、コーデックに必要なものだけをオーバーライドしてください。
リクエストの圧縮だけでなくレスポンスのボディもデコードできるよう、Decompress を実装してください。ボディが破損している場合やフォーマットが正しくない場合は、
返したストリームから InvalidDataException をスローするようにしてください。
InsertOptions.Compressor が適用されるのはバイナリ挿入だけです。ドライバーのほかのリクエストボディには別の圧縮規則が適用され、InsertOptions.Compressor は経由しません。
- SQL テキストを送信するすべてのリクエスト (
ExecuteReaderAsync、ExecuteScalarAsync、ExecuteNonQueryAsync、QueryAsync<T>、ExecuteRawResultAsync、ADO.NET レイヤー) は、UseCompressionがtrueの場合 (既定) 、ステートメントをContent-Encoding: gzip付きで送信します。このコーデックは変更できません。AcceptEncodingが制御するのはレスポンスだけなので、リクエストは gzip 圧縮するか、圧縮しないかの二択です。Compression=falseの場合、ステートメントは圧縮せずに送信されます。ステートメントは小さいため、通常は意識する必要はありませんが、プロキシやパケットキャプチャでリクエストを調べる際には覚えておくと役立ちます。 - マルチパートのボディ — パラメータをフォームデータとして送信するクエリ (
UseFormDataParameters=true) — は、UseCompressionの設定にかかわらず、常に非圧縮で送信されます。 - 生データのアップロード (
InsertRawStreamAsync、PostStreamAsync) では、呼び出しごとに指定する専用のフラグを使用し、UseCompressionもInsertOptions.Compressorも参照しません。フラグを設定すると gzip で圧縮され、設定しなければ非圧縮で送信されます。InsertRawStreamAsyncのuseCompressionパラメータは既定でtrueです。そのため、クライアントでCompression=falseを設定していても、falseを渡さない限り、生データのアップロードは gzip で圧縮されます。
圧縮の調整
圧縮では、CPUリソースを消費する代わりに転送データ量を減らします。メリットがあるかどうかは、コーデックの処理速度と 回線速度の関係にほぼ左右されます。すべての環境に適した設定は ありません。圧縮の損得を決める速度
コーデックによる圧縮がネットワーク転送より速ければ、圧縮する価値があります。 読み取り時は、圧縮が有利になる速度の境目が予想以上に低くなります。ClickHouse は 出力バッファ内で HTTP レスポンスをシングルスレッドで圧縮するためです。16 vCPU の ClickHouse Cloud サービス (hits、RowBinary、レベル 3) で測定したところ、サーバーが圧縮済みの出力を生成する速度は約 100~200 MB/s でした。
そのため、結果が大きく、一度に処理するクエリが 1 つであれば、転送速度が約 100 MB/s を超えたあたりで圧縮の利点がなくなります。同じクラウドリージョン内の単一の HTTPS ストリームは
この速度を超えることが珍しくありません。一方、パブリックインターネットや VPN を経由する通信、リージョンをまたぐ通信は通常、この速度を下回ります。
挿入時は、転送速度がさらに高くても圧縮が有効です。クライアントが専用の CPU コアで圧縮を行うため、通常はサーバーによるレスポンスの圧縮より高速です。
デプロイ環境別の大まかな目安
この表には反映されていない点が 3 つあります。
- エグレス料金: データ転送が従量課金の場合、転送量はレイテンシーだけでなく料金にも影響します。そのため、 回線速度にかかわらず、圧縮率を高めるメリットがあります。
- 小さな結果: 上記はすべて大きなペイロードを想定しています。小さなレスポンスではコーデックの違いはほとんど 影響せず、リクエストごとのオーバーヘッドが支配的になります。
- 並列挿入では、挿入時の帯域幅の目安が上がります。 上記のスループットは、いずれも単一スレッドでの値です。
InsertOptions.MaxDegreeOfParallelismのデフォルトは1ですが、この値を上げると複数のバッチが同時に圧縮されるため、クライアント全体のエンコード速度は割り当てたコア数にほぼ比例して向上します。したがって、高速な回線でも、単一スレッドでは圧縮のメリットがなくなる速度を大きく超える帯域幅まで、並列挿入なら圧縮する価値があります。表の挿入に関する行は下限の目安と考え、すでにバッチを並列処理している場合は、回線が速すぎて圧縮は不要だと判断する前に再テストしてください。
コーデックの選択
圧縮レベル
レスポンスの圧縮レベルは、サーバー設定http_zlib_compression_level で制御します。この設定は zlib だけでなく、すべての HTTP コーデックに適用されます。デフォルト値は 3 です。
測定結果から変更が必要だと判断できない限り、デフォルト値のままにしてください。値を上げても、CPU 使用量が大幅に増える割にデータサイズはほとんど減りません (zstd では 3 → 6 にすると、サーバーの CPU 使用量が約 2 倍になる一方、バイト数は約 14% しか減りません) 。br では CPU 使用量が極端に増えます。一方、デフォルト値より低いレベル 1 では状況が大きく変わります。lz4 の CPU 使用量が大幅に減り、zstd は lz4 に対する CPU 使用量の面での優位性を失います。必要であれば、クエリごとに設定してください。
環境に応じた損益分岐点を見極める
最適なコーデックと圧縮レベルを手早く見つけるには、複数のコーデックで同じクエリの実行時間を測定し、比較します。system.query_log の ProfileEvents を参照します。該当する行を特定できるよう、
QueryOptions.QueryId を設定してください。
ORDER BY を指定せずに LIMIT n だけを使うと、実行するたびに
返される行が変わるため、毎回異なるデータが圧縮され、圧縮率を正しく比較できません。必ず同じ結果セットを
使って比較してください。
生のストリームから挿入
InsertRawStreamAsync を使用すると、CSV、JSON、Parquet などのClickHouse がサポートするフォーマットで、ファイルストリームやメモリストリームからデータを直接挿入できます。
CSV ファイルから挿入する:
データのインジェスト動作を制御するオプションについては、フォーマット設定のドキュメントを参照してください。
その他の例
実践的な使用例をさらに確認するには、GitHub リポジトリ内のexamples ディレクトリを参照してください。ADO.NET
このライブラリは、ClickHouseConnection、ClickHouseCommand、ClickHouseDataReader を通じて ADO.NET を完全にサポートしています。ORM とのインテグレーション (Dapper、Linq2db) や、標準的な .NET のデータベース抽象化を利用する場合には、この API が必要です。
ClickHouseDataSource によるライフサイクル管理
適切なライフサイクル管理と接続プーリングのため、接続は必ずClickHouseDataSource から作成してください。DataSource は内部で単一の ClickHouseClient を管理しており、すべての接続でその HTTP 接続プールを共有します。
ClickHouseCommand の使用
接続を使用してコマンドを作成し、SQL を実行します。ExecuteNonQueryAsync()- INSERT、UPDATE、DELETE、DDL ステートメントの実行に使用ExecuteScalarAsync()- 最初の行の最初のカラムの値を返すExecuteReaderAsync()- 結果を順に読み取るためのClickHouseDataReaderを返す
ClickHouseDataReader の使用
ClickHouseDataReader を使用すると、クエリ結果に型を指定してアクセスできます。
enum の序数を取得する
Enum8 または Enum16 のカラムはラベルとしてマテリアライズされます。GetFieldType は string を返し、
GetString、GetValue、GetFieldValue<string> はいずれもラベルを返します。一方、数値用のアクセサは、
enum カラムの値が文字列として保持されているため、InvalidCastException をスローします。
ラベルに対応する数値を取得するには、TryGetEnumOrdinal を使用します。
Enum8/Enum16 カラム、およびセルが NULL ではない Nullable(Enum...)
カラムでは、true を返し、value に値を設定します。セルが NULL の場合や、enum ではない
カラムの場合は、value を 0 に設定して false を返します。ordinal は
wire 上の符号付きの値なので、負になることがあります。また、Enum16 の ordinal は
1 バイトで表せる範囲を超えることがあります。
ベストプラクティス
接続のライフタイムとプーリング
ClickHouse.Driver は内部で System.Net.Http.HttpClient を使用します。HttpClient はエンドポイントごとに接続プールを持つため、次のような動作になります。
- データベースのセッションは、接続プールが管理する HTTP 接続を介して多重化されます。
- HTTP 接続はプールによって自動的に入れ替えられます。
ClickHouseClientまたはClickHouseConnectionオブジェクトを破棄した後も、接続が維持される場合があります。
DateTime の扱い
-
可能な限り UTC を使用してください。 タイムスタンプは
DateTime('UTC')型のカラムに保存し、コードではDateTimeKind.Utcを使用してください。これにより、タイムゾーンの解釈が曖昧になるのを防げます。 -
タイムゾーンを明示的に扱う場合は
DateTimeOffsetを使用してください。DateTimeOffsetは常に特定の時点を表し、オフセット情報も保持します。 -
SQL の型ヒントでタイムゾーンを指定してください。
Unspecifiedの DateTime 値をパラメータとして使用し、UTC 以外のカラムに書き込む場合は、SQL 内でタイムゾーンを指定してください。
非同期 INSERT
非同期 INSERTでは、バッチ処理をクライアントではなくサーバーが担います。クライアント側でバッチ処理を行う必要はなく、サーバーが受信データをバッファに保持し、設定可能なしきい値に達するとストレージに書き出します。多数のエージェントが小さなペイロードを送信するオブザーバビリティのワークロードなど、同時実行数が多い環境で役立ちます。CustomSettingsまたは接続文字列を使って、非同期 INSERT を有効にします。
wait_for_async_insert で制御) :
主な設定:
セッション
セッションは、次のような状態を保持するサーバー側の機能が必要な場合にのみ有効にしてください。- 一時テーブル (
CREATE TEMPORARY TABLE) - 複数のステートメントにまたがるクエリコンテキストの維持
- セッション単位の設定 (
SET max_threads = 4)
サポートされているデータ型
ClickHouse.Driver は、すべての ClickHouse データ型をサポートしています。以下の表に、データベースからデータを読み込む際の ClickHouse データ型と .NET のネイティブ型の対応関係を示します。
型マッピング:ClickHouse からの読み取り
整数型
浮動小数点型
Decimal 型
Decimal 型の変換は UseCustomDecimals 設定で制御できます。
ブール型
String 型
デフォルトでは、
String と FixedString(N) のカラムはどちらも string として返されます。byte[] として読み取るには、接続文字列に ReadStringsAsByteArrays=true を設定します。これは、有効な UTF-8 ではないバイナリデータを保存している場合に役立ちます。この設定は他の型にネストされた文字列にも適用されます。そのため、Array(String) は byte[][] として、
Map(String, String) はキーも含めて Dictionary<byte[], byte[]> として読み取られます。ただし、
JSON カラムは例外で、末端の文字列値は常にテキストとして扱われます。詳しくは JSON 型 を参照してください。日付と時刻の型
ClickHouse は
DateTime と DateTime64 の値を、内部では Unixタイムスタンプ (エポックからの経過秒数または秒未満の単位) として保存します。保存には常に UTC が使用されますが、カラムにはタイムゾーンを設定でき、その設定が値の表示と解釈に影響します。
DateTime の値を読み取る際、DateTime.Kind プロパティはカラムのタイムゾーンに応じて設定されます。
UTC 以外のタイムゾーンが設定されたカラムでは、返される
DateTime はそのタイムゾーンの現地時刻を表します。そのタイムゾーンに対応する正しいオフセットを持つ DateTimeOffset を取得するには、ClickHouseDataReader.GetDateTimeOffset() を使用します。
DateTime('Europe/Amsterdam') ではなく DateTime) の場合、ドライバーは Kind=Unspecified の DateTime を返します。これにより、タイムゾーンを推定することなく、保存された時計上の時刻をそのまま保持します。
明示的なタイムゾーンがないカラムでタイムゾーン対応の動作が必要な場合は、次のいずれかを行ってください。
- カラム定義でタイムゾーンを明示する:
DateTime('UTC')またはDateTime('Europe/Amsterdam') - 読み取り後に自分でタイムゾーンを適用する。
JSON 型
JSON カラムの戻り値の型は、
JsonReadMode 設定によって決まります。
-
Binary(デフォルト) :System.Text.Json.Nodes.JsonObjectを返します。JSON データの構造に沿ってアクセスできますが、IP アドレス、UUID、桁数の多い Decimal 型など、ClickHouse 固有の型は JSON 構造内で文字列表現に変換されます。 -
String: JSON を加工せずにstringとして返します。ClickHouse から取得した JSON 表現をそのまま保持するため、パースせずに JSON を受け渡したい場合や、デシリアライズを自分で行いたい場合に便利です。
None は3つ目のモードです。Binary とまったく同じように読み取りますが、クエリとともにサーバー設定を送信しません。
サーバー設定の指定が許可されていない接続で使用してください。
カラムの型で宣言されたパスは型付きパス、ドキュメント内のそれ以外のパスは
動的パスです。値がnullの場合、両者の扱いは異なります。
型付きパスは常にJsonObjectに含まれます。Nullable(T)またはDynamicとして宣言されている場合、
保存された値がnullでも、ドキュメント内にそのパスがなくても、JSONのnullとして返されます。この2つの
ケースは区別できません。
JSON(x String) では
{"x":""}、JSON(x Int64) では {"x":0} になります。
値が null の動的パスはオブジェクトから完全に除外されるため、そのパスに対して ContainsKey は
false を返します。通常の JSON カラムから {"x":null} を読み取ると {} になります。
ネストした型付きパスでは親パスも作成されるため、JSON(a.b Nullable(Int64)) は
空のドキュメントでも {"a":{"b":null}} になります。
これはサーバー自体が出力する結果なので、現在は
Binary モードと String モードで結果が一致します。1.4.0 より前は、
値が null の型付きパスが JsonObject から除外され、{"x":null} を読み取ると
{} になっていました。また、JSON(a.b Nullable(Int64)) のようなネストしたパスでは、a 以下のサブツリー全体が消えていました。JSON カラム内の文字列型の末端値は、ReadStringsAsByteArrays の設定にかかわらず、
常にテキストとして返されます。JsonValue にはバイト配列形式がないため、byte[] にすると
base64 形式で表現されます。これは String、FixedString、およびこれらを
LowCardinality、Nullable、SimpleAggregateFunction でラップした型のほか、Array や Map 内の文字列にも当てはまり、
Map のキーも例外ではありません。
JSON リーダーが型を判別できないバイト配列は、引き続き base64 形式で表現されます。
Variant または Dynamic の型付きパスでは、値の型は行ごとにしか分からないため、
Variant(Array(UInt8), String) 内の文字列は base64 エンコードされて返されます。どちらの設定でも同じです。JSON の Map のキーの型が String そのものではない場合、たとえば Map(LowCardinality(String), String) では、
NotSupportedException がスローされます。JSON(a Int64, a.b Int64) です。どちらのパスもすべての行に存在するため、サーバーは
重複するキーを含む行を出力します: {"a":0,"a":{"b":7}}。JsonObject は 1 つのキーに 2 つの値を
保持できないため、JsonReadMode.Binary は両方のパスを示す SerializationException をスローします。
値が Map の場合も同様です。たとえば JSON(a Map(String, Int64)) で、読み取る行に
動的な a.b も存在する場合です。
これは、その行で両方のパスに値がある場合にのみ当てはまります。片方に値がない場合、つまり null、
空のオブジェクト、またはすべての値が null の部分木である場合は、サーバーがどちらのパスを先に送信するかに関係なく、
値のある側が採用されます。したがって、Nullable 型で宣言された重なり合うパスでは、行ごとに値のある側が反映され、
エラーなく読み取れます。JSON(a Nullable(Int64), a.b Nullable(Int64)) は、想定どおり
{"a":5} と {"a":{"b":7}} を返します。
このようなカラムを JsonReadMode.String で読み取ると、重複するキーを含め、サーバーの JSON テキストを
そのまま取得できます。
例外をスローせずにカラムを JsonObject として読み取り続けるには、AllowDuplicateJsonKeys を設定します。
この場合、ドライバーは行内で後に現れた値を保持し、もう一方を破棄するため、
情報が失われます。{"a.b":7} を保持する JSON(a Int64, a.b Int64) は {"a":0} として読み取られます。値を持つパスの
親がスカラーまたは配列を保持している場合は、その下に部分木を配置できないため、引き続き例外がスローされます。
Map 型
ClickHouse の
Map(K, V) は物理的には Array(Tuple(K, V)) で、同じキーを持つエントリを複数格納できます。一方、Dictionary では同じキーを複数保持できません。そのため、デフォルトモードでキーが重複した場合は最後の値だけが残り、それ以前のペアは破棄されます。表現形式は MapReadMode 設定で選択します。
-
Dictionary(デフォルト):Dictionary<K, V>を返します。 -
KeyValuePairs: サーバーが送信した順序でList<KeyValuePair<K, V>>を返します。同じキーを持つエントリも含め、すべてのペアが保持されます。
Map カラムのフレームワーク上の型が決まります。そのため、GetFieldValue<T>、ドライバーが報告するスキーマの型、POCO のプロパティへのマッピングにも影響します。カラムの型構造内で Map が現れる箇所すべてに適用され、Array(Map(...))、Map(K, Map(...))、Tuple(..., Map(...))、Dynamic も対象です。
どちらのモードでも、書き込み時には両方の表現を使用できます。詳しくは Map の書き込み を参照してください。
その他の型
Dynamic 型と Variant 型は、各行で実際に格納されている値の型に対応する型に変換されます。
ジオメトリ型
Geometry 型は、どのジオメトリ型の値でも保持できる Variant 型です。読み取り時には、対応する型に変換されます。
型マッピング: ClickHouse への書き込み
データの挿入時、ドライバーは .NET の型を対応する ClickHouse の型に変換します。以下の表に、ClickHouse の各カラム型で使用できる .NET の型を示します。整数型
浮動小数点型
ブール値型
String 型
日付と時刻の型
範囲外の値バイナリ書き込み時に、
Date、Date32、DateTime、DateTime32 の値がサポート範囲外の場合、Write の時点で、カラムの型とサポート範囲を示す ArgumentOutOfRangeException がスローされます。以前は、範囲外の値が 32 ビット整数への変換時に警告なく切り詰められ、サーバー側で再解釈された結果、有効ではあるものの誤ったタイムスタンプになることがありました。DateTime.Kind を考慮します。
DateTimeOffset の値は常に同じ時点を維持します。
例:UTC の DateTime (同じ時点を維持)
DateTimeKind.Utc または DateTimeOffset を使用してください。これにより、サーバー、クライアント、カラムのタイムゾーンに左右されず、コードが一貫して動作します。
HTTP パラメータとバルクコピーの違い
Unspecified の DateTime 値を書き込む場合、HTTP パラメータのバインディングとバルクコピーでは動作が大きく異なります。
バルクコピーでは書き込み先カラムのタイムゾーンが分かるため、Unspecified の値をそのタイムゾーンで正しく解釈できます。
HTTP パラメータではカラムのタイムゾーンを自動的に判別できません。SQL 型ヒントでタイムゾーンを指定してください:
Decimal 型
JSON 型
JSON 書き込み時の動作は、
JsonWriteMode の設定で制御します。
-
String(デフォルト) :string、JsonObject、JsonNode、任意のオブジェクトを受け入れます。すべての入力はSystem.Text.Json.JsonSerializerでシリアル化され、サーバー側でのパース用に JSON 文字列として送信されます。最も柔軟なモードで、型を登録せずに使用できます。 -
Binary: 登録済みの POCO 型のみを受け入れます。データはクライアント側で ClickHouse のバイナリ JSON フォーマットに変換され、型ヒントを完全にサポートします。使用前にconnection.RegisterJsonSerializationType<T>()を呼び出す必要があります。このモードでstringまたはJsonNodeの値を書き込むと、ArgumentExceptionがスローされます。
JSON(id UInt64, price Decimal128(2))) がある場合、ドライバーはそのヒントを使い、型情報を完全に保持したまま値をシリアル化します。これにより、汎用 JSON としてシリアル化すると精度が失われる UInt64、Decimal、UUID、DateTime64 などの型でも、精度を維持できます。
POCO を JSON カラムに書き込む方法は、JsonWriteMode によって次の 2 つに分かれます。
String mode (デフォルト) : POCO は System.Text.Json.JsonSerializer を使用してシリアル化されます。型の登録は不要です。最も簡単な方法で、匿名オブジェクトにも使用できます。
Binary mode: POCO は、型ヒントを全面的にサポートするドライバーのバイナリ JSON フォーマットを使用してシリアル化されます。使用前に connection.RegisterJsonSerializationType<T>() で型を登録する必要があります。このモードでは、属性を使ってカスタムのパスマッピングを指定できます。
-
[ClickHouseJsonPath("path")]: プロパティをカスタムの JSON パスに対応付けます。ネストした構造を扱う場合や、プロパティ名と使用したい JSON キーが異なる場合に便利です。Binary mode でのみ有効です。 -
[ClickHouseJsonIgnore]: プロパティをシリアライゼーションの対象から除外します。Binary mode でのみ有効です。
UserId は、UserId として定義された型ヒントにのみ一致し、userid には一致しません。これは、userName と UserName のようなパスを別々のフィールドとして共存させられる ClickHouse の動作と同じです。
制限事項 (Binary mode のみ) :
- シリアル化する前に、
connection.RegisterJsonSerializationType<T>()を使用して POCO 型を接続に登録する必要があります。未登録の型をシリアル化しようとすると、ClickHouseJsonSerializationExceptionがスローされます。 - Dictionary および配列/リストのプロパティを正しくシリアル化するには、カラム定義に型ヒントが必要です。型ヒントがない場合は、代わりに String mode を使用してください。
- POCO プロパティの null 値が書き込まれるのは、カラム定義でそのパスに
Nullable(T)型ヒントが指定されている場合だけです。ClickHouse では動的な JSON パス内にNullable型を使用できないため、型ヒントのない null 値のプロパティはスキップされます。 ClickHouseJsonPath属性とClickHouseJsonIgnore属性は String mode では無視されます (Binary mode でのみ機能します) 。
その他の型
ジオメトリ型
書き込みには非対応
Nested 型の扱い
ClickHouse の Nested 型 (Nested(...)) は、配列と同じように読み書きできます。
ロギングと診断
ClickHouse .NET クライアントはMicrosoft.Extensions.Logging の抽象化を利用し、必要な場合にのみ有効にできる軽量なロギング機能を提供します。有効にすると、ドライバーは接続のライフサイクルに関するイベント、コマンドの実行、通信処理、一括挿入操作について、構造化されたメッセージを出力します。ロギングの設定は任意で、ロガーを設定しないアプリケーションも追加の処理負荷なく動作します。
クイックスタート
appsettings.json を使用する
標準的な .NET の設定を使って、ログレベルを指定できます。メモリ上の設定を使用する
コード内でカテゴリごとにログ出力の詳細度を設定することもできます:カテゴリとログ出力元
ドライバーには専用のカテゴリが用意されており、コンポーネントごとにログレベルを細かく調整できます。例:接続の問題を診断する
- HTTP クライアントファクトリの選択 (デフォルトのプールか単一の接続か)
- HTTP ハンドラーの設定 (SocketsHttpHandler または HttpClientHandler)
- 接続プールの設定 (MaxConnectionsPerServer、PooledConnectionLifetime など)
- タイムアウトの設定 (ConnectTimeout、Expect100ContinueTimeout など)
- SSL/TLS の設定
- 接続の確立・切断イベント
- セッション ID の追跡
デバッグモード: ネットワークトレースと診断
ネットワークの問題を診断できるよう、ドライバーライブラリには、.NET のネットワーク内部処理を低レベルでトレースするためのヘルパーが用意されています。有効にするには、ログレベルを Trace に設定した LoggerFactory を渡し、EnableDebugMode を true に設定します (または、ClickHouse.Driver.Diagnostic.TraceHelper クラスを使用して手動で有効にします) 。イベントは ClickHouse.Driver.NetTrace カテゴリに記録されます。警告: 大量の詳細なログが生成され、パフォーマンスに影響します。本番環境でデバッグモードを有効にすることは推奨しません。
OpenTelemetry
ドライバーは、.NET のSystem.Diagnostics.Activity API を使用した OpenTelemetry の分散トレーシングを標準でサポートしています。有効にすると、ドライバーはデータベース操作のスパンを生成します。生成されたスパンは、Jaeger や ClickHouse 自体 (OpenTelemetry Collector 経由) などのオブザーバビリティ基盤にエクスポートできます。
トレーシングの有効化
ASP.NET Core アプリケーションでトレーシングを有効にするには、ClickHouse ドライバーのActivitySource を OpenTelemetry の設定に追加します:
スパンの属性
各スパンには、OpenTelemetry の標準的なデータベース属性と、デバッグに役立つ ClickHouse 固有のクエリ統計が含まれます。設定オプション
ClickHouseDiagnosticsOptions でトレースの動作を設定します。
TLS 設定
HTTPS で ClickHouse に接続する際は、TLS/SSL の動作を複数の方法で設定できます。カスタム証明書検証
本番環境で独自の証明書検証ロジックが必要な場合は、ServerCertificateCustomValidationCallback ハンドラーを設定した HttpClient を用意してください。
カスタム HttpClient を指定する際の重要な注意事項
- 自動展開:
AutomaticDecompressionは無効のままにしてください。圧縮されたレスポンスはドライバー自身が展開するため、必要ありません。有効にすると、リクエスト側で問題が生じます。送信時にハンドラーがマスク内のすべてのアルゴリズムを送信するAccept-Encodingに追加するため、ドライバーが指定した範囲が広がり、ClickHouse が要求していないコーデックで応答する可能性があります。レスポンスの展開を参照してください。 - アイドルタイムアウト: 半開状態の接続によるエラーを避けるため、
PooledConnectionIdleTimeoutをサーバーのkeep_alive_timeout(ClickHouse Cloud では 10 秒) より短く設定してください。
パフォーマンスチューニング
このセクションでは、クライアントのパフォーマンスを最大限に引き出す方法と、用途に応じて調整できる各種オプションについて説明します。ひと目でわかる設定の目安
| 操作 | 推奨事項 | |---|---|---| | 行をPOCOに読み込む |MapTo<T>ではなくQueryAsync<T>を使用する |
| 大量の行を挿入する | InsertOptions.BatchSizeを大きくする |
| 挿入処理が多いコンソールアプリやワーカーアプリを実行する | Server GCを有効にする |
| ネットワーク経由で大量の結果を読み込む | 応答の圧縮を有効にしておく (デフォルトで有効) |
| 高速な接続を介して挿入する | InsertOptions.Compressor = nullを試す |
| 同じテーブルに何度も挿入する | UseSchemaCacheまたはColumnTypesを使用する |
| 非常に大量の結果を読み込む | ReadBufferSizeを大きくする |
読み取り: マテリアライズの方法を選ぶ
結果から行を取得する方法は3つあり、それぞれ処理コストが異なります。方法によっては結果がボックス化され、メモリの割り当てが増えてパフォーマンスが低下します。
hits データセットの105カラムから1,000,000行を読み取った場合:
ORM は型付きアクセサを使うことで高速処理経路を利用できます。 linq2db は各カラムに
GetInt64、
GetDouble、GetDateTime を登録するため、ボックス化せずに値を読み取れます。
GetValue で値を読み取るコード (Dapper の dynamic な結果を含む) では、値ごとにボックス化が発生します。頻繁に実行される ORM のクエリが
GetValue で値を読み取っている場合は、そのクエリに QueryAsync<T> を使用してください。挿入: バッチサイズと並列度
バッチサイズは、挿入のスループットを左右する最大の要因です。InsertOptions.BatchSize のデフォルトは
100,000 行です。
大きなバッチを使用してください。 1,000,000 行を挿入する際、バッチあたりの行数を 10,000 行から 100,000 行に
増やすと、結果は次のようになりました。
バッチサイズを制御できない場合 (多数の小規模な送信元が個別に行を送信する場合など) は、非同期 INSERT を使用し、サーバー側でバッチ化してください。
並列アップロード。
InsertOptions.MaxDegreeOfParallelism のデフォルトは 1 です。値を増やすと、
複数のバッチを同時に送信できます。圧縮が有効な場合は各バッチがそれぞれ別のスレッドで圧縮されるため、
特に効果的です。並列 INSERT と session は併用できません。session を無効にするか、
MaxDegreeOfParallelism = 1 のままにしてください。
スキーマ確認クエリを省略する。 InsertBinaryAsync は呼び出しのたびに、カラム型を調べるため、
最初に SELECT ... WHERE 1=0 クエリを送信します。ColumnTypes または UseSchemaCache を使って
この往復通信を省く方法は、スキーマ確認クエリの省略を参照してください。
ボックス化せずに挿入する処理経路は、デフォルトの
RowBinary フォーマットに適用されます。RowBinaryWithDefaults では
各値を調べて DBDefault マーカーを見つける必要があるため、低速な処理経路が使われます。圧縮:転送方向によって効果が異なる
圧縮は、CPU リソースを使って転送データ量を減らします。圧縮が有利かどうかは、 転送方向、ClickHouseサーバーへの接続帯域幅、データと選択した圧縮アルゴリズムの相性、転送量に応じた課金の有無によって決まります。 読み取り: サーバーが同じマシン上で稼働している場合を除き、圧縮は有効のままにしてください。デフォルトでも有効です。圧縮なしの場合と比較すると、レベル 1 のzstd では
次の結果になりました。
挿入: 圧縮を有効にする前に効果を測定してください。得られる効果が、有効にするだけの価値に見合わない場合があります。また、解凍によってサーバーの負荷が増える点にも注意してください。Zstd と LZ4 では負荷は小さいものの、ほかのアルゴリズム (Brotli など) では大きくなる場合があります。
挿入時の圧縮を無効にするには、次のようにします。
バッファ
ReadBufferSize は、HTTP レスポンスの読み取りに使用するバッファのサイズを設定します。デフォルトは 64 KiB です。
ドライバーはこのバッファを共有プールから借り、リーダーを破棄する際に返却するため、クエリごとに
新たな割り当ては発生しません。大きな結果を読み取る際にバッファを補充する回数を減らすには、
この値を大きくしてください。ドライバーは同時に開いているリーダーごとに 1 つのバッファを保持するため、メモリ使用量は
バッファサイズと同時に開いているリーダーの数に応じて増加します。
ランタイムと GC
INSERT 処理が多いアプリケーションでは Server GC を有効にしてください。 同じコードで割り当てバイト数も 同じ場合、Workstation GC での挿入処理は Server GC に比べて最大 97% 低速でした。Server GC はスループットを重視する設定であり、レイテンシーを重視する設定ではありません。同じ測定では、Server GC の
停止時間の合計は半分未満だった一方、個々の停止時間は長くなりました
(95 パーセンタイルで 114.6 ミリ秒、Workstation GC では 61.9 ミリ秒) 。サービスでテールレイテンシーが重要な場合は、
選択する前に両方のモードを測定してください。
レイテンシーの低減: 接続の再利用
新しいTCP接続の確立とTLSハンドシェイクには、かなりの時間がかかります。 接続を再利用することで、クエリのレイテンシーを大幅に低減できます。- リクエストごとにクライアントを作成しないでください。独自の
HttpClientを持つクライアントを新たに作成するたびに、新しい 接続プールが作成され、ハンドシェイクもやり直すことになります。アプリケーションの稼働中は、同じClickHouseClientを使い続けてください。ClickHouseClientはスレッドセーフで、 シングルトンとしての使用を想定して設計されています。 - ADO.NETやORMでは、すべての接続で同じプールを共有できるよう、
ClickHouseDataSourceを使用してください。
実際の環境で測定する
パフォーマンスは、データの構造、サーバーへの接続速度、 クライアントとサーバーのどちらで CPU 負荷を処理するか、ハードウェアの制約などによって変わります。 そのため、実際のデータと環境でパフォーマンスを測定することをお勧めします。 サーバー側で行われた処理を確認するには、QueryOptions.QueryId を設定し、カウンターの値を取得します。
ORM のサポート
ORM では ADO.NET API (ClickHouseConnection) を使用する必要があります。接続のライフタイムを適切に管理するため、ClickHouseDataSource から接続を作成してください。
Dapper
ClickHouse.Driver は Dapper に対応しています。ドライバーは Dapper の @parameter 構文を ClickHouse ネイティブの {parameter:Type} 構文に自動変換します。その際、.NET の値から型を推論します。
接続のライフタイム管理を適切に行うには、ClickHouseDataSource を使用してください:
パラメータの渡し方
Dapper で標準的に使われるパラメータの渡し方は、すべてサポートされています。 匿名オブジェクト:DynamicParameters (辞書または匿名オブジェクトから作成) :
POCO へのクエリ結果の格納
Dapper はカラム名とプロパティ名を照合し (大文字と小文字は区別しません) 、カラムをプロパティにマッピングします。ClickHouseネイティブのパラメータ構文
型を明示的に指定する必要がある場合は、パラメータ値をDictionary<string, object> で渡し、SQL内でClickHouseの {param:Type} 構文を直接使用します。同じパラメータに @param 構文と {param:Type} 構文を併用しないでください。
WHERE IN
DapperのネイティブなIN句の展開を利用できます。WHERE id IN (@Ids1, @Ids2, @Ids3) に書き換え、ドライバーが展開された各パラメータを変換します。
ClickHouse’s has() は Array パラメータでも使用できます:
カスタム型ハンドラー
ITuple、BigInteger、ClickHouseDecimal などの一部の ClickHouse 型では、起動時にハンドラーを登録する必要があります。
Dapper.Contrib
GetAll<T>() と Get<T>(id) は動作します。一方、Insert<T>() は SQL Server 向けの構文 (SCOPE_IDENTITY、[]) を生成するため、動作しません。代わりに、ClickHouseClient がネイティブで提供する InsertBinaryAsync メソッドの使用を推奨します。
制限事項
Linq2db
このドライバーは、.NET 向けの軽量な ORM および LINQ プロバイダーである linq2db に対応しています。詳しくはプロジェクトのウェブサイトにあるドキュメントを参照してください。 使用例: ClickHouse プロバイダーを使用してDataConnection を作成します。
BulkCopyAsync を使用します。
Entity Framework Core
ClickHouse 向けの公式 Entity Framework Core プロバイダーです。使い慣れた EF Core の流儀で、C# のクラスを ClickHouse のテーブルに対応付け、LINQ でクエリを実行し、SaveChanges でデータを挿入できます。
- NuGet:
ClickHouse.EntityFrameworkCore - ソースコード: GitHub
このプロバイダーは現在も活発に開発されています。現行リリースでは、LINQ クエリ (結合、サブクエリ、集合演算を含む) 、
SaveChanges / BulkInsertAsync による INSERT、DDL (CREATE / ALTER / DROP) に完全対応した移行、ClickHouse 固有のテーブルエンジン設定をサポートしています。UPDATE / DELETE はサポートされていません。インストール
クイックスタート
エンティティとDbContext を定義してから、LINQ でクエリを実行します。
サポートされる型
Decimal128/Decimal256 カラムの元の精度を維持する必要がある場合は、decimal ではなく ClickHouseDecimal (ClickHouse.Driver.Numerics に含まれる) を使用してください。.NET の decimal で扱える有効桁数は 28~29 桁です。
サポートされる LINQ 操作
クエリ:Where、OrderBy、Take、Skip、Select、First、Single、Any、All、Count、Distinct、AsNoTracking
GROUP BY と集約: Count、LongCount、Sum、Average、Min、Max を伴う GroupBy。HAVING (.GroupBy() の後の .Where()) 、単一の射影内での複数の集約、集約結果に対する OrderBy にも対応しています。
JOIN: Join (INNER) 、GroupJoin/SelectMany パターン (LEFT および CROSS) 。LEFT JOIN では、一致する行がない場合、実際の null が返されます (後述の LEFT JOIN の NULL セマンティクス を参照) 。
サブクエリ: 相関サブクエリでの Contains / IN、Any / EXISTS、All、および射影内のスカラーサブクエリ。
集合演算: Concat (→ UNION ALL) 、Union (→ UNION DISTINCT) 、Intersect、Except。
インラインのローカルコレクション: インメモリコレクション (int[]、List<T> など) との結合や、それに対する Contains は、一連の UNION に変換されます。
文字列メソッド: Contains、StartsWith、EndsWith、IndexOf、Replace、Substring、Trim/TrimStart/TrimEnd、ToLower、ToUpper、Length、IsNullOrEmpty、Concat (および + 演算子) 。
数学関数: 標準の Math および MathF のメソッドは、算術、対数、三角関数、ユーティリティ関数に相当する ClickHouse の関数に変換されます。
プロバイダーは、JOIN の動作を Entity Framework の想定に合わせるため、すべての接続パスに set_join_use_nulls=1 を自動的に追加します。
ClickHouseサーバーやプロファイルでこの設定の変更が禁止されている場合 (例: readonly=1 のプロファイル) は、次の方法で自動追加を無効にしてください。
== null の代わりに、0 / "" と明示的に比較してください。
データの挿入
SaveChanges は、ドライバーが提供するネイティブの InsertBinaryAsync API を使用します。RowBinary エンコーディングを採用し、リクエストボディを圧縮するため、パラメータ化された SQL よりもはるかに効率的です。
Added から Unchanged に変わります。
バッチサイズは設定できます (既定値は 1000) 。
一括挿入
高スループットでデータを投入する場合は、SaveChanges ではなく BulkInsertAsync を使用してください。これは DbContext の拡張メソッドで、EF Core の変更追跡、アイデンティティ解決、状態管理を一切介さず、RowBinary エンコーディングと圧縮されたリクエストボディを使用して、ドライバーの InsertBinaryAsync を直接呼び出します。
そのため、挿入後にエンティティを追跡する必要がない大規模なデータセットの読み込みに適しています。
IEnumerable<T> を指定できます。エンティティをすべてメモリに読み込まず、順次処理します。戻り値は挿入された行数です。挿入後もエンティティは DbContext にアタッチされないため、Added → Unchanged の状態遷移は発生しません。
列挙型
ClickHouse のEnum8/Enum16 カラムは、string プロパティまたは C# の enum 型にマッピングできます。C# の列挙型を使用すると、プロバイダーが列挙型とその文字列表現を自動的に相互変換します。
カスタム型変換
EF Core’のValueConverter を使うと、カスタム型をプロバイダーが対応する型に変換できます。カスタム型はプロバイダーに渡される前に EF Core によって変換されるため、プロバイダーがカスタム型を直接扱うことはありません。
プロパティごとの変換:
カラム型の注釈
string、int、DateTime などのスカラー型については、プロバイダーが ClickHouse の型を自動的に推論します。パラメータ化された型やラッパーについては、ClickHouse の型を明示的に指定する必要があります。
データ注釈 (属性) を使用する場合:
OnModelCreating で fluent API を使用する場合:
Array(Nullable(Int32)) や LowCardinality(Nullable(String)) のようにラッパーを入れ子にした型にも対応しています。プロバイダーは、入れ子の各階層で Nullable と LowCardinality を自動的にアンラップします。
Variant カラムと Dynamic カラム
ClickHouse のVariant(T1, T2, ...) カラムと Dynamic カラムは、.NET では object にマッピングされます。object は汎用的すぎて型を自動推論できないため、.HasColumnType() でストア型を明示的に指定する必要があります。
string、ulong、ulong[]) の値に自動的にデシリアライズされます。
JSON カラム
このプロバイダーは ClickHouse のJson カラム型に対応しており、主に System.Text.Json.Nodes.JsonNode にマッピングします。自動 ValueConverter を介して string にマッピングすることもできます。
SaveChanges と BulkInsertAsync のどちらでも行えます。
Json カラム型の string としてマッピングしてください。プロバイダーが ValueConverter を自動的に適用します。
- JSON パス変換には非対応 — LINQ の
entity.Data["name"]は、ClickHouse’sdata.nameという SQL 構文には変換されません。JSON 以外のカラムでフィルタし、JSON はメモリ上で確認してください。 - NULL の扱い — ClickHouse の JSON 型は、NULL 値に対して SQL NULL ではなく
{}(空のオブジェクト) を返します。 - 整数の精度 — ClickHouse の JSON はすべての整数を
Int64として格納します。JsonNodeで読み取る際は、GetValue<int>()ではなくGetValue<long>()を使用してください。
テーブルエンジン
ToTable(name, t => ...) のフルーエント API を使用して、ClickHouse のテーブルエンジンとエンジン固有の句を設定します。エンジンを設定しない場合、プロバイダーはデフォルトで MergeTree を使用し、エンティティの主キーに基づいて ORDER BY を設定します。
エンジン句:
WithOrderBy、WithPartitionBy、WithPrimaryKey、WithSampleBy、WithTtl、WithSettings。いずれも HasXxxEngine() が返すエンジンビルダーに指定します。
カラム単位の機能: HasCodec、HasTtl、HasComment、HasDefault — いずれも移行に反映されます。
データスキッピングインデックス — HasIndex(...).HasSkippingIndexType(...) で指定:
移行
EF Core の標準的な移行ワークフロー:移行に関する制限
移行以外にも、プロバイダーは以下に対応していません。
UPDATE/DELETE- トランザクション:
BeginTransactionを呼び出しても何も起こりません。ClickHouse では ACID トランザクションはサポートされていません。 - JSON パスクエリの変換: LINQ の
entity.Data["key"]は、ClickHouse のdata.keyという SQL 構文に変換されません。JSON 以外のカラムで絞り込み、JSON はメモリ上で確認してください。
制限事項
要素数が 8 以上で、最後の要素が入れ子のタプルであるタプル
要素数が 7 を超える C# のValueTuple 型では、コンパイラーが生成する入れ子構造が使われます。8 番目のジェネリック引数 (TRest) 自体が、残りの要素を保持する ValueTuple です。たとえば、(int, int, int, int, int, int, int, string, string) は ValueTuple<int, int, int, int, int, int, int, ValueTuple<string, string>> にコンパイルされます。
このため、ClickHouse のカラムが、最後の要素自体もタプルである 8 要素のタプル (例: Tuple(Int32, Int32, Int32, Int32, Int32, Int32, Int32, Tuple(String, String))) の場合、次の 2 つをドライバーが区別できません。
- 9 要素のフラットなタプル (コンパイラーが生成した TRest の入れ子構造)
- 最後の要素が入れ子の
Tuple(String, String)である 8 要素のタプル
ValueTuple<int, int, int, int, int, int, int, ValueTuple<string, string>> になります。
ドライバーは 8 番目の引数を TRest として扱い、その中身をフラット化します。そのため、最後の要素が入れ子のタプルである 8 要素のタプルは正しくシリアル化されません。
System.Tuple と ValueTuple はどちらも要素数が 7 を超える場合に TRest の入れ子構造を使うため、この問題は両方に影響します。要素数が 7 以下のタプルや、最後の要素自体がタプルではないタプルには影響しません。
回避策: ドライバーが TRest の入れ子構造と区別できるよう、内側のタプルをもう 1 層で包みます。
AggregateFunction カラム
AggregateFunction(...) 型のカラムは、直接クエリしたり、データを挿入したりすることはできません。
データを挿入するには: