Google Cloud の Cloud SQL for MySQL は、Google Cloud Platform で MySQL、PostgreSQL、SQL Server データベースを設定、維持、運用、管理できるよう支援するフルマネージド データベース サービスです。Cloud SQL はフリート内で多数の MySQL データベースを実行しますが、メモリ不足(OOM)エラーが原因で一部の MySQL インスタンスがメモリを大量に消費してクラッシュすることがよく見られます。この記事では、MySQL のメモリに関する問題のトラブルシューティングについて説明します。
メモリ不足(OOM)状態は、プロセスがメモリを割り当てようとし、サーバーに使用可能なメモリがないために失敗した場合に発生します。これにより、多くの場合、Linux の OOM キラーが発生します。これは、システムのメモリが著しく低下したときに Linux カーネルが採用し、データベース プロセスを終了させるプロセスです。
OOM 状態によるデータベース クラッシュは理想的ではありません。データベース プロセスは SIGKILL ディレクティブを使用して終了し、アプリケーションが通常の MySQL シャットダウンを行えないためです。これにより、次の問題が発生します。
バージョン 5.7 以降、MySQL のメモリ割り当ての理解に役立つ、performance_schema へのメモリ インストルメンテーションが追加されました。この記事では、performance_schema を使用して MySQL のメモリに関する問題のトラブルシューティングを行う方法について説明します。
各 Cloud SQL インスタンスは、ホストである Google Cloud サーバー上で実行されている仮想マシン(VM)を利用しています。各 VM は、MySQL サーバーなどのデータベース プログラムと、ロギングやモニタリングなどのサポート サービスを提供するサービス エージェントを実行します。Cloud SQL は、メモリの一部をオペレーティング システム(OS)とサービス エージェント用に予約し、残りのメモリを mysqld などのデータベース プログラムに割り当てます。
次の表に、mysqld プロセスのメモリ割り当てと、各マシンタイプのデフォルトの InnoDB バッファプール構成を示します。
マシンタイプ |
インスタンスの RAM GiB(バイト) |
mysqld メモリ(GB) |
InnoDB バッファプールの GiB(バイト) |
バッファプール(インスタンス RAM の割合) |
バッファプール(mysqld メモリの割合) |
db-g1-small |
1.7(1825361101) |
1.11 |
0.625(671088640) |
37% |
56% |
db-custom-1-3840 |
3.75(4026531840) |
2.97 |
1.375(1476395008) |
37% |
46% |
db-custom-2-7680 |
7.5(8053063680) |
6.72 |
4(4294967296) |
53% |
60% |
db-custom-2-13312 |
13(13958643712) |
12 |
9(9663676416) |
69% |
75% |
db-custom-4-15360 |
15(16106127360) |
14 |
10.5(11274289152) |
70% |
76% |
db-custom-4-26624 |
26(27917287424) |
24 |
19(20401094656) |
73% |
79% |
db-custom-8-30720 |
30(32212254720) |
28 |
22(23622320128) |
73% |
80% |
db-custom-8-53248 |
52(55834574848) |
48 |
38(40802189312) |
73% |
79% |
db-custom-16-61440 |
60(64424509440) |
55 |
44(47244640256) |
73% |
80% |
db-custom-16-106496 |
104(111669149696) |
96 |
75(80530636800) |
72% |
78% |
db-custom-32-122880 |
120(128849018880) |
110 |
87(93415538688) |
73% |
79% |
db-custom-32-212992 |
208(223338299392) |
191 |
150(161061273600) |
72% |
78% |
db-custom-64-245760 |
240(257698037760) |
221 |
173(185757335552) |
72% |
78% |
db-custom-96-368640 |
360(386547056640) |
331 |
260(279172874240) |
72% |
79% |
db-custom-64-425984 |
416(446676598784) |
383 |
300(322122547200) |
72% |
78% |
db-custom-96-638976 |
624(670014898176) |
574 |
450(483183820800) |
72% |
78% |
10 GB 以上の RAM を搭載したインスタンスの場合、mysqld プロセスはインスタンス RAM の 92% を使用するように制限され、InnoDB バッファプールは mysqld プロセスで使用可能なメモリの 75 ~ 80% の範囲に構成されます。InnoDB バッファプールに加えて、MySQL は、パフォーマンスを向上させるために複数のバッファとキャッシュを割り当てます。MySQL でのメモリ割り当てについて詳しくは、こちらの記事をご覧ください。
mysqld のメモリ使用量が mysqld プロセスの設定上限を超えると、Linux OOM キラーはインスタンス上の最大メモリ コンシューマである mysqld プロセスを強制終了します。
performance_schema は、サーバーの実行を低レベルでモニタリングする MySQL の機能です。Cloud SQL for MySQL では、RAM サイズが 3 GB 以上のインスタンスで performance_schema を有効にできます。MySQL 8.0 バージョン以降の場合、15GB 以上の RAM を持つインスタンスでデフォルトで有効になっています。
performance_schema は、MySQL 5.6 および 5.7 バージョンではデフォルトで無効になっていますが、データベース フラグを使用して有効にできます。
performance_schema を有効にすると、パフォーマンスのオーバーヘッドが生じ、メモリにも影響します。メモリ割り当ての詳細については、パフォーマンス スキーマのメモリ割り当てモデルをご覧ください。
performance_schema のメモリ インストルメンテーションは、MySQL のメモリ使用量のモニタリングに役立ちます。メモリ インストルメントの名前は、memory/code_area/instrument_name の形式です。ここで code_area は、sql や Innodb などの値、instrument_name はインストルメンテーションの詳細です。
MySQL 5.7 では、一部のメモリ インストゥルメントがデフォルトで有効になっていますが、すべてではありません。これらを有効にするには、performance_schema.setup_instruments テーブルの ENABLED 列を更新します。
mysql> select version();
+-------------------+
| version() |
+-------------------+
| 5.7.39-google-log |
+-------------------+
1 row in set (0.00 sec)
mysql> SELECT enabled, count(*) FROM performance_schema.setup_instruments WHERE NAME LIKE '%memory%' group by enabled;
+---------+----------+
| enabled | count(*) |
+---------+----------+
| YES | 70 |
| NO | 306 |
+---------+----------+
2 rows in set (0.00 sec)
mysql> UPDATE performance_schema.setup_instruments SET ENABLED = 'YES' WHERE NAME LIKE 'memory/%';
Query OK, 306 rows affected (0.00 sec)
Rows matched: 376 Changed: 306 Warnings: 0
mysql> SELECT enabled, count(*) FROM performance_schema.setup_instruments WHERE NAME LIKE '%memory%' group by enabled;
+---------+----------+
| enabled | count(*) |
+---------+----------+
| YES | 376 |
+---------+----------+
1 row in set (0.00 sec)
ランタイム時にメモリ インストルメンテーションを有効にすると、インストルメンテーションを有効にした後に作成されたメモリ オブジェクトのみがカウントされます。そのため、トラブルシューティングを行うのに十分なデータがサーバーによって収集されるまで待つことをおすすめします。
MySQL 8.0 では、デフォルトですべてのメモリ インストゥルメントが有効になります。
mysql> select version();
+---------------+
| version() |
+---------------+
| 8.0.28-google |
+---------------+
1 row in set (0.00 sec)
mysql> SELECT enabled, count(*) FROM performance_schema.setup_instruments WHERE NAME LIKE '%memory%' group by enabled;
+---------+----------+
| enabled | count(*) |
+---------+----------+
| YES | 490 |
+---------+----------+
1 row in set (0.00 sec)
sys スキーマには、performance_schema データをわかりやすい形式に要約するビューが多数含まれています。ほとんどのビューはペアで提供されます。名前が同じで、そのうちの 1 つに x$ 接頭辞が付いています。MySQL 5.7 および 8.0 では、さまざまなレベルでメモリ割り当てを表示するために以下のビューを使用できます。
mysql> use sys;
Database changed
mysql> show tables like '%memory%';
+-------------------------------------+
| Tables_in_sys (%memory%) |
+-------------------------------------+
| memory_by_host_by_current_bytes |
| memory_by_thread_by_current_bytes |
| memory_by_user_by_current_bytes |
| memory_global_by_current_bytes |
| memory_global_total |
| x$memory_by_host_by_current_bytes |
| x$memory_by_thread_by_current_bytes |
| x$memory_by_user_by_current_bytes |
| x$memory_global_by_current_bytes |
| x$memory_global_total |
+-------------------------------------+
10 rows in set (0.02 sec)
sys.memory_global_total ビューは、合計メモリ使用量を読み取り可能な形式でまとめます。
mysql> select * from sys.memory_global_total;
+-----------------+
| total_allocated |
+-----------------+
| 12.43 GiB |
+-----------------+
1 row in set (0.44 sec)
sys.x$memory_global_total は、同じ情報をバイト単位で表示します。
mysql> select * from sys.x$memory_global_total;
+-----------------+
| total_allocated |
+-----------------+
| 13351482992 |
+-----------------+
1 row in set (1.39 sec)
x$ 接頭辞のないビューは、よりユーザー フレンドリーで読みやすい出力を提供することを目的としています。同じ値を未加工の形式で表示する x$ 接頭辞が付いたビューは、データに対して独自の処理を行う他のツールで使用することを目的としています。
sys.memory_global_by_current_bytes ビューは、現在のメモリ使用量を割り当てタイプ(event_name)ごとにグループ化して表示します。たとえば、memory/innodb/buf_buf_pool(InnoDB バッファプール)です。デフォルトでは、行は使用されているメモリ量の降順で並べ替えられます。
各割り当てタイプを掘り下げる前に、Innodb、sql、performance_schema などの各コード領域のメモリ使用量の概要を把握しておくことをおすすめします。
次のクエリは、現在割り当てられているメモリをコード領域ごとに集計します。
SELECT SUBSTRING_INDEX(event_name,'/',2) AS code_area,
FORMAT_BYTES(SUM(current_alloc)) AS current_alloc
FROM sys.x$memory_global_by_current_bytes
GROUP BY SUBSTRING_INDEX(event_name,'/',2)
ORDER BY SUM(current_alloc) DESC;
+---------------------------+---------------+
| code_area | current_alloc |
+---------------------------+---------------+
| memory/innodb | 11.28 GiB |
| memory/performance_schema | 520.02 MiB |
| memory/temptable | 514.00 MiB |
| memory/sql | 106.12 MiB |
| memory/mysys | 43.25 MiB |
| memory/vio | 372.15 KiB |
| memory/myisam | 696 bytes |
| memory/csv | 88 bytes |
| memory/blackhole | 88 bytes |
+---------------------------+---------------+
9 rows in set (1.02 sec)
この出力は、innodb がメモリの最大コンシューマであり、その次に performance_schema テーブルと一時テーブルが続くことを示しています。
sys.memory_global_by_current_bytes を直接クエリすると、event_name 列で説明されている MySQL 内部構造のメモリ使用量が表示されます。メモリの異常な増加をトラブルシューティングする場合は、このビューの出力を確認してソースを特定できます。
mysql> select event_name,current_alloc,high_alloc from sys.memory_global_by_current_bytes limit 5;
+-------------------------------+---------------+------------+
| event_name | current_alloc | high_alloc |
+-------------------------------+---------------+------------+
| memory/innodb/buf_buf_pool | 10.72 GiB | 10.72 GiB |
| memory/temptable/physical_ram | 514.00 MiB | 515.00 MiB |
| memory/innodb/hash0hash | 292.69 MiB | 292.69 MiB |
| memory/innodb/memory | 191.77 MiB | 197.94 MiB |
| memory/sql/TABLE | 60.39 MiB | 62.35 MiB |
+-------------------------------+---------------+------------+
5 rows in set (0.45 sec)
以下に、各割り当てタイプの詳細を示します。
割り当てタイプ |
Details(詳細) |
memory/innodb/buf_buf_pool |
InnoDB バッファプールで使用されるメモリ |
memory/temptable/physical_ram |
内部一時テーブルで使用されているメモリ |
memory/innodb/hash0hash |
InnoDB ハッシュ テーブルで使用されるメモリ |
memory/innodb/memory |
さまざまな InnoDB バッファによって使用されるメモリ |
memory/sql/TABLE |
TABLE オブジェクトとその mem ルートによって使用されるメモリ |
このビューは、ユーザーごとにグループ化されたメモリ使用量を要約します。ユーザーに複数の接続がある場合は、ユーザーのすべてのインスタンスのメモリ使用量が合計されます。
mysql> select user,current_allocated from memory_by_user_by_current_bytes;
+-----------------+-------------------+
| user | current_allocated |
+-----------------+-------------------+
| sbtest | 60.30 MiB |
| background | 2.68 MiB |
| root | 1.52 MiB |
| event_scheduler | 16.38 KiB |
+-----------------+-------------------+
4 rows in set (1.16 sec)
アプリケーション / ジョブごとに異なるユーザーを使用する場合(推奨)、このビューは、特定のアプリケーションまたはジョブがメモリを大量に消費しているかどうかを特定するのに役立ちます。
このビューは、ホスト名ごとにグループ化されたメモリ使用量を要約します。
mysql> select host,current_allocated from memory_by_host_by_current_bytes;
+------------+-------------------+
| host | current_allocated |
+------------+-------------------+
| 10.128.0.8 | 62.10 MiB |
| background | 2.70 MiB |
| 127.0.0.1 | 311.01 KiB |
| localhost | 16.38 KiB |
+------------+-------------------+
4 rows in set (1.58 sec)
このビューは、クライアント ホストごとにメモリ使用量を特定するのに役立ちます。最近では、複数のクライアント / アプリケーション サーバーを使用するのが一般的です。このビューは、特定のクライアントまたはワークロードがメモリ使用量の増加を引き起こしているかどうかを特定するのに役立ちます。また、最初に 1 つのクライアント / アプリケーション サーバーに変更をデプロイし、メモリ使用量を他のクライアント ホストと比較してメモリへの影響をモニタリングして、情報に基づいた意思決定を行うこともできます。
このビューは、スレッドごとにグループ化されたメモリ使用量の概要を表示するため、メモリを大量に消費しているスレッドを見つけるのに役立ちます。
mysql> select thread_id,user,current_allocated from sys.memory_by_thread_by_current_bytes limit 5;
+-----------+-------------------+-------------------+
| thread_id | user | current_allocated |
+-----------+-------------------+-------------------+
| 12999 | sbtest@10.128.0.8 | 3.80 MiB |
| 12888 | sbtest@10.128.0.8 | 3.75 MiB |
| 12760 | sbtest@10.128.0.8 | 3.68 MiB |
| 13128 | sbtest@10.128.0.8 | 3.67 MiB |
| 13221 | sbtest@10.128.0.8 | 3.47 MiB |
+-----------+-------------------+-------------------+
5 rows in set (2.29 sec)
特定のスレッドが大量のメモリを消費していることがわかった場合は、thread_id を使用して MySQL の遅いクエリのログまたは一般的なログをフィルタし、メモリの増加につながったセッションまたはクエリを特定できます。
メモリ使用量が多い場合のトラブルシューティングは、困難な作業です。幸いなことに、performance_schema メモリ インストルメンテーションにより、ユーザーは MySQL の内部構造でメモリがどのように使用されるかを理解できます。sys スキーマ ビューを使用すると、情報を解釈しやすくなります。メモリ関連の問題が発生した場合は、この機能を使って問題の原因を特定することができます。また、構成やクエリを変更することで同じ問題を解消できます。
次に例を示します。
MySQL インスタンスが必要以上にメモリを使用しており、サーバーのメモリが不足するまでメモリ使用量が継続的に増加し、performance_schema がインストルメント化されたメモリと合計メモリでのギャップを示すか、メモリがどこで使用されているかわからない場合は、メモリリークの兆候を示している可能性があります。メモリリークが疑われる場合は、次の手順を実施してください。