Skip to main content
クエリキャッシュを使うと、SELECT クエリの計算は一度だけで済み、以降の同じクエリの実行結果はキャッシュから直接返せます。 クエリの種類によっては、これにより ClickHouse server のレイテンシとリソース使用量を大幅に削減できます。

背景、設計、および制限事項

クエリキャッシュは、一般にトランザクション整合性があるものとないものに分けられます。
  • トランザクション整合性のあるキャッシュでは、SELECT クエリの結果が変化した場合、または変化する可能性がある場合に、データベースがキャッシュされたクエリ結果を無効化 (破棄) します。ClickHouse では、データを変更する操作には、table への insert/update/delete や、折りたたみ merge が含まれます。トランザクション整合性のあるキャッシュは、たとえば MySQL (クエリキャッシュ は v8.0 以降で削除) や Oracle のような OLTP データベースに特に適しています。
  • トランザクション整合性のないキャッシュでは、すべてのキャッシュエントリに有効期間が設定され、期限切れになると失効する (たとえば 1 分) こと、さらにその期間中は基盤となるデータがほとんど変化しないことを前提として、クエリ結果に多少の不正確さが生じることを許容します。この方式は、全体として OLAP データベースにより適しています。トランザクション整合性のないキャッシュで十分な例として、複数のユーザーが同時に利用するレポートツールの時間別売上レポートを考えてみてください。通常、売上データの変化は十分に緩やかなため、データベースはレポートを 1 回だけ計算すればよく (最初の SELECT クエリがこれに相当します) 、以降のクエリはクエリキャッシュから直接返せます。この例では、妥当な有効期間は 30 分です。
トランザクション整合性のないキャッシュは、従来、データベースとやり取りするクライアントツールや proxy パッケージ (たとえば chproxy) によって提供されてきました。その結果、同じキャッシュロジックや configuration が重複して実装されることが少なくありません。ClickHouse の クエリキャッシュ では、キャッシュロジックが server 側に移ります。これにより、保守の手間が減り、冗長性も回避できます。

設定と使用方法

