Software Transactional Memo

STM関係のことをメモっていこうと思います。

ストレージとコンフィグでデータベースのグリッチを探す

 

AIに描いてもらったストレージで作ったレース会場

 

はじめに

この記事はデータベース・システム系 Advent Calendar 2023の一日目の投稿である。今年読んだ論文(今年書かれた論文とは限らない)の中で驚きや納得があって良かったなぁと思った論文をいくつか紹介していきたいと思う。

論文の本文そのものは機械翻訳なりチャットAIなりに叩き込めば誰でも内容の抽出はできるので、こちらのブログ内では何故これが良いと思ったかについて僕の主観に基づいて書いていく。僕の解釈が厳密に正しいことは一切保障しないし、気になって読んでみたら全然内容違うやんけ!と驚くところまでがセットくらいの気軽なつもりで読んで欲しい。

最初に紹介する論文は「When Database Meets New Storage Devices: Understanding and Exposing Performance Mismatches via Configurations」である。

CPUは新製品が発売されるたびに前回より8%高速になったとかそういう細かい性能アップを刻んでいるが、市販品のストレージデバイスはここ15年ぐらいで大きな転機を2回迎えている。

ストレージデバイスの進歩と乗り遅れるDB

ハードディスクはヘッドシークに10ミリ秒ほど所要し、最高速度は100MB/sから近年のものは200MB/s程度まで高速化した(15000rpmの奴らの話は置いとこう)ここ20年ぐらい掛けてレイテンシはほぼ改善せず、連続アクセスの帯域速度だけだいたい倍化した感じである。容量やコストは劇的に改善しているが今回の話には関係ない。読み書き性能という側面だけでCPUの進化と比べるとあくびが出るような速度でしか改善していない。

SSD、中でもSATA接続なやつはデスクトップ向けにはあたかもハードディスクのような振る舞いをしながらその裏側ではフラッシュメモリという高密度な半導体に読み書きするという代物の物は10年ちょっと前ぐらいからそこらの電気屋さんでも普通に買えるようになった。HDDと通信するためのSATAケーブルで通信していたので6Gbps(=750MBps)程度が経路の帯域速度の限界であり、フラッシュメモリはともかくSSD内部のコントローラがそんな速度出なかったので400MB/sで実測のスループットが出れば上々といった感じであった。スループットだけだとHDDのひと桁倍で大した事はなかったが、レイテンシという観点では1回のアクセス所要時間が0.5ミリ秒〜0.2ミリ秒といったレベルで2桁近い高速化が起こっておりSSDの使用の有無で日常作業での体感速度は大きく改善した。古くはパソコンなんていったらちょっと触ったら固まるのが当たり前でありフォルダをダブルクリックすると内容物がちゃんと表示されるまで秒単位でマウスカーソルがカクつくのを我慢しながら待つのが常態であったが、SSD以後からはスムーズさが大幅に変わった。しかしながら所謂「プチフリ」などと呼ばれる問題が随所で囁かれ、安物のSATA SSDはなぜか時々すごく遅くなるという話があった。Flashメモリの特性によるものだともコントローラが枯れていないせいによるものだとも言われている。それでも帯域はHDDと比べて2倍、レイテンシは1/60程度になったという肌感覚である。

NVMe SSDは通信経路にSATAではなくPCI-Expressを採用しており、その上でNVM express(略してNVMe)を使って通信することによって圧倒的な高速化を果たしたものである。PCI-ExpressGPUなど高速かつ広帯域な通信を志向して設計されており、その帯域を遺憾なく発揮したストレージは帯域にして6000MB/sなどを平気で叩き出し、レイテンシは0.1ミリ秒近辺で安定するようになった。SATAと比べて帯域は10倍、レイテンシは半分。CPUの進化と比べても充分大きなステップアップと言える。

