クエリキャッシュ
クエリキャッシュを使用すると、SELECT
クエリを一度だけ計算し、同じクエリのさらなる実行をキャッシュから直接提供することができます。クエリの種類によっては、これにより ClickHouse サーバーのレイテンシとリソース消費を大幅に削減することができます。
背景、設計と制限
クエリキャッシュは一般的に、トランザクション整合性を持つか持たないかで見ることができます。
- トランザクション整合性のあるキャッシュでは、
SELECT
クエリの結果が変更された場合、または変更される可能性がある場合に、データベースはキャッシュされたクエリ結果を無効に(破棄)します。ClickHouse では、データを変更する操作には、テーブルへの挿入/更新/削除や、単一のマージが含まれます。トランザクション整合性のあるキャッシングは、特に OLTP データベースに適しています。たとえば、MySQL(v8.0以降はクエリキャッシュを削除しました)や Oracle です。 - トランザクション整合性のないキャッシュでは、クエリ結果にわずかな不正確さが許容され、すべてのキャッシュエントリには有効期間が割り当てられ、その後に有効期限が切れます(例:1分)。この期間中、基礎データはあまり変化しないと想定しています。このアプローチは、全体的に OLAP データベースにより適しています。トランザクション整合性のないキャッシングが十分である例としては、複数のユーザーが同時にアクセスするレポートツールの時間ごとの販売レポートがあります。販売データは通常、データベースがレポートを一度だけ計算する必要があるほど十分に遅く変化します(最初の
SELECT
クエリで表される)。その後のクエリは、クエリキャッシュから直接提供されます。この例では、妥当な有効期間は30分かもしれません。
トランザクション整合性のないキャッシングは、通常、データベースと対話するクライアントツールやプロキシパッケージ(例:chproxy)によって提供されます。その結果、同じキャッシングロジックと設定がしばしばデュプリケートされます。ClickHouse のクエリキャッシュでは、キャッシングロジックがサーバー側に移動します。これにより、メンテナンスの手間が減り、冗長性が回避されます。
設定と使用法
clickhouse-local は、一度に単一のクエリを実行します。クエリ結果のキャッシングは意味を成さないため、clickhouse-local ではクエリ結果キャッシュが無効になっています。
設定 use_query_cache を使用すると、特定のクエリまたは現在のセッションのすべてのクエリがクエリキャッシュを利用するかどうかを制御できます。たとえば、次のクエリの最初の実行
は、クエリ結果をクエリキャッシュに保存します。同じクエリのその後の実行(パラメータ use_query_cache = true
でも)では、キャッシュから計算された結果を直接読み込んで即座に返します。
設定 use_query_cache
および他のすべてのクエリキャッシュ関連設定は、スタンドアロンの SELECT
文に対してのみ影響を与えます。特に、CREATE VIEW AS SELECT [...] SETTINGS use_query_cache = true
で作成されたビューへの SELECT
の結果は、SETTINGS use_query_cache = true
で実行されない限りキャッシュされません。
キャッシュの利用方法は、設定 enable_writes_to_query_cache および enable_reads_from_query_cache を使用して、より詳細に構成できます(両方ともデフォルトでは true
です)。前者の設定はクエリ結果がキャッシュに保存されるかどうかを制御し、後者の設定はデータベースがクエリ結果をキャッシュから取得しようとするかどうかを決定します。たとえば、次のクエリはキャッシュを受動的に使用します、つまり、キャッシュから読み取ろうとしますがその結果を保存しません:
最大限の制御を得るために、一般的に、設定 use_query_cache
、enable_writes_to_query_cache
および enable_reads_from_query_cache
を特定のクエリのみに提供することが推奨されます。また、ユーザーやプロファイルレベルでキャッシングを有効にすることも可能ですが(例:SET use_query_cache = true
経由)、その場合、すべての SELECT
クエリがキャッシュされた結果を返す可能性があることに注意する必要があります。
クエリキャッシュは、ステートメント SYSTEM DROP QUERY CACHE
を使用してクリアできます。クエリキャッシュの内容はシステムテーブル system.query_cache に表示されます。データベースの起動以来のクエリキャッシュのヒット数とミス数は、システムテーブル system.events にイベント "QueryCacheHits" と "QueryCacheMisses" として表示されます。これらのカウンタは、use_query_cache = true
設定で実行される SELECT
クエリに対してのみ更新され、他のクエリは "QueryCacheMisses" に影響を与えません。システムテーブル system.query_log のフィールド query_cache_usage
は、実行された各クエリについて、そのクエリ結果がクエリキャッシュに書き込まれたか、読み込まれたかを示します。システムテーブル system.asynchronous_metrics の非同期メトリクス "QueryCacheEntries" と "QueryCacheBytes" は、現在クエリキャッシュが含むエントリ数およびバイト数を示します。
クエリキャッシュは、各 ClickHouse サーバープロセスごとに一度だけ存在します。ただし、キャッシュ結果はデフォルトではユーザー間で共有されません。これは変更可能ですが(以下参照)、セキュリティ上の理由から推奨されません。
クエリ結果は、そのクエリの 抽象構文木 (AST) でクエリキャッシュに参照されます。つまり、キャッシングは大文字と小文字に依存しないため、たとえば SELECT 1
と select 1
は同じクエリとして扱われます。マッチングをより自然にするために、クエリキャッシュに関連するすべてのクエリレベル設定は、AST から削除されます。
クエリが例外またはユーザーキャンセルにより中断された場合、エントリはクエリキャッシュに書き込まれません。
クエリキャッシュのサイズ(バイト単位)、最大キャッシュエントリ数、および個々のキャッシュエントリの最大サイズ(バイトおよびレコード単位)は、異なる サーバー設定オプション を使用して構成できます。
個々のユーザーのキャッシュ使用量を制限することも可能です。これは 設定プロファイル および 設定制約 を使用して行います。具体的には、ユーザーがクエリキャッシュに割り当てられる最大メモリ量(バイト単位)や、最大の保存クエリ結果数を制限できます。そのために、まず users.xml
のユーザープロファイル内で設定 query_cache_max_size_in_bytes と query_cache_max_entries を提供し、次に両方の設定を読み取り専用にします:
クエリの結果がキャッシュされるために、クエリが少なくともどのくらいの時間実行される必要があるかを定義するには、設定 query_cache_min_query_duration を使用します。たとえば、次のクエリの結果
は、クエリが5秒以上実行される場合にのみキャッシュされます。また、キャッシュされるまでにクエリがどのくらい実行される必要があるかを指定することも可能です - そのためには設定 query_cache_min_query_runs を使用します。
クエリキャッシュのエントリは、一定の時間(有効期限)が経過すると古くなります。デフォルトでは、この期間は60秒ですが、異なる値をセッション、プロファイルまたはクエリレベルで指定できます。設定 query_cache_ttl を使用します。クエリキャッシュは、エントリを「遅延的に」削除します。つまり、エントリが古くなった場合、それはすぐにキャッシュから削除されるわけではありません。代わりに、クエリキャッシュに新しいエントリを挿入する必要がある場合、データベースは新しいエントリ用の十分な空きスペースがキャッシュにあるかどうかを確認します。これが不可能な場合、データベースはすべての古いエントリを削除しようとします。それでもキャッシュに十分な空きスペースがない場合、新しいエントリは挿入されません。
クエリキャッシュ内のエントリは、デフォルトで圧縮されています。これにより、キャッシュへの書き込みおよび読み込みの速度が遅くなる代わりに、全体のメモリ消費が削減されます。圧縮を無効にするには、設定 query_cache_compress_entries を使用します。
同じクエリの複数の結果をキャッシュしておくことが有用な場合があります。これは、設定 query_cache_tag を使用して、クエリキャッシュエントリのラベル(または名前空間)として機能させることで達成できます。クエリキャッシュは、異なるタグを持つ同じクエリの結果を異なるものとして扱います。
同じクエリのために3つの異なるクエリキャッシュエントリを作成する例:
tag
タグを持つエントリのみをクエリキャッシュから削除するには、ステートメント SYSTEM DROP QUERY CACHE TAG 'tag'
を使用できます。
ClickHouse は、max_block_size 行のブロックでテーブルデータを読み取ります。フィルタリング、集約などにより、結果ブロックは通常「max_block_size」よりはるかに小さくなりますが、はるかに大きくなる場合もあります。設定 query_cache_squash_partial_results (デフォルトでは有効) は、結果ブロックが小さい場合は圧縮されるか、大きい場合は「max_block_size」サイズのブロックに分割されるかを制御します。これにより、クエリキャッシュへの書き込み性能は低下しますが、キャッシュエントリの圧縮率が向上し、後でクエリ結果がクエリキャッシュから提供される際に、より自然なブロック粒度が提供されます。
その結果、クエリキャッシュは各クエリの複数の(部分的な)結果ブロックを保存します。この動作は良好なデフォルトですが、設定 query_cache_squash_partial_results を使用して抑制することができます。
また、非決定論的関数を含むクエリの結果は、デフォルトではキャッシュされません。このような関数には以下が含まれます:
- 辞書にアクセスするための関数:
dictGet()
など。 - XML 定義に
<deterministic>true</deterministic>
タグのない ユーザー定義関数。 - 現在の日付または時刻を返す関数:
now()
、today()
、yesterday()
など。 - ランダムな値を返す関数:
randomString()
、fuzzBits()
など。 - クエリ処理に使用される内部チャンクのサイズと順序に依存する関数:
nowInBlock()
など、rowNumberInBlock()
、runningDifference()
、blockSize()
など。 - 環境に依存する関数:
currentUser()
、queryID()
、getMacro()
など。
非決定論的関数を含むクエリの結果を強制的にキャッシュするには、設定 query_cache_nondeterministic_function_handling を使用します。
システムテーブル(例:system.processes や information_schema.tables)を含むクエリの結果は、デフォルトではキャッシュされません。システムテーブルを含むクエリの結果を強制的にキャッシュするには、設定 query_cache_system_table_handling を使用します。
最後に、クエリキャッシュのエントリはセキュリティ上の理由からユーザー間で共有されません。たとえば、ユーザー A は、ユーザー B が存在しない行ポリシーを回避するために、別のユーザー B と同じクエリを実行することはできません。ただし、必要に応じて、キャッシュエントリを他のユーザーがアクセス可能(つまり共有)としてマークすることができます。設定 query_cache_share_between_users を提供することによってです。