概要

最近、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_ramtemptable_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テーブルに書き込むようになる。
つまり、遅くはなるけど処理は継続される。

MySQL Community 8.0.28

Aurora3の挙動

Aurora3ではどうなるかというと、ライターとリーダーで挙動が異なる。
ライターはMySQL8のデフォルトの挙動と同じになるが、リーダーの場合は temptable_max_mmap の値を超えると /tmp/xxx is full!とエラーが出力されてクエリが失敗する。
temptable_max_ramtemptable_max_mmap はデフォルト1GiBなので、合計2GiBを超えるようなクエリは失敗して終了してしまう。

Aurora MySQL the replica

原因については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_ramtemptale_max_mmap の値をパラメータグループで調整したほうが良いという結論に至っている。
また本件はAWSサポートにも確認して、上記を教えていただいている(丁寧に教えてもらって感謝している)

パラメータ

では temptable_max_ramtemptable_max_mmap はどれくらい割り当てたほうが良いかという点だが、インスタンスサイズに応じて割り当てていくことになると思われる。
ローカルストレージエンジンについても、下記でインスタンスクラスごとに異なり、FreeLocalStorage などのメトリクスを見比べながら割り当てるのが良さそう。

https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/AuroraMySQL.Managing.Performance.html#AuroraMySQL.Managing.TempStorage

余談だが、パラメータグループの変更について、パラメータグループそのものを変更する場合はインスタンスの再起動が必要になる。
ただし、パラメータ自体はパラメータごとに異なり、temptable_max_ramtemptable_max_mmapはいずれもdynamicなので、再起動なしに適用可能となる。

まとめ

問題に当たったときはどうしたものかと思ったものの、調べてみると納得と、あと正直とてもおもしろいとも思った。
特にmmapを使ったメモリマップトファイルみたいな概念は、そもそもMySQLの仕様ではなくて、Linuxのシステムコールの話なので、改めてLinuxの仕組みそのものを勉強しようという良い機会になった。
とりあえず少しは理解はできたと思い勉強のために残したが、誤っているところがあればぜひコメントをいただけるととても助かります。

Use the TempTable storage engine on Amazon RDS for MySQL and Amazon Aurora MySQL

Amazon Aurora アーキテクチャ概要

Aurora MySQL version 3でTempTable溢れの振り返り

最近の MySQL の Internal Temporary Table 動作まとめ (version 8.0.28 版)

カテゴリー: AWSRDS