概要
最近、Aurora2(MySQL5.7)だったクラスターをAurora3(MySQL8.0)にアップグレードしたところ、特定のクエリだけが失敗するという事象があった。
ClowdWatchLogsに出力していたerrorログには /tmp/xxx is full!
と出力されていた。
Aurora2(MySQL5.7)の時代にはなかったことだったので、Aurora3(MySQL8.0)によるものであることはわかり、それが新しく採用されたTempTableストレージエンジンによるものであることがわかった。
TempTableストレージエンジンとはどういったものか、Aurora3の仕様もあわせて色々わかったので記録をしておこうと思います。
最初に結論
Aurora2からAurora3にアップグレードする際には temptable_max_ram
と temptable_max_mmap
の値を必ず確認して、必要に応じて調整したほうが良い。
TempTableストレージエンジンとは
詳細な説明は下記を参照してもらうとして、ざっくり説明すると内部一時テーブルを使うようなクエリを実行した場合に、MySQL5.7のときにつかっていたInnoDBテーブルではなくメモリを利用したストレージエンジンとなり、5.7のときよりも効率的にクエリを処理することができるようになったストレージエンジンのことを指す。
https://dev.mysql.com/doc/refman/8.0/ja/internal-temporary-tables.html
TempTableストレージエンジンでは temptable_max_ram
を上限としてオンメモリで処理されて、それを超えると、temptable_max_mmap
を上限として、mmap()システムコールにより、ローカルストレージにファイルを作成して、仮想メモリにマッピングを行う。
ページサイズ単位(Linuxではデフォルト4KiB)でメモリ上にデータがロードされるので、InnoDBテーブルに変換される場合よりも、効率的(可変長)で高速に処理される。
では、temptable_max_mmap
を超えたサイズのデータをクエリするような場合はどのような挙動になるか。
MySQL8.0の挙動
Aurora3に関わらず、MySQL8.0の仕様では、temptable_max_mmap
を超えると、InnoDBテーブルに書き込むようになる。
つまり、遅くはなるけど処理は継続される。
Aurora3の挙動
Aurora3ではどうなるかというと、ライターとリーダーで挙動が異なる。
ライターはMySQL8のデフォルトの挙動と同じになるが、リーダーの場合は temptable_max_mmap
の値を超えると /tmp/xxx is full!
とエラーが出力されてクエリが失敗する。
temptable_max_ram
と temptable_max_mmap
はデフォルト1GiBなので、合計2GiBを超えるようなクエリは失敗して終了してしまう。
原因についてはAWSサポートにも確認してわかっているが、まずAuroraは共有ストレージを採用しており、
そして、リーダーインスタンスは共有ストレージを参照は可能だが、書き込みはできない。
内部一時テーブルを作成するのにあたり、ライターは問題にならないがリーダーインスタンスで、MySQL5.7のときは共有ストレージではなく、ローカルストレージにMyISAMとして書き込みを行う仕様だった。
リーダーインスタンスでは、オンディスク一時テーブルはローカルストレージを使用する MyISAM ストレージエンジンを使用します。これは、読み取り専用インスタンスでは Aurora クラスターボリュームにデータを保存できないためです。
https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/AuroraUserGuide/AuroraMySQL.CompareMySQL57.html
MySQL8.0.16以降、内部一時テーブルのストレージエンジンを指定していた internal_tmp_disk_storage_engine
が廃止されて、デフォルト InnoDB
となった。
Auroraでもこの仕様に則り、Aurora2では internal_tmp_disk_storage_engine
が適用され、Aurora3では internal_tmp_mem_storage_engine
が適用されるようになった。
パラメータグループをみても、Aurora2のときにあった internal_tmp_disk_storage_engine
は Aurora3では見当たらず、かわりに internal_tmp_mem_storage_engine
のみが存在する。
https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/AuroraUserGuide/AuroraMySQL.Reference.ParameterGroups.html
internal_tmp_mem_storage_engine
はメモリを使った内部一時テーブルの処理のみとなり、デフォルトが TempTable
で、MEMORY
にも変更は可能である。
結果、リーダーインスタンスはローカルストレージには書き込みができなくなり(InnoDBへのフォールバックがされず)、メモリのみが使用可能となっている。
以上から、Aurora3では temptable_max_ram
と temptale_max_mmap
の値をパラメータグループで調整したほうが良いという結論に至っている。
また本件はAWSサポートにも確認して、上記を教えていただいている(丁寧に教えてもらって感謝している)
パラメータ
では temptable_max_ram
と temptable_max_mmap
はどれくらい割り当てたほうが良いかという点だが、インスタンスサイズに応じて割り当てていくことになると思われる。
ローカルストレージエンジンについても、下記でインスタンスクラスごとに異なり、FreeLocalStorage
などのメトリクスを見比べながら割り当てるのが良さそう。
https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/AuroraMySQL.Managing.Performance.html#AuroraMySQL.Managing.TempStorage
余談だが、パラメータグループの変更について、パラメータグループそのものを変更する場合はインスタンスの再起動が必要になる。
ただし、パラメータ自体はパラメータごとに異なり、temptable_max_ram
とtemptable_max_mmap
はいずれもdynamic
なので、再起動なしに適用可能となる。
まとめ
問題に当たったときはどうしたものかと思ったものの、調べてみると納得と、あと正直とてもおもしろいとも思った。
特にmmapを使ったメモリマップトファイルみたいな概念は、そもそもMySQLの仕様ではなくて、Linuxのシステムコールの話なので、改めてLinuxの仕組みそのものを勉強しようという良い機会になった。
とりあえず少しは理解はできたと思い勉強のために残したが、誤っているところがあればぜひコメントをいただけるととても助かります。
Use the TempTable storage engine on Amazon RDS for MySQL and Amazon Aurora MySQL
Aurora MySQL version 3でTempTable溢れの振り返り
最近の MySQL の Internal Temporary Table 動作まとめ (version 8.0.28 版)
<p style='padding: 5px;'>