2021年5月6日木曜日

ディスクフルは危険かも

(7/3追記)以下の話題に加えて色々見つかったものも含めてディスクフル関連の問題は内部開発ツリーではとりあえず全部修正済みにできたので次かその次のリリースでは全部直ってると思います。(5.7,8.0共に)

今回は性能の話ではありません。MySQLがディスクフルで詰む可能性についてです。

とあるバグ(また新機能入れて共通パスを変更したせいで…既存に影響するのは入れなければバグ減るのに…)をきっかけに最近いろいろ調べていましたが、 一応情報共有したほうが良いと思うので可能と思われる限り共有します。 しかし、今後の対応は議論中でチームとして会社としての方針とは異なる可能性があるので、 現状を説明するに留めます。

しかしそもそも、ディスクフルまで使う容量設計は元来非常にマズいので 誰の環境も該当していないと思いますが、一応順を追って(遡って?)説明します。

とはいえ基本的な利用では問題は起きないはずなのです。

データファイル作成/拡張時にディスクフルで失敗なら、その作成/拡張の原因となるSQLがエラーで返る。 これが基本動作です。サービスが落ちることは無いです。

しかし、ファイル作成/拡張ではエラーが起きなかった場合は処理はそのまま通り、 その処理のトランザクションログは生成されCOMMITも成立しますが、 データページをWriteする時にENOSPCエラーとなる場合があります。

データページのWriteがどうしてもできない場合に、InnoDB は Abort するしかありません。 (ページを書き出せないのでその変更LSNまでのチェックポイントができず、 ログの上書きができずにどうせ止まってしまうので。傷口が拡がる前に止まる。 ということかと思います。)

そうしてAbortしても、ディスクフルで書けない訳なので、 そのままリカバリしても同じ理由でWriteエラーでAbortしてリカバリできません。

ストレージを拡張するか大きなものに移動してのリカバリができないのなら、ここで詰みます。 データの整合性も、お終いです。

というわけで、 ストレージを拡張するか大きなものに移動できない状況(そんなカツカツな状況があるか知らんけど)で、 クラッシュしても100%データの整合性を確保するためには、(というかそもそもクラッシュしないように) Write IO が ENOSPC を起こさないように使う必要があります。

何が ENOSPC の大元かというと、Sparse File です。 ファイルサイズは確保するけど、間欠的に内容を破棄してその分の容量を空き容量に回すことができますが、 破棄した空き領域に書き込む際に容量を食い、足りない場合に ENOSPC が発生します。 もちろんOS/FSがサポートしなければ使われないのですが、 Linuxでいうと、xfs、ext4 はサポートされているので対象です。 なので、利用法で避けることになります。

<ストレージ容量カツカツ(もう替えも足しもない)の場合に安全のため避ける設定>

(1) InnoDBの透過的ページ圧縮(表定義で COMPRESSION="zlib" とか指定するやつ)

ページ単位で圧縮して書き込み、空いた分の内容を破棄して空き領域に返す仕組みです。

容量を節約するのに使うと思いますが、容量が足りない場合は危ないです(矛盾?!)

既存圧縮ページよりも圧縮率が悪くなるページの上書きは空き容量を必要とするので ENOSPC が発生するかも知れません。

さらに、表単位のこの圧縮の設定はデータディクショナリにあるので、 そのフラグはリカバリ中(ディクショナリもリカバリ中)には参照できず、、、

リカバリ中に変更が発生したページはすべて非圧縮に戻ってしまいます。(更に空きが足りなくなる!)
※この対応は議論中です。簡単な変更で実用レベルに軽減はできると思います。

(2) AUTOEXTEND_SIZE= 指定の表(ibdファイル)の作成

どういうわけか、小さな表が(死ぬほど)多い場合の節約のためか、 ibdファイル作成時の最初のサイズ分は 未書き込みのページは 空き領域に返されます。 (ちなみに拡張時はSparseにはしないので安全です。)

とはいえデフォルトでは、初期ファイルサイズは数ページで、そのような小さなサイズでは問題は起きません。 が、8.0.23 でInnoDBでも効くようになった AUTOEXTEND_SIZE= 指定を大きく(max 64M)すると、 最初のファイルサイズもそのサイズで確保され、最初の未書き込みぶんがまるごと空き領域に返されるので(拡張時は大丈夫。念のため)、 ディスクフル近傍で表を作ったり書いたりを繰り返してると空き領域がショートして落ちるかも知れません。
(e.x. AUTOEXTEND_SIZE=64M とかで空の表をディスクフル近くまで沢山作成しても、全部の表を64Mまで埋める前に落ちる。)
※この対応も議論中というかレビュー中です。これは普通に空き領域に返さない様に直しても困る人は居ないと思う。。。のだが。。。

以上でした。

基本的には以上なのですが、 詳しくは言えませんが、そもそもディスクフルまで使うのは 特に 8.0.23、8.0.24 では止めたほうが良いと思います。(8.0.25で直る予定) 万が一そこでクラッシュすると、リカバリでデータが壊れる可能性があるので。。。