Fast Query Cache は、Alibaba Cloud が開発したクエリキャッシュで、MySQL ネイティブのクエリキャッシュをロックフリーで高い同時実行性を持つ設計に置き換えるものです。読み取り負荷の高いワークロードにおいて 1 秒あたりのクエリ数 (QPS) を大幅に向上させる一方、読み取りと書き込みが混在するシナリオでは追加するオーバーヘッドは最小限です。
Fast Query Cache は、マイナーエンジンバージョンが 20200331 以降の MySQL 5.7 を実行している ApsaraDB RDS for MySQL インスタンスで、かつ専用プロキシサービスが無効化されている場合にのみ利用可能です。
前提条件
開始する前に、以下を確認してください。
-
ApsaraDB RDS for MySQL インスタンスが、マイナーエンジンバージョン 20200331 以降の MySQL 5.7 を実行していること
-
ApsaraDB RDS for MySQL インスタンスの専用プロキシサービスが無効になっていること。詳細については、「ApsaraDB RDS for MySQL インスタンスの専用プロキシサービスを無効にする」をご参照ください。
仕組み
クエリキャッシュは、条件を満たすクエリの結果セットを保存することでパフォーマンスを向上させます。同じクエリが再度実行されると、データベースはキャッシュされた結果を直接返すため、SQL の解析、最適化、実行をスキップできるため、高頻度かつシンプルなクエリにおける CPU オーバーヘッドと応答時間が削減されます。
MySQL ネイティブのクエリキャッシュを使用しない理由
MySQL ネイティブのクエリキャッシュは、すべてのキャッシュ操作にグローバルロックを使用します。高い同時実行性環境下では、このロックがボトルネックとなります。CPU コアを追加しても、QPS が向上するどころか低下することがあります。メモリ管理の不備がこの問題をさらに悪化させます。断片化の蓄積、回収の遅延、キャッシュヒット率の低さが、大幅なパフォーマンス低下を引き起こします。これらの問題により、MySQL は MySQL 8.0 でネイティブのクエリキャッシュを完全に廃止し、それ以前のバージョンではデフォルトで無効にしました。
Fast Query Cache がこれらの問題を解決する方法
Alibaba Cloud は、クエリキャッシュをゼロから再設計しました。
| 改善領域 | 変更内容 |
|---|---|
| 同時実行性 | グローバルロックを削除しました。シャーディングを伴うロックフリー設計を採用し、ロック競合のない真のマルチコア並列処理を実現しています。 |
| メモリ管理 | オンデマンドでメモリを割り当てます。インテリジェントな回収ポリシーにより、断片化を削減し、メモリ使用率を向上させます。 |
| キャッシュポリシー | キャッシュヒット率をリアルタイムで監視し、エビクションポリシーとキャッシュ有効期間を動的に調整することで、古いエントリがリソースを占有するのを防ぎます。 |
| 書き込み互換性 | 増分無効化を使用して、書き込みの影響を受けたキャッシュエントリのみを部分的に無効化し、キャッシュされた読み取りへの影響を軽減します。 |
Fast Query Cache の有効化
Fast Query Cache は、query_cache_type (オン/オフスイッチ) と query_cache_size (メモリ割り当て) の 2 つのパラメーターで制御します。
パラメーター
| パラメーター | 説明 |
|---|---|
query_cache_type |
キャッシュの有効/無効を制御します。有効な値: 0 — 無効 (デフォルト)、1 — 条件を満たすすべてのクエリで有効になります (SQL_NO_CACHE でステートメントごとにキャッシュを無効化できます)、2 — グローバルでは無効ですが、SQL_CACHE で特定のステートメントのキャッシュを有効化できます |
query_cache_size |
キャッシュに割り当てるメモリサイズ。有効な値: 0 ~ 10,485,760,000 バイト (1,024 の倍数である必要があります)。単位: バイト |
手順
Fast Query Cache は追加のメモリを消費するため、まず innodb_buffer_pool_size を再設定してメモリの空き容量を確保します。
-
innodb_buffer_pool_sizeを現在の値の 90% に削減します。これにより解放された 10% をクエリキャッシュに利用できます。たとえば、現在の値が{DBInstanceClassMemory*7/10}の場合、{DBInstanceClassMemory*63/100}に設定します。詳細については、「InnoDB バッファープールサイズの変更」をご参照ください。 -
ワークロードに基づいて
query_cache_sizeを設定します。パラメーターの変更の詳細については、「インスタンスパラメーターの変更」をご参照ください。-
結果セットのサイズがわかっている場合:
query_cache_sizeを 結果セットサイズの 20% に設定します。 -
結果セットのサイズがわからない場合:
query_cache_sizeを [innodb_buffer_pool_sizeの 10%] に設定します。
重要ApsaraDB RDS for MySQL インスタンスの仕様を変更しても、
query_cache_sizeは自動的にスケーリングされません。仕様変更が有効になった直後に、このパラメーターを再設定してください。 -
-
query_cache_typeを [1] に設定して、Fast Query Cache を有効にします。詳細については、「インスタンスパラメーターの変更」をご参照ください。
セッションレベルでのテスト
グローバル設定を変更する前に、他のユーザーに影響を与えることなく、単一の接続で Fast Query Cache をテストできます。
-- このセッションでのみ Fast Query Cache を有効にする
SET SESSION query_cache_type = 1;
-- このセッションでのみ Fast Query Cache を無効にする
SET SESSION query_cache_type = 0;
キャッシュが機能していることの確認
Fast Query Cache を有効にした後、次のステートメントを実行してキャッシュの動作状況を確認します。
SHOW STATUS LIKE 'Qcache%';
確認すべき主な指標は次のとおりです。
| 指標 | 内容 |
|---|---|
Qcache_hits |
キャッシュから返されたクエリの数。この値が増加していれば、キャッシュが機能していることを確認できます。 |
Qcache_inserts |
キャッシュに追加されたクエリの数 |
Qcache_lowmem_prunes |
メモリ不足により削除されたキャッシュエントリの数。この値が高い場合は、query_cache_size の設定値が小さすぎることを示します。 |
Qcache_not_cached |
キャッシュされなかったクエリの数 (キャッシュ対象外のクエリ、または SQL_NO_CACHE が指定されたクエリ) |
キャッシュが正常に機能している場合、Qcache_hits が Qcache_inserts よりも速く増加します。
パフォーマンスベンチマーク
以下のベンチマークは、専用 ApsaraDB RDS for MySQL インスタンス (4 CPU コア、8 GB メモリ、Sysbench を使用した 250 MB のテストデータ) における 3 つの構成での QPS を比較したものです。
-
QC-OFF: クエリキャッシュなし
-
MySQL-QC: MySQL ネイティブのクエリキャッシュ
-
Fast-QC: Fast Query Cache
全ヒットの読み取りワークロード (100% キャッシュヒット率、512 MB キャッシュ)
プライマリキーに対する POINT SELECT ステートメントを使用した oltp_point_select スクリプトを使用します。
表 1. キャッシュヒット率 100% での読み取りクエリの QPS
| 同時クエリ数 | QC-OFF | MySQL-QC (QC-OFF と比較した QPS の増加) | Fast-QC (QC-OFF と比較した QPS の増加) |
|---|---|---|---|
| 1 | 8,093 | 8,771 (8.38%) | 9,261 (14.43%) |
| 8 | 62,262 | 65,686 (5.50%) | 75,313 (20.96%) |
| 16 | 97,083 | 73,027 (-24.78%) | 139,323 (43.51%) |
| 32 | 97,337 | 60,567 (-37.78%) | 200,978 (106.48%) |
| 64 | 106,283 | 60,216 (-43.34%) | 221,659 (108.56%) |
| 128 | 107,781 | 62,844 (-41.69%) | 231,409 (114.70%) |
| 256 | 106,694 | 63,832 (-40.17%) | 222,187 (108.25%) |
| 512 | 101,733 | 64,866 (-36.24%) | 203,789 (100.32%) |
| 1,024 | 89,548 | 62,291 (-30.44%) | 203,542 (127.30%) |
同時実行数が増加するにつれて、MySQL ネイティブのクエリキャッシュの QPS は急激に低下し、64 スレッドで最大 43% 減少します。一方、Fast Query Cache の QPS は上昇を続け、1,024 スレッドで QC-OFF を最大で 127.30% 上回ります。
高ヒット率の読み取りワークロード (80% 以上のキャッシュヒット率、512 MB キャッシュ)
複数のレコードを返す範囲クエリを使用した oltp_read_only スクリプトを使用します。
表 2. キャッシュヒット率 80% 以上での読み取りクエリの QPS
| 同時クエリ数 | QC-OFF | MySQL-QC (QC-OFF と比較した QPS の増加) | Fast-QC (QC-OFF と比較した QPS の増加) |
|---|---|---|---|
| 1 | 5,099 | 6,467 (26.83%) | 7,022 (37.71%) |
| 8 | 28,782 | 28,651 (-0.46%) | 45,017 (56.41%) |
| 16 | 35,333 | 31,099 (-11.98%) | 66,770 (88.97%) |
| 32 | 34,864 | 27,610 (-20.81%) | 67,623 (93.96%) |
| 64 | 35,503 | 27,518 (-22.49%) | 75,981 (114.01%) |
| 128 | 35,744 | 27,733 (-22.41%) | 80,396 (124.92%) |
| 256 | 35,685 | 27,738 (-22.27%) | 80,925 (126.78%) |
| 512 | 35,308 | 27,398 (-22.40%) | 79,323 (124.66%) |
| 1,024 | 34,044 | 26,861 (-22.10%) | 75,742 (122.48%) |
Fast Query Cache の QPS は同時実行数の増加とともに上昇を続け、QC-OFF を 124% 以上上回るピークに達します。一方、MySQL ネイティブのクエリキャッシュは、高い同時実行性で QC-OFF を 22% 下回るまで低下します。
低ヒット率の読み取りワークロード (約 10% のキャッシュヒット率、16 MB キャッシュ)
oltp_read_only スクリプトを使用します。16 MB のキャッシュはデータセットよりもはるかに小さいため、頻繁なエビクションが発生し、ヒット率は約 10% になります。
表 3. キャッシュヒット率約 10% での読み取りクエリの QPS
| 同時クエリ数 | QC-OFF | MySQL-QC (QC-OFF と比較した QPS の増加) | Fast-QC (QC-OFF と比較した QPS の増加) |
|---|---|---|---|
| 1 | 5,004 | 4,727 (-5.54%) | 5,199 (3.90%) |
| 8 | 28,795 | 22,542 (-21.72%) | 28,578 (-0.75%) |
| 16 | 35,455 | 24,064 (-32.13%) | 35,682 (0.64%) |
| 32 | 34,526 | 21,330 (-38.22%) | 35,871 (3.90%) |
| 64 | 35,514 | 19,791 (-44.27%) | 36,051 (1.51%) |
| 128 | 35,983 | 19,519 (-45.75%) | 36,253 (0.75%) |
| 256 | 35,695 | 19,168 (-46.30%) | 36,337 (1.80%) |
| 512 | 35,182 | 18,420 (-47.64%) | 35,972 (2.25%) |
| 1,024 | 33,915 | 20,168 (-40.53%) | 34,546 (1.86%) |
MySQL ネイティブのクエリキャッシュでは、低ヒット率の条件下で QPS が最大 48% 低下します。一方、Fast Query Cache の QPS は QC-OFF の 1~4% 以内に留まり、キャッシュミスがほとんどの場合でも、実質的にオーバーヘッドは発生しません。
読み取りと書き込みが混在するワークロード
頻繁なテーブル更新によってキャッシュエントリが常に無効化される oltp_read_write スクリプトを使用します。
表 4. 読み取りおよび書き込みクエリの QPS
| 同時クエリ数 | QC-OFF | Fast-QC (QC-OFF と比較した QPS の増加) |
|---|---|---|
| 1 | 4,152 | 4,098 (-1.30%) |
| 8 | 21,359 | 21,195 (-0.77%) |
| 16 | 26,020 | 25,548 (-1.81%) |
| 32 | 27,595 | 26,996 (-2.17%) |
| 64 | 29,229 | 28,733 (-1.70%) |
| 128 | 29,265 | 28,828 (-1.49%) |
| 256 | 29,911 | 29,616 (-0.99%) |
| 512 | 29,148 | 28,816 (-1.14%) |
| 1,024 | 29,204 | 28,824 (-1.30%) |
Fast Query Cache を使用した場合、読み取りと書き込みが混在するワークロードにおいて QPS の低下は最大でも 2.17% にとどまります。増分無効化メカニズムにより、頻繁な書き込みが発生してもキャッシュメンテナンスのオーバーヘッドは低く抑えられています。
キャッシュのサイジング
query_cache_size は、ヒット率と QPS に直接影響します。以下のベンチマークは、10 GB のデータセット (テーブル数 100、各テーブルのレコード数 400,000、ホットデータ 20%、同時実行スレッド数 64、innodb_buffer_pool_size = 6 GB) におけるキャッシュサイズとパフォーマンスの関係を示しています。
表 5. 異なるキャッシュサイズでの QPS
| query_cache_size (MB) | QC-OFF | Fast-QC ヒット率 | Fast-QC (QC-OFF と比較した QPS の増加) |
|---|---|---|---|
| 64 | 98,236 | 22% | 99,440 (1.23%) |
| 128 | 98,236 | 45% | 114,155 (16.21%) |
| 256 | 98,236 | 72% | 140,668 (43.19%) |
| 512 | 98,236 | 82% | 151,260 (53.98%) |
| 1,024 | 98,236 | 84% | 153,866 (56.63%) |
| 2,048 | 98,236 | 87% | 159,597 (62.46%) |
| 4,096 | 98,236 | 92% | 169,412 (72.45%) |
このテストから得られた主な知見は次のとおりです。
-
Fast Query Cache は、キャッシュサイズに関係なく、22% のヒット率であっても QPS が低下することはありません。
-
プライマリキークエリの場合、Fast Query Cache は、どのヒット率でも MySQL ネイティブのクエリキャッシュを上回り、場合によっては 90% 以上上回ります。
-
範囲クエリおよび
ORDER BYを含むクエリの場合、Fast Query Cache は、ヒット率が 90% 未満のときに MySQL ネイティブのクエリキャッシュよりも優れたパフォーマンスを発揮し、大幅な CPU リソースを節約します。
このテストの実際の結果セットサイズは 2.5 GB でした。ホットデータ (このテストでは 10 GB の 20% で 2 GB) をカバーするサイジングの場合、このシナリオで 80% 以上のヒット率に達するには、約 512 MB~1 GB の query_cache_size が必要になります。
query_cache_size に過度のメモリを割り当てると、InnoDB バッファープールで利用可能なメモリが減少し、全体的なパフォーマンスが低下する可能性があります。query_cache_size を増やす場合は、必ず innodb_buffer_pool_size をその分だけ削減してください。
Fast Query Cache を使用する場合
Fast Query Cache の使用を推奨する場合
-
ワークロードが読み取り集中型で、書き込み頻度が低い場合 (例: eコマースサイトの商品詳細ページ、レポート用クエリ)
-
読み取り/書き込み比率が高い特定のテーブルをキャッシュしたい場合。
TABLE_STATISTICSテーブルを確認してこれらのテーブルを特定し、query_cache_type = 2と組み合わせてSQL_CACHEキーワードを使用することで、対象のクエリのみをキャッシュします。TABLE_STATISTICSのクエリの詳細については、「Performance Insight」をご参照ください。
Fast Query Cache をグローバルに有効にする前に、InnoDB バッファープールのヒット率を確認してください。
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_reads';
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read_requests';
ヒット率 = 1 - (Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests)
InnoDB バッファープールのヒット率が 80% 未満の場合、バッファープールにはすでにメモリプレッシャーがかかっています。この状態で Fast Query Cache を有効にすると (innodb_buffer_pool_size を削減する必要があるため)、パフォーマンスが低下する可能性があります。キャッシュを有効にする前に、インスタンスメモリを増やすか、クエリを最適化してください。
Fast Query Cache を避けるべき場合
-
書き込み負荷の高いワークロード: 頻繁な書き込みにより、キャッシュが常に無効化され、キャッシュの利点がほとんど得られないままオーバーヘッドが発生します。高頻度のトランザクションシステムでは、
query_cache_type = 0のままにしてください。 -
リアルタイムデータの要件: キャッシュされた結果は、実データより古い可能性があります。金融市場データのように古いデータの読み取りが許容されないユースケースでは、キャッシュを無効にするか、クエリごとに
SQL_NO_CACHEを使用してください。
適切な query_cache_type 値の選択
query_cache_type はセッションレベルの変更をサポートしているため、グローバル設定を変更せずに、接続ごとにキャッシュ動作を調整できます。
SET SESSION query_cache_type = 1; -- このセッションで有効化
SET SESSION query_cache_type = 0; -- このセッションで無効化
| 値 | 動作 | 推奨される用途 |
|---|---|---|
| 0 | Fast Query Cache をグローバルに無効化します。 | 書き込み負荷の高いワークロード、またはヒット率が非常に低いシナリオ。 |
| 1 | 条件を満たすすべてのクエリで有効化します。特定のステートメントをキャッシュ対象外にするには SQL_NO_CACHE を使用します。 |
データの変更が少なく、読み取り負荷の高いワークロード。 |
| 2 | グローバルでは無効化します。SQL_CACHE を含むクエリのみをキャッシュします。 |
大規模なデータセット、予測不能なアクセスパターンがある場合、またはテーブルレベルでのキャッシュ制御が必要な場合。 |