ClickHouse Cloud では、クエリキャッシュの設定を変更するには、クエリレベルの設定を使用する必要があります。構成レベルの設定の変更は現在サポートされていません。
clickhouse-local は一度に 1 つのクエリしか実行できません。単一のプロセスでクエリ結果をメモリ上にキャッシュしても意味がないため、 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 の結果は、その 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 CLEAR QUERY CACHE を使用してクリアできます。クエリキャッシュの内容は、システムテーブル system.query_cache に表示されます。データベースの起動以降のクエリキャッシュのヒット数とミス数は、システムテーブル system.events のイベント “QueryCacheHits” および “QueryCacheMisses” として確認できます。どちらのカウンターも、 SELECT クエリが設定 use_query_cache = true で実行された場合にのみ更新され、その他のクエリは “QueryCacheMisses” に影響しません。これらのカウンターはクエリ キャッシュ全体を対象とします。ディスク上のクエリキャッシュが有効な場合 (下記を参照)、メモリ上でミスした後にディスクから結果が返されたクエリは 1 回の “QueryCacheHits” として、両方のバックエンドでミスしたクエリは 1 回の “QueryCacheMisses” としてカウントされます。フィールド query_cache_usage は、システムテーブル system.query_log にあり、実行された各クエリについて、そのクエリ結果がクエリキャッシュに書き込まれたか、または クエリキャッシュから読み取られたかを示します。システムテーブル system.metrics のメトリクス QueryCacheEntries および QueryCacheBytes は、インメモリのクエリキャッシュに現在含まれているエントリ数 / バイト数を示します。これらには ディスク上のクエリキャッシュ のエントリは含まれません。ディスク上のクエリキャッシュには、現時点で使用量を示すメトリクスはありません。 クエリキャッシュは ClickHouseサーバープロセスごとに 1 つ存在します。ただし、キャッシュ結果はデフォルトではユーザー間で共有されません。これは 変更できます (下記を参照) が、セキュリティ上の理由から推奨されません。 クエリ結果は、クエリキャッシュ内でそのクエリの Abstract Syntax Tree (AST) によって参照されます。 つまり、キャッシュでは大文字・小文字は区別されず、たとえば SELECT 1 と select 1 は同じクエリとして扱われます。 マッチングをより自然にするため、クエリキャッシュおよび出力フォーマット)に関連するクエリレベルの設定はすべて AST から除去されます。 例外またはユーザーによるキャンセルによってクエリが中断された場合、クエリキャッシュにエントリは書き込まれません。 クエリキャッシュのサイズ (バイト単位)、cache エントリの最大数、および個々の cache エントリの最大サイズ (バイト単位および レコード数) は、各種サーバー設定オプションで設定できます。
設定プロファイル と 設定の 制約 を使用して、個々のユーザーによる cache の使用量を制限することもできます。具体的には、ユーザーが インメモリのクエリキャッシュ内で割り当て可能なメモリの最大量 (バイト単位) と、保存できるクエリ結果の最大数を制限できます (これらの制限は ディスク上の クエリキャッシュ には適用されません)。そのためには、まず users.xml のユーザープロファイルに query_cache_max_size_in_bytes と query_cache_max_entries の設定を追加し、その後、両方の設定を 読み取り専用にします。
クエリ結果をキャッシュできるようにするために、クエリが少なくともどれくらいの時間実行されている必要があるかを定義するには、設定 query_cache_min_query_durationを使用できます。たとえば、次のクエリの結果は
クエリの実行時間が 5 秒を超えた場合にのみキャッシュされます。また、結果が キャッシュされるまでにクエリを何回実行する必要があるかを指定することもできます。その場合は設定 query_cache_min_query_runs を使用します。 クエリキャッシュ内のエントリは、一定時間 (time-to-live) が経過すると古くなります。デフォルトでは、この期間は 60 秒ですが、設定 query_cache_ttl を使用して、セッション、プロファイル、またはクエリのレベルで別の 値を指定できます。クエリ キャッシュではエントリは「遅延的に」削除されます。つまり、エントリが古くなっても、すぐにキャッシュから削除されるわけではありません。代わりに、新しいエントリを クエリキャッシュに挿入しようとすると、データベースは新しいエントリのためにキャッシュに十分な空き領域があるかどうかを確認します。そうでない 場合、データベースは古くなったすべてのエントリの削除を試みます。それでもキャッシュに十分な空き領域がない場合は、新しいエントリは挿入されません。 クエリが HTTP 経由で実行される場合、ClickHouse は Age および Expires ヘッダーに、キャッシュされたエントリの経過時間 (秒単位) と有効期限のタイムスタンプを設定します。 クエリキャッシュ内のエントリは、デフォルトで圧縮されています。これにより、クエリキャッシュへの書き込み / 読み取りは遅くなるものの、全体的なメモリ消費量は削減されます 。圧縮を無効にするには、設定 query_cache_compress_entries を使用します。 同じクエリに対して複数の結果をキャッシュしておくことが有用な場合があります。これは、 クエリキャッシュの cache エントリに対するラベル (またはネームスペース) として機能する設定 query_cache_tag を使うことで実現できます。クエリキャッシュは、 同じクエリであってもタグが異なる結果は別のものとして扱います。 同じクエリに対して 3 つの異なるクエリキャッシュの cache エントリを作成する例:
クエリキャッシュからタグ tag の付いたエントリのみを削除するには、ステートメント SYSTEM CLEAR QUERY CACHE TAG 'tag' を使用します。

サブクエリのキャッシュ