通信帯域という観点において HDD < SATA SSD < NVMe SSDという順序が成り立ち、レイテンシでは HDD > SATA SSD > NVMe SSDという順序が成り立つ。平たく言うとNVMe SSDは全面的に速く、HDDは全面的に遅いのだ。


さてデータベースとはアプリケーションの心臓部であり、サービスの速度や収容できるユーザ数を決める重要な要素であるからしてNVMe SSDを使って運用したほうが良いというのが直感的なアイデアである。しかし本当にストレージのカタログスペックだけでNVMe SSDに乗り換えるべきだろうか?実はそうとは言い切れない。例えば  https://ragainis.lt/cloud-nvmes-the-blind-side-of-them-da927d09b378 ではGCPでは特定の環境において普通のSSDの方がNVMe SSD(local-SSD)より高速であるケースについて報告している。図を引用すると以下である。

SSDベンチマーク結果

なぜこのような事が起きるかと言うと、NVMeのストレージ内に用意されたキャッシュサイズが実はそれなりに大きく(32KBとか)、例えばデータベースのページがこれより小さい単位で書き込みを続けるとパディングを埋めて再書き込みするような無駄が繰り返されてしまうと言われている。

https://dev.mysql.com/doc/refman/8.0/ja/innodb-file-space.html によるとMySQLのデフォルトページサイズは16KBのようだ。

ついでに https://www.postgresql.org/docs/current/storage-page-layout.html によると PostgreSQLは8KBらしい。

効率的にRDBMSグリッチを洗い出す

そこでこの論文では、「同一のDB」「同一のコンフィグ」「同一のベンチマーク」をHDD, SATA SSD, NVMe SSDの3つでそれぞれ走らせた時にパフォーマンスの変動幅が HDD < SATA SSD < NVMe SSD にならないケースを見つける事によって、既存のデータベースが新しいストレージデバイスの性能を活かしきれていないケースを洗い出すことができるのではないか、という仮説を検証するものである。

まずこの論文では対象とするデータベースのうち、変えた時にストレージデバイスによってベンチマーク性能が変わりそうなコンフィグ項目を洗い出した。そして、数値で指定するようなコンフィグは10倍単位でさまざまな値を試す事で網羅的に組み合わせを作り、そのマス目を埋めていった。合計でおよそ511日のベンチマーク時間の果てに複数の疑わしいケースを見つけ、それぞれに対して原因を深掘りしていったという多大な労力に恐れ入る研究である。その努力の様子が以下のテーブルに集約されている。

網羅的にグリッチを探索した結果

#Violationというのが実際に見つけたグリッチの数であり、MySQLで一番多く見つかっている(と言ってもテスト量もMySQLがぶっちぎりで多いのだが)。

内容を分析した結果、根本的な問題は3種類に分類できることを突き止めた。

  1. サイズ: 細かくfsync(つまり強制的なバッファの書き出し)をしすぎておりSSDに備わったwrite cacheを活用できていない
  2. 並列性: 前に発行したread/writeが終わらないと次のread/writeを依頼しない、というスタイルの使い方が多いとSSDに備わった並列処理性能が活かせない
  3. シーケンシャルストレージアクセス: RDBMSはHDDのシーケンシャル・ランダムの巨大なIO速度差に気を遣いすぎてシーケンシャル化に多大なCPU・メモリリソースを消費しているが、SSDではそれらの速度差が小さいので相対的に無駄が大きくなる

それぞれについて細かく見ていこう

IOサイズミスマッチ

NVMeのSSDではfsyncのペナルティは相対的に大きくなりがちで、HDDであれば倍速程度の差にしかならなかったfsyncの有無の性能差が9倍近くにまで広がる。この図は6種類のストレージについてNVMe SSD/SATA SSD/HDDでfsync有無でベンチマークを取った結果である。

ページサイズを大きくしたりするといくらか改善はできるが、ページサイズの変更はDB自体の再コンパイルや再フォーマットなどを要求するし、そもそもページという単位でデータを扱わないDB(Redis)もある。

