PostgreSQLのIOにまつわるエトセトラ

こんにちは。寺岡です。
これは TECHSCORE Advent Calendar 2017 の14日目の記事です。

PostgreSQLのIO問題を調査する機会があり、RDBMSのIO問題を取り巻く状況についてまとめてみました。
本記事は一般的なLinux+PostgreSQL環境を対象としています。

RDBMSとACID

PostgreSQLをはじめとした多くのRDBMSはトランザクションの信頼性を強く保証するシステムです。
この特性は原子性(Atomicity) 一貫性(Consistency) 独立性(Isolation) 永続性(Durability) の頭文字を取りACIDと呼ばれています。
RDBMSにおけるACIDとは、全利用者(クライアント)が実施した操作(クエリ)が、実施された順番で完了することが保証(トランザクション、レプリケーション)されることを表します。
トランザクションのロック待ちなど、かかるコストは非常に大きく、ACID特性を保ったまま分散処理によりパフォーマンスを上げるのは非常に困難だと言われています。
※すこし前にGoogleがCloud Spannerを発表してしまいましたね。

ここからCAP定理を経由してBASEになだれ込むのが常道ですが、そこは飛ばしてRDBMSの話を続けます。

PostgreSQLの永続化戦略

PostgreSQLのテーブルに保存された行データ(タプルと呼ばれます)は8KBのブロックにまとめて順番にディスクに書き出されます。
SQLでのデータ挿入、更新、削除の単位が行である以上、この単位での保存は自然な発想に思えますが、クエリによるスキャン時のパフォーマンスにおいて、このアーキテクチャは多くの場合不利に働きます。
ディスクIOはブロック単位で発行されるため、SELECT col FROM .. など、クエリでは1列分のデータしか必要としない場合も、全ての列のデータをディスクから取得した後に余分を読み飛ばす必要があるためです。

PostgreSQLの場合、MVCCを担保するための戦略として、UPDATE対象のタプルはコピーされ、INSERTと同様にテーブルに追記される点にも気を付ける必要があります。
例え1バイトのboolean型でも、条件なしの全行UPDATEは、全行読み込み+全行書き込み+WALでIOはえらいこっちゃ~、となりますのでもう少し労ってあげましょう。

このように、行単位でデータの永続化を行うデータベースを行指向データベースと呼びます。

行指向と列指向

行指向があれば列指向もあるわけで、列指向データベースでは、永続化時、複数行を列単位に並べなおした列ブロックを永続化します。
これにより一部の列のみ取得する場合も無駄なIOを発行することなく読み込みの効率が上がります。
また、列単位でまとめるとブロック内の同一データが多くなる可能性が上がり、多くのケースで圧縮が効果的に働きます。

しかし、列単位での圧縮によるメリットを享受するには、それなりに多くの行の列データをまとめてからディスクに書き出す必要があり、RDBMSのように行単位でレコードを操作し、ACID特性を重視するシステムで列指向アーキテクチャを選択することは困難です。

仮想化、クラウド化の波

大昔はデータベースを仮想環境で動作させるなんて……みたいなことが良く言われていた気がしますが、今は数多のサーバが仮想環境で起動される時代です。
耐障害性やメンテナンス性の高さ故、RDBMSも仮想環境で動作させることが多いかと思います。
仮想環境の耐障害性を享受するために、ディスクも仮想化されることが殆どで、iSCSIやFibre ChannelなどのSANによりネットワーク経由でIO転送を行うことになります。

技術の進歩によりネットワーク性能が向上したとはいえ、スループット、レイテンシ両面において、物理的に直結される、近年進化の著しいSSD・NVMe擁する高速ストレージと比較するのは無理があります。

書き込みと読み込みとキャッシュ

RDBMSのIO処理は不利な要因が多く、パフォーマンス面で問題となるケースが多くあります。
その際、書き込み時よりも読み込みのクエリがボトルネックとなるのが一般的です。
通常のケースでは、書き込みよりも読み込みの方が、IO対象となるデータ量が圧倒的に多いのが主要因ですが、それ以外にも注意すべきポイントがあります。

OSはディスクIOの効率を上げるため、メモリ上のキャッシュを利用しています。
このキャッシュは書き込み時にも利用され、OS(カーネル)に書き込みを依頼したプロセスはディスクへの書き出しを待たずに次の処理を続けることが出来るのです。
処理特性上、読み込み時はデータを取得しないと後続処理が続行できない場合が殆どで、(たとえ非同期読み込みを行ったとしても)何らかの待ちが発生し、処理効率が上がらない場合があります。
直結された物理ストレージの場合、通常は読み込みスループットが書き込みスループットを大きく上回る点は、読み込み不利を挽回する大きなポイントでした。
また、近年PostgreSQLではPararellScanなどの実装により、読み込みを並列化して処理効率を上げる試みが続けられています。

一転、INとOUTのスループットに差がないネットワークがボトルネックになってくると、読み込みの処理の非効率は大問題になり得ます。
IOが詰まってしまっては読み込み並列化の効果も期待できません。

PostgreSQLのキャッシュ戦略

PostgreSQLでは自身の管理下にあるSharedBufferと、OSの管理するPageCacheの二段構えのキャッシュ構成を取ります。
クエリ実行時間の最悪値を知りたい場合、下記のコマンドでOSのPageCacheをクリアした上、

更にPostgreSQLの再起動などにより、SharedBufferをクリアする必要があります。
「あれー、このクエリさっき遅かったのに今では速攻で返ってくるのはなんでだー」
と嘆く同僚を見かけた際は、こっそり教えてあげてると良いでしょう。

弾丸

今回はクラウド時代のRDBMSでボトルネックとなりやすい、IOに関しての問題要因をまとめました。

実は、これらの問題には銀の弾丸が存在します。
お金を積んでメモリに乗せる。……おっと、これでは金の弾丸ですね。

次回があれば、テーブルのチューニングの主役、インデックスについてまとめたいと思います。

Comments are closed, but you can leave a trackback: Trackback URL.