デフォルトでは、外側のクエリで use_query_cache を有効にしても、その設定はサブクエリには伝播しません。つまり、各サブクエリで明示的にキャッシュを有効にする必要があります:
この例では、キャッシュされるのは内側のサブクエリの結果だけです。外側のクエリはキャッシュされません。 すべてのサブクエリに対するキャッシュを一括で有効にするには、設定 query_cache_for_subqueries を使用します。
バルク伝播が有効な状態で特定のサブクエリのキャッシュを明示的に無効にするには、そのサブクエリで use_query_cache = false を設定します。
インメモリのクエリキャッシュにおけるサブクエリの cache エントリは、system.query_cache で is_subquery = 1 のエントリとして確認できます。ディスク上のクエリキャッシュ が有効な場合、サブクエリの結果はディスクにもキャッシュされますが、これらのエントリは system.query_cache には表示されません。query_cache_ttl 設定はサブクエリの cache エントリにも適用され、サブクエリごとに設定できます。 ClickHouse は、テーブルデータを max_block_size 行ごとのブロックで読み取ります。フィルタリングや集約、 その他の処理の影響により、結果ブロックは通常 ‘max_block_size’ よりかなり小さくなりますが、これよりずっと大きくなる場合もあります。設定 query_cache_squash_partial_results (デフォルトで有効) は、結果ブロックを クエリ結果キャッシュに挿入する前に、 (非常に小さい場合は) まとめるか、 (大きい場合は) ‘max_block_size’ サイズのブロックに分割するかを制御します。 これによりクエリキャッシュへの書き込み性能は低下しますが、cache エントリの圧縮率が向上し、後でクエリ結果がクエリキャッシュから返される際の ブロック粒度がより自然になります。 その結果、クエリキャッシュには各クエリごとに複数の (部分的な) 結果ブロックが保存されます。この挙動はデフォルトとしては適切ですが、設定 query_cache_squash_partial_results を使用して無効にできます。 また、非決定論的関数を含むクエリの結果は、デフォルトではキャッシュされません。こうした関数には次のものが含まれます。 それでも非決定論的関数を含むクエリの結果を強制的にキャッシュするには、設定 query_cache_nondeterministic_function_handling を使用します。 システムテーブル (例: system.processes` や information_schema.tables) を含むクエリの結果は、デフォルトではキャッシュされません。それでもシステムテーブルを含む クエリの結果を強制的にキャッシュするには、設定 query_cache_system_table_handling を使用します。 最後に、クエリキャッシュ内の cache エントリは、セキュリティ上の理由からユーザー間で共有されません。たとえば、ユーザー A が、同じクエリを実行することで、 そのようなポリシーが存在しない別のユーザー B の結果を利用し、テーブル上の ROW POLICY を回避できてはなりません。ただし、必要に応じて、 設定 query_cache_share_between_users を指定することで、cache エントリを他のユーザーから アクセス可能 (つまり共有) としてマークできます。

ディスク上のクエリキャッシュ

デフォルトでは、クエリキャッシュはエントリをメモリ上に保持します。加えて、 ファイルシステムキャッシュをバックエンドとして、クエリ結果をディスク上にキャッシュすることもできます。インメモリのクエリキャッシュと比べて、ディスク上のクエリキャッシュは より多くの容量を使用でき、サーバーを再起動しても失われません。エントリは、それを書き込んだサーバービルドと同じビルドからのみ提供されます。そのため、 アップグレードやダウングレードを行うと、以前のビルドで書き込まれたエントリはキャッシュミスとなり、いずれ上書きされるか追い出されます。 ディスク上のクエリキャッシュを使用するには、まずサーバー設定の filesystem_caches セクションでファイルシステムキャッシュを設定します。
次に、その名前を設定 query_cache_on_disk_cache_name で指定します:
ディスク上のクエリキャッシュはインメモリのクエリキャッシュとは独立して動作し、設定 enable_reads_from_query_cache_on_disk および enable_writes_to_query_cache_on_disk (いずれもデフォルトは true ) により、読み取りと書き込みをそれぞれ個別に有効化できます。両方のキャッシュで読み取りが有効な場合、ルックアップはまずメモリで行われ、ミスした場合にのみディスクで行われます。両方のキャッシュで書き込み が有効な場合、結果は両方に保存されます。結果を保存できるキャッシュが存在しない場合 (例:インメモリのクエリキャッシュを持たない clickhouse-local で enable_writes_to_query_cache_on_disk = false とした場合) でも、非決定論的 関数、システムテーブル、または throw 以外のオーバーフローモードを理由にクエリが拒否されることはありません。これらのチェックは、誤った結果の保存を防ぐためだけのものだからです。ディスク上のエントリは、設定 query_cache_on_disk_codec (デフォルトは ZSTD(3)) で指定されたコーデックで圧縮されます。クエリキャッシュのサーバー設定 max_entry_size_in_bytes および max_entry_size_in_rows は、 両方のバックエンドのエントリを制限します。各バックエンドはエントリを自身の保存形式に基づいて計測するため、ディスク上ではバイト数の上限は シリアル化・圧縮後のエントリに適用されます。いずれかを 0 に設定すると、両方のバックエンドへの書き込みが無効になります。その他の すべてのクエリキャッシュ設定 (例:query_cache_ttl、query_cache_min_query_duration) は、以下の 2 つの例外を除き、 インメモリのクエリキャッシュと同様にディスク上のクエリキャッシュにも適用されます。 設定 query_cache_max_size_in_bytes および query_cache_max_entries は、インメモリのクエリキャッシュにのみ適用されるユーザーごとの上限です。これらは ディスク上のクエリキャッシュには適用されません。ディスク上のエントリの上限は、設定されたファイルシステムキャッシュのサイズによって決まり、そのルールに従って追い出されます (後述) 。そのため、制限の厳しい設定プロファイルを持つユーザーであっても、これらの設定でメモリ上に許容される量を超えて結果をディスクに保存できます。 これは意図的な仕様です。ファイルシステムキャッシュはサイズ上限のある共有リソースであり、ディスク上のクエリキャッシュはその中に領域を予約しません。 設定 query_cache_share_between_users も例外です。この設定は両方のバックエンドで同じ意味を持ち、この設定を有効にして書き込まれたエントリは すべてのユーザーに提供されます。ただし、非共有エントリの分離方法が異なります。インメモリのクエリキャッシュはクエリごとに単一のエントリを保持し、 読み取り時にそのエントリが別のユーザーのものであれば拒否します。そのため、ユーザー A の非共有エントリは、期限切れになるか 追い出されるまで、ユーザー B の書き込みを妨げます。一方、ディスク上のクエリキャッシュでは、ユーザーとそのユーザーの現在のロールの組み合わせを非共有エントリのキーの一部とするため、 ユーザー B はユーザー A のエントリにはヒットせず、結果のコピーを独自に保存します。これは、同一ユーザーが異なる現在のロールの組み合わせで 実行する場合 (例:SET ROLE の後) にも当てはまります。ロールは権限と行ポリシーを伴うため、異なるロールの下で計算された結果は 別々にキャッシュされます。 ディスク上のクエリキャッシュのエントリは、ファイルシステムキャッシュの通常のエントリであり、同じファイルシステムキャッシュにキャッシュされている他のデータと区別されません。つまり、削除から保護されることはなく、 ファイルシステムキャッシュの容量が不足した場合には同じルール (例: least-recently-used) に従って追い出されます。合計サイズや件数に対する個別の制限はなく、ファイルシステム キャッシュ自体の制限のみが適用されます。ステートメント SYSTEM CLEAR QUERY CACHE (および SYSTEM CLEAR QUERY CACHE TAG 'tag') は、セッションの設定 query_cache_on_disk_cache_name で選択されたファイルシステムキャッシュ (例: SET query_cache_on_disk_cache_name = 'query_results' の実行後) から、ディスク上のクエリキャッシュのエントリ (指定された タグを持つもの。ただし、破損したエントリおよび互換性のないフォーマットのエントリはタグに関係なく削除されます) も削除し、ファイルシステムキャッシュ内のその他のデータはそのまま残します。 エントリを見つけるために、ファイルシステムキャッシュのすべてのファイルセグメントのメタデータを (ディスクから読み取ることなくメモリ上で) 走査し、 クエリキャッシュのエントリとしてマークされた各キーのヘッダーを読み取ります。この走査にかかる時間は ファイルシステムキャッシュ内のファイルセグメント数に比例するため、クエリ結果専用のファイルシステムキャッシュを用意しておけば、SYSTEM CLEAR QUERY CACHE のコストを低く抑えられます。 ファイルシステムキャッシュがエントリの先頭部分をすでに追い出している場合、エントリの残りの部分は、ファイルシステムキャッシュが それを追い出すか、同じクエリ結果が再度書き込まれるまで残り続けますが、結果として返されることはありません。書き込み中の エントリも同様にそのまま残されます。ステートメント SYSTEM DROP FILESYSTEM CACHE '<name>' を実行すると、これらのエントリはファイルシステムキャッシュ内の他のすべてのデータと ともに削除されます。システムテーブル system.query_cache およびメトリクス QueryCacheEntries と QueryCacheBytes に表示されるのは、 インメモリのクエリキャッシュのエントリのみです。ディスク上のクエリ キャッシュのヒット数とミス数は、システムテーブル system.events のイベント “QueryCacheOnDiskHits” および “QueryCacheOnDiskMisses” として表示されます。これらはディスク上のバックエンドのみの内訳です。一方、“QueryCacheHits” と “QueryCacheMisses” は引き続きクエリキャッシュ全体のカウンターであり、“QueryCacheAgeSeconds” は両方のバックエンドのヒットを集計します。 メモリ内キャッシュが先に参照されるため、メモリ内のヒット数は “QueryCacheHits” から “QueryCacheOnDiskHits” を引いた値になります。