IO並列性ミスマッチ

NVMeでは操作バッファが同時に64,000件まで受け付けられて内部で可能な限り並列に操作をこなすが、HDDやSATA SSDでは数件しか受け付けない。面白いことにMySQLでは write_io_thread の数字を増やすほどに3倍程度までTPC-Cベンチマークのスコアを上げる事ができたが実測のデバイス速度は285MB/s程度でありストレージ自体の2200MB/sには遠く及ばない速度しか出せなかった。しかもMySQLのそれは遅いデバイスに対してIOをワーカーが却下できるという状況下に置いてワーカーを増やすと却下の確率が減るという理由であった。他のDBでは例えばPostgreSQLの effective_io_concurrency などを増やしてもほとんど性能が上がらないばかりか劣化するケースまであった。

前の書き込みが終わってから次の書き込みを発行する、みたいな使い方だと64000件あるキューを埋めるのに全然足りない使い方しかできないことや、それに対応するコードを書くのが大変というのがある。PostgreSQLだと個々のエグゼキュータが内部で特定のIOパターンを発行して並列度を上げたりできるがプロセス並列性までしか引き出せないのでOSのオーバーヘッドを受けたりする。MySQLはlibaioという非同期IOライブラリを同期的に使っているので非同期化の恩恵を受けられていない。

この問題は根深く、MariaDBはio_uringに載せ替えたりしているがそれ以外の全ては最初のリリース以来ずっと同期IOエンジンまわりを改めることはなかった、なぜならどのみちfsyncを使わないと正しい永続性が保障できなかった歴史的経緯から並列性を引き出しても恩恵が限定的だった事が挙げられる。この辺は大工事になるが開発者コミュニティとしては改善の余地が大いにあることは認識しておりio_uringを使うなどの改善をしようという話もあるがSQLiteみたいに構造をシンプルに保つために受け入れないケースもある。

シーケンシャルストレージアクセス

HDDではランダム書き込みはシーケンシャル書き込みより性能が出ないので、ランダムな書き込みを纏めてシーケンシャルにすることはHDDにおいて大きな価値があった。MySQLでは innodb_change_buffering というパラメータによってインデックスに対して起きた更新をある程度の規模までバッチ化して纏めて書き出すという最適化ができるがNVMe SSDではそれをやるとむしろ性能が下がったケースすらあるというのが下の図の左端のバーである。(これw/とw/oを逆に描いている?)単にオフにするだけで良いケースは状況としてマシであるが、DBが暗黙のうちにやってるとか、オンオフできるコンフィグフラグがないケースは改善の余地があると言える。

OracleDBのグリッチも探す

コンフィグとストレージを変えてベンチマークを取るまでであればプロプライエタリRDBMSでも測定はできる。そこでOracleDBについても同様の実験を回してみると興味深い結果が得られた。COMMIT_WAITコンフィグをWAITに変えるとfsyncの頻度が増えてNVMe SSDでかえって遅くなるケースが発見されたのである。デバイスへのIOを観察すると8KB単位の書き込みが連発されておりNVMe SSDに優しくなかったとの事である。

まとめ

調査対象としたすべてのDBにおいてIOサイズやIO並列度の問題があり、シーケンシャル変換についてはコンフィグの余地すらないケースもあった。この論文の著者からDB開発者への提言は以下だ

  • レガシーなIOインタフェースを使うのをやめるべき。io_uring等でデバイスの性能を引き出そう。
  • ランダム・シリアル変換の部分などで最適化をオンオフできるようにすべき
  • SQLiteでは複数ページの書き戻しを暗黙のうちに直列化するがそれを無効にできない
  • PostgreSQLではプリフェッチにfadviceを使っているがオンオフを制御できない
  • DBコミュニティ向け17件のレポートを提出した
    • 性能を上げるのも大事だがDBMSのコードをシンプルに保ちたいというモチベーションも理解できる

初回なので少し長くなった。