今回もっとも注目されるのがHOT(Heap Only Tuple)である。ではなぜ画期的なのか、その点を調べる前に、PostgreSQLの更新処理を復習しておこう。

PostgreSQLの記憶構造は追記型

PostgreSQLでは、1つのテーブルは1つのファイルの対応する(実際には大きなテーブルは複数のファイルに分割されるが、ここでは論理的に1つのファイルだと思ってかまわない)。ファイルの中は「ブロック」または「ページ」と呼ばれる固定長の領域に分割されている。ブロックの中には複数の行が格納される。ブロックはPostgreSQLがI/Oを行う単位であり、1つの行を読み出す場合にも必ずブロックごと読み出しが行われる。書き込みも同様だ。

行が更新されると、新しい行が追加され、古い行から新しい行へのリンクが記録される。これをUPDATE連鎖(UPDATE chain)と呼ぶ。古い行がすぐに削除されないのは、PostgreSQLの重要な機能であるMVCC(Multi VersionConcurrency Control)を実現するためである。MVCCでは、あるトランザクションが参照中の行を、別のトランザクションが更新できる。そのためには、トランザクションが参照中の行は削除してはいけないわけだ。MVCCにより、トランザクションの同時並列実効性が高まるというメリットがある。

テーブルの肥大化

ここで問題になるのは、もはや誰からも参照されなくなった古い行(デッドタプル: dead tuple)をどうするかである。デッドタプルは放っておけばたまる一方なので、削除するか、次に新しい行を追加する際に再利用しなければならない。これらの処理は、PostgreSQL独自のVACUUMというSQLコマンドで対応する。

ここで困ったことになる。

VACUUMは、テーブルの中身をくまなく調べる必要があるため、テーブルの全件検索を行う。したがって、テーブルの大きさに比例した時間がかかる。そのため、非常に大きなテーブルに対して更新処理が頻繁に行われると、デッドタプルの発生にVACUUMによるデッドタプル回収が追い付かなくなり、テーブルが肥大化して、パフォーマンスが徐々に劣化していく。

インデックスの肥大化

さらに深刻なのがインデックスの肥大化である。

次のようなWebページのアクセスカウントを記録するためのテーブルがあったとする。

CREATE TABLE t1 (
          url TEXT PRIMARY KEY,        -- WebページのURL
          cnt INTEGER DEFAULT 0        -- そのアクセスカウンタ
);

url列は主キーなので、Btreeインデックスが張られている。人気のあるページは頻繁にcntが更新されるが、その際、値に変化のないurlのインデックスまで更新されてしまうのである。

PostgreSQLではインデックスもまた追記型の記憶管理を行っているので、インデックスの更新は実際にはインデックスのエントリ(インデックスタプル)の追加を意味する。人気のあるページにアクセスが100回あれば、urlのインデックスにまったく同じ内容のインデックスタプルが100個追加されるということだ。これは無駄であるばかりでなく、インデックスアクセスのパフォーマンス劣化を引き起こす。

もちろんVACUUMをかければインデックスのデッドタプルは回収されるが、前述のように、大きなテーブルに対する頻繁なVACUUMは困難であり、したがってそのテーブルに付属するインデックスのVACUUMも難しい。