VACUUM不要の新アプローチ「zHeap」
(EDBチーフアーキテクト、Robert Hass)

Chief Architect, Databese Server
もしPostgreSQLでVACUUMの必要がまったくなくなったらどうでしょうか?これはなかなか想像し難いことのようです。結局のところ、PostgreSQLはマルチバージョン コンカレンシー コントロール(MVCC)を使用しています。従って複数の行バージョンを作成する場合、最終的に何らかの形で不要行バージョンを削除する必要があります。 PostgreSQLでは、VACUUMがそれを確実に実行しており、オートバキュームプロセスのおかげで、それが遅滞なく行われているわけです。とは言え、全てのリレーショナル・データベースが、同じ方法でMVCCを実現しているわけではないという事実からも明らかなように、他の方法を取ることも可能です。PostgreSQLが新しいアプローチを採用することで、大いに恩恵を得ることができる可能性もあるのです。実際、EnterpriseDBの私の同僚の多くは、新しいアプローチの実装に忙殺されております。今日は私たちが何をやっており、なぜそれをやっているのかについてお話ししたいと思います。
VACUUMの課題
VACUUMが長年にわたって大幅に改善されたことは間違いありませんが、現在のシステム構造では解決が困難ないくつかの問題があります。古い行バージョンと新しい行バージョンが同じ場所に格納されているため、大量の行の更新が行われると、ヒープとして知られるテーブルは、一時的にしろ拡大してしまいます。更新パターンによっては、後でヒープを簡単に縮小することができない場合があります。たとえば、多数の行をテーブルにロードし、各ブロックの行の半分を更新するとします。テーブルのサイズは、新しい行バージョンを格納するために50%増加させる必要があります。 VACUUMがこれら行の古いバージョンを削除すると、元のテーブル・ブロックの50%が使用されている状態となります。残りのスペースは新しい行バージョンで使用できますが、新しく追加されたブロック上の行を、古いブロックの利用可能な半分のブロックに移動する簡単な方法はありません。VACUUM FULLを使用することも、pg_repackなどのサードパーティのツールを使用することもできますが、どちらの方法でもテーブル全体を書き直すことになります。これまでも、その場で行を再配置しようという提案がなされてきましたが、行の移動の度に、行の新しい位置を示す個々のインデックスのエントリーが必要となるため、正しく実行するのは難しく、インデックスを肥大化させる危険性があります。
ヒープの肥大化が1つの大規模なアップデートによって引き起こされる場合、大規模なアップデートを一連の小さなアップデートに分割し、その間にVACUUMを実行するか、オートバキュームを使うことでそのような事態は回避できるかもしれません。このようにして、最初のアップデートによって生成された古い行バージョンは再利用され、2番目の更新で再利用できる空き領域になり、肥大化が減少します。ただし、1つの大規模な更新が原因ではなく、長期間にわたる多数の小さな更新の積み重ねが原因で、テーブルが肥大化するアクセスパターンもあります。簡単な例は、単一行のUPDATEを実行してから長時間アイドル状態にしたままでトランザクションを開くことです。他のトランザクションは大なり小なりデータベースへの書き込みを継続します。どのテーブルが更新されても、肥大化につながり、あとからこれを縮小させる簡単な方法はありません。はっきり言って、書き込みトランザクションが開いたまま、長時間アイドル状態のセッションが残るということは決して良いことではありません。そのような状況が発生するということは、オープン中のトランザクションを管理がきちんとできていない設計不足なクライアント・アプリケーションによるものです。このようなワークロードのもとでは、どのようなリレーショナルデータベースでも問題が生じます。PostgreSQLがこのような状況にきちんと対処できないことを責めるのは酷ではありますが、問題が生じてからの復旧はさらに苦痛です。長時間実行されるレポートクエリでも同様の問題が発生する可能性があります。
逆に言うと、PostgreSQLのVACUUM実装は、より迅速かつ少ない労力で、デッドタプルが占める領域を再利用する点においてますます良くなっています。それは本当に良いことです。なぜなら、スペースを再利用する速度が速くなると、新たに割り当てるスペースが少なくなり、テーブルが小さくなりパフォーマンスが向上するからです。しかし、上記の例では、VACUUMが問題の全てではないことを示しています。これらの例では、デッドタプルが占有する空間を再利用できる最も早い時点で、かつ、超高速にVACUUMが実行されたとしても、やはりテーブルは肥大化します。1つの長時間実行されているトランザクションが開いたまま多くの短いクエリが実行され、肥大化するようなケースでは、よりスマートなスナップショット管理によって、最悪の肥大化のケースでも約2倍に抑えることができます。つまり、タプルのバージョンを古いスナップショットと現在のバージョンで確認できるようにし、中間バージョンを破棄するわけです。これは現在のPostgreSQLにはできない芸当です。しかし、2倍というのも大きな数字ですし、オープンスナップショットが複数ある場合はどうでしょう?さらに、テーブル全体に分散したブロックの更新をかけるSQLステートメントによって肥大化が生じた場合、VACUUMをいかに改善しても役に立たないでしょう。 SQLステートメントが終了するまでには、すでにダメージを受けているからです。
したがって、ある意味では、VACUUMの欠点による肥大化を責めることは、ゴミ屋敷化したあなたの家について、地元のごみ収集業者を責めるようなものです。確かにごみ収集業者がもう少し頻繁にやって来るか、より速やかにテキパキとゴミを回収してくれれば助かりますが、おそらく問題の一部はあなた自身です。ごみ収集業者のサービスが極端に悪くない限り、いつもゴミ屋敷になってしまうのは、あなたが膨大なゴミをものすごいスピードで生み出しているからです。膨大なゴミを投げ捨てることを止めれば、ごみ収集業者が来る頻度は重要ではありません。あちこちに分散したゴミ箱ではなく、1個の巨大なゴミ箱にすべてのゴミを捨てていれば、まだ少しはましでしょう。
この問題の原因は、PostgreSQLが本当のトランザクションUPDATEをその場で行うことができないということにあります。実際のところ、UPDATEはDELETEとINSERTを合わせたような感じです。テーブルに空きスペースがなく、1つのタプルを更新するトランザクションを実行すると、UPDATEトランザクションがコミットまたはアボートするかどうかにかかわらず、2つのことが起こります。まず、新しい行バージョンを格納するためにテーブルを拡張する必要があり、次に、古い行バージョンがデッドとなります。トランザクションがコミットされれば古いバージョンがデッドに、トランザクションがアボートされれば新バージョンが即座にデットになります。どちらにしてもテーブルは少し肥大化し、VACUUMの出番となります。
このシステムは非常に対照的です。コミットするトランザクションは、アボートするトランザクションとほぼ同じ量の作業を生成します。これは非常にエレガントではありますが、最善とは言えません。トランザクションの50%近くがアボートされるようなワークロードを実行する人はほとんどいないからです。(そのような場合は、PostgreSQLが競合他社よりも優れた性能を発揮するでしょう。)コミットを安く実行でき、アボートすると高くつくシステムを持つ方が良いでしょう。
zHeapによる新たなアプローチ
そこで、EnterpriseDBが提案している設計となるわけです。私たちはzHeapと呼ばれるPostgreSQL用の新しいテーブルストレージフォーマットを構築しようとしています。 zHeapは、可能な場合はいつでも、古い行バージョンをUNDOログに移動し、古いものが以前に占有していた場所を新しい行バージョンで置換することによって、UPDATEを処理します。トランザクションがアボートされた場合、元の行バージョンをUNDOログから取り出し、元の場所に戻します。コンカレントトランザクションが古い行バージョンを表示する必要がある場合は、UNDOログを探します。もちろん、ブロックがいっぱいになり行が広がっている場合動かなかったり、他にもいくつかの問題がありますが、多くの有益なケースをカバーしています。一括更新でさえ、zHeapを強制的に拡大させることはなく、UNDOが拡大します。トランザクションがコミットされると、デッドとなる行バージョンはすべてzheapではなくUNDO上にあります。
これは、VACUUMまたは同様のプロセスが、テーブルをスキャンして不在行を探す必要がないことを意味します。テーブルにデッド行が含まれている唯一のケースは、トランザクションが異常終了した場合です。この場合、UNDOを使用して、まだ生きている行バージョンを戻します。そのプロセスだけが対象となり、テーブル全体をスキャンする必要はありません。トランザクションがコミットされると、生成された UNDO は破棄されます。これはバルク操作であり、非常に迅速に行えます。
インデックスの扱いはより複雑ですが、UNDOインフラを多く使用することで、インデックスのバキューム処理の必要がなくなると考えています。しかし、この話題は長くなるので、別のブログ記事で説明したいと思います。zHeapの設計詳細についても、書きたいことは山ほどありますが、そちらについても別の機会とし、この投稿では、私たちが何をしているのかではなく、なぜこの仕事をしているのかを説明するにとどめます。
PostgreSQLの経験豊富なハッカーたちは、このアプローチの実現可能性について、懐疑的な意見も多いようです。私たちがすべての問題を解決したとも、成功が保証されいるなどとも主張する気はありません。膨大な量の作業が残っています。その作業のすべてが正常に完了し、さらに残る問題のすべてが解決されたとしても、既存のヒープの方がzheapを上回るケースもあるかもしれません。とは言え、私たちはこの新しいシステムのかなりの部分をすでに構築しており、現状のヒープに対するテストも実施し、期待できる結果を得ています。近い将来、これまでに行ったコードをリリースし、PostgreSQLコミュニティ全体でこれを検討し、独自の意見を出し、独自のテストを実行できるようにしたいと思います。
私は基本的な設計作業を行いましたが、このプロジェクトの開発責任者は Amit Kapilaで、Dilip Kumar、Kuntal Ghosh、Mithun CY、Ashutosh Sharma、Rafia Sabih、Beena Emerson がアシストしてくれました。Thomas Munroは、UNDOストレージシステム、Marc Linsterはアンフェイリング マネジメントサポート、Andres Freundはデザインインプット(およびクリティシズム)を担当してくれました。ありがとう。
Robert Hassは、EnterpriseDBデータベースサーバー担当副社長兼チーフアーキテクトです。この記事はロバートの個人ブログからの転載です。
[出典:米国EDB公式ホームページ]
▼ 出典URL
https://www.enterprisedb.com/blog/do-or-undo-there-no-vacuum