🧩

Google Cloud SQL for MySQLがダブルバッファーライトをやめて16KiBのアトミックなダイレクトI/Oを実現するまで

2022/12/11に公開

まとめ

AWSはre:Invent 2022でRDS MySQLの書き込みスループットが最大で2倍になる機能を発表しました。

x86_64アーキテクチャの場合、1ページの大きさは4KiBです。
複数ページにまたがるデータをflush中に障害が起こると、一部のページだけが書き込み成功することがあります。この状態を torn write と呼びます。
torn writeを防ぐために(torn write prevention;TWP)、MySQLにはダブルバッファーという仕組みがあり、ダーティーページをまずダブルバッファーに書き込み、その後、flushします。
flush時にtorn writeが起きたときは、ダブルバッファーからリカバリできます。

一方で、ダブルバッファーを利用するとI/Oが増えるため、書き込みスループットが伸びません。

ストレージスタックを改善し、MySQLがダブルライトバッファーを経由せずに16KiB単位でアトミックに書き込めるようにしたのが本機能です。

2018年のGoogle Cloud Nextにおいて、技術的に同等の機能を要するCloud SQL 16 KiBインスタンスの取り組みが発表されています。当時の発表から性能改善の背景を探ります。

Optimizing performance on Cloud SQL for MySQL (Cloud Next '18)

動画

https://www.youtube.com/watch?v=gIeuiGg-_iw

概要

The doublewrite buffer is a storage area where InnoDB writes pages flushed from the buffer pool before writing the pages to their proper positions in the InnoDB data files. If there is an operating system, storage subsystem, or unexpected mysqld process exit in the middle of a page write, InnoDB can find a good copy of the page from the doublewrite buffer during crash recovery.

並列度が高い環境下で書き込みがスケールしない

512スレッドでSysbenchのOLTPワークロードを流すと、TPS(transaction/second)の性能で偏差が大きく、前半では3倍、後半では10倍程度と振れ幅が大きい。

キャパシティプランニングが難しく、TPSが大きく落ちている後半では、大量のタイムアウトが発生しており、データベースが使い物にならない。

問題はflushの遅延

MySQLは以下の流れでトランザクションを書き込む

  1. トランザクションをREDOログにWRITE
  2. データベースページにWRITE
  3. データベースページを非同期にデータファイルにWRITE(flush)

REDOログとflushしたデータのサイズの差(flushラグ)をプロット

※ キャプチャは動画から

REDOログが大きくなりすぎるとリカバリ時間に影響するため、REDOログには上限(グラフの赤線)があり、ベンチマークはこの上限付近でサチっている。

この状態はsync flushモードと呼ばれる。
新規WRITE処理を停止し、古いダーティーページをflushし、REDOログの空きを作ったあとで、止めていたWRITE処理を再開する。

TPSが落ちているのはこのsync flushモード。

ボトルネックであるInnoDB Double Writeを無効化したのが次のグラフ

※ キャプチャは動画から

sync flushモードに陥らなくなり、トランザクションを滞りなく処理できるようになった。

flushラグを制御

新規書き込みよりも速いペースでダーティーページをflushできればラグは発生しない。

flushレートを上げるテクニックの一つがInnoDB Double Write Bufferの無効化

InnoDB Double Writeとは?

InnoDB Double Writeは”Double Write”の名前の通り、連続して2回書き込む。1回目がダーティーページからダブルライトバッファー、2回目データファイルへの書き込み。

なんでこんなめんどくさいことをしているかというと torn write 対策のため。

ストレージのブロックサイズより大きなサイズでflushする際の障害により、一部のページのみ書き込みが成功することがある。
この状態を"torn write"と呼ぶ。
データ不整合が起きているため、REDOログをリプレイできない。
このような障害が起きたときは、ダブルライトバッファーをソースにリカバリする。

Torn Writeが起きるのはなぜか?

Torn Writeが起きる理由は2つ

  • ストレージ・レイヤー:
    • ブロックサイズは4KiB
    • 16KiBをアトミックに書き込みできない
  • ファイル・レイヤー:
    • ブロックサイズが4KiB
    • ストレージのブロックサイズが16KiBでも、ファイルシステムが16KiBのboundaryでallignされていないと、複数のブロックにまたがってアトミックに書き込めない

ストレージスタックend-to-endで16KiBのアトミックな書き込みに対応

I/Oを減らすためにInnoDB DoubleWriteを無効化しても問題が起きないようにするために、ストレージスタック全体で16KiBをアトミックに書き込めるようにする

※ キャプチャは動画から

スタックの下から上に順に紹介

  • ディスク(Googleの場合はPersistent Disk;PD)
    • ブロックサイズを16KiBにする
  • フィルシステムを16KiB
    • EXT4のBigAllocを使い、16KiBバウンダリーでアライン
  • Container Optimized OS
    • Linuxカーネルのアップストリームの変更で壊れないようにテスト追加
  • virtioレイヤー
    • I/O設定の調整
  • InnoDB
    • O_DIRECT(ダイレクトI/O)を使って書き込み

Cloud SQLが動作するCOSの改善

Cloud SQL VMはContainer-Optimized OS(COS) のイメージで動作

Chromium OSがベース。以下の理由でリリースサイクルが短い

  • セキュリティフィックス
  • 機能追加

Linux Kernelのtorn write対策

16KiBデータベースページがインスタンスやハードウェアの障害のためにtorn writeにならないために次の2点を保証

  • データベースファイルは16KiBのチャンク(「クラスター」と呼ぶ)でレイアウト
  • IOリクエストを複数に分裂させない

Ext4 BigAllocの活用

BigAllocというExt4ファイルシステムの機能を利用

この機能はもとはGoogleのGFSのような大規模なクラスターファイルシステムでの書き込みパターンを最適化するためのもの

ファイルシステムのブロックサイズはページサイズより大きくできない
x86_64アーキテクチャの場合、このページサイズは4KiB

4KiBのブロックを2のべき乗個でひとまとまりにした「クラスター」という概念を導入(16KiB = 4Kib * (2^2))
データファイルはクラスター単位でアロケートされる

ファイルシステムは書き込みを16KiBのクラスターでトラック

BigAllocを実践導入させるには?

BigAllocはKernelのメインストリームに2011年から存在するが、限定された用途でのみ用いられてきた

Cloud SQLに導入するために、2点を決めた

  • オンライン・リサイジング対応
    • DBはどんどん容量が増える
    • Persistent Diskを拡張できること
  • 広範囲な回帰テストを追加
    • BigAllocが登場してから年月が経つが、あまり使われてこなかった

IOスタックをaudit

歴史的に、Linuxを含む多くのOSは効率化のために、複数の連続するセクタへのI/Oリクエストをマージして処理

マージした結果、SCSIコントローラーやSCSIディスクの制限に引っかかる場合、制限を回避するために、リクエストを分割してきた

IOスタックの各レイヤー

  • ファイルシステム
  • Linuxブロック
  • Linux SCSIレイヤー
  • Vertio SCSIドライバー

において、これまでのように、分割しないこと、仮に分割する場合は16KiBで分割するかあらゆるコードパスを後半にauditした。

ゲストカーネルをテスト

  • Linuxカーネルが今後変更されてもauditしたことを保証したい
  • COSカーネルのビルド・リリースサイクルに回帰テストを追加

関わったチームは多岐にわたる

Google内の

  • Cloud SQL
  • COS
  • Persistent Disk
  • GCE Virtualization

など様々なチームとの連携の上で成し遂げられた

AWS RDSの書き込み最適化について

AWS RDSの最適化書き込みについて、関連するドキュメントを確認します。

ストレージ・ファイルシステムは次のEC2のドキュメントが該当します。

Improving write performance with Amazon RDS Optimized Writes - Amazon Relational Database Service

torn write問題やdouble bufferを無効化した場合のメリットが記載されています。

With torn write prevention, data is written to storage in all-or-nothing write transactions, which eliminates the need for using the doublewrite buffer. This prevents partial, or torn, data from being written to storage in the event of operating system crashes or power loss during write transactions. The number of transactions processed per second can be increased by up to 30 percent, and write latency can be decreased by up to 50 percent, without compromising the resiliency of your workloads.

ストレージのブロックサイズとして4KiB以外の8KiBと16KiBも選択できるようになっています。

キャプチャはEC2ドキュメントから

Requirementsの動作検証環境を確認すると、

  • ext4 with bigalloc enabled
  • O_DIRECT file access mode to bypass Linux kernel buffer cache.

などGoogle Cloud SQLと同じですね。

続いて、RDSのドキュメントを確認します。

Improving write performance with Amazon RDS Optimized Writes - Amazon Relational Database Service

次の箇所からNitroでハードウェア(ストレージ)レベルから手が入っていることがわかります。

These databases run on DB instance classes that use the AWS Nitro System. Because of the hardware configuration in these systems, the database can write 16 KiB pages directly to data files reliably and durably in one step. The AWS Nitro System makes RDS Optimized Writes possible.

なお、RDS Optimized WritesはデフォルトでAUTOに設定されており、利用可能な環境下では、自動的に有効化されます。

具体的には、以下の要件が必要です。

  • RDS MySQL 8.0.30 以降を利用
  • インスタンスタイプが db.r6i または db.r5b

Fusion-IOはdublewrite bufferが無効化される

MySQLドキュメントにあるように、doublewrite bufferが Fusion-io 上にある場合、同機能は無効化され、Fusion-ioのatomic write機能が使われます。

If the doublewrite buffer is located on a Fusion-io device that supports atomic writes, the doublewrite buffer is automatically disabled and data file writes are performed using Fusion-io atomic writes instead.

参考

Discussion