第6章 性能向上に関する技法

目次

6.1. 大きなジオメトリを持つ小さなテーブル
6.1.1. 問題の説明
6.1.2. 応急処置
6.2. ジオメトリインデクスでCLUSTERを実行する
6.3. 次元変換の回避
6.4. コンフィギュレーションのチューン
6.4.1. スタートアップ
6.4.2. 実行時

6.1. 大きなジオメトリを持つ小さなテーブル

6.1.1. 問題の説明

現版のPostgreSQL(8.0を含む)では,TOASTテーブルに従うクエリオプティマイザの弱さに苦しみます. TOASTテーブルは,(長いテキスト,イメージ,多数の頂点を持つ複合ジオメトリといった)通常のデータページに適合しない,(データサイズという意味では)巨大な値を納めるための「拡張部屋」の一種です.詳細情報は http://www.postgresql.org/docs/8.0/static/storage-toast.html をご覧ください.

高解像度で全てのヨーロッパの国の境界を含むテーブルのような)大きなジオメトリがあるうえ,行がそう多くないテーブルを持つようになると,この問題が出てきます.テーブル自体は小さいのですが,多くのTOASTスペースを使います.例として,テーブル自体は概ね80行で3データページしか使わなくてもTOASTテーブルで8225ページを使うとします.

ここで,ジオメトリ演算子の&&を使って,ほとんどマッチしないようなバウンダリボックスを検索するクエリを出してみます.クエリオプティマイザにはテーブルは3ページ80行しかないように見えます.オプティマイザは,小さなテーブルを順に走査する方がインデクスを使うよりも早いと見積もります.そして,GiSTインデクスは無視すると決めます.通常なら,この見積もりは正しいです.しかし,この場合は &&演算子が全てのジオメトリをディスクから呼び出しでバウンディングボックスと比較しなければならなくなり,ゆえに,全てのTOASTページもまた呼び出す必要があります.

このバグに苦しむかどうかを見るには,PostgreSQLの"EXPLAIN ANALYZE"コマンドを使います.詳しい情報と技術に関する詳細については,postgres performance mailing list のスレッド ( http://archives.postgresql.org/pgsql-performance/2005-02/msg00030.php ) をご覧下さい.

6.1.2. 応急処置

PostgreSQLコミュニティでは,TOASTを意識したクエリ見積もりを作ることで,この問題を解決しようとしています.今のところは,二つの応急処置があります.

一つ目は,クエリプランナにインデクスの使用を強制することです.クエリを発行する前に"SET enable_seqscan TO off;"をサーバに送信します.これは基本的にクエリプランナに対して可能な限り順に走査することを避けるよう強制します.そのためGiSTインデクスを通常使うようになります.しかし,このフラグは接続するたびに設定しなければならず,他のケースにおいてはクエリプランナに誤った見積もりをさせることになるので, "SET enable_seqscan TO on;"をクエリの後に送信すべきです.

二つ目は,順に走査することをクエリプランナが考える程度に早くすることです.これは,バウンダリボックスの「キャッシュ」を行う追加カラムを作成し,このカラムにマッチさせるようにすることで達成することができます.ここでの例では次のようになります.

SELECT AddGeometryColumn('myschema','mytable','bbox','4326','GEOMETRY','2'); 
UPDATE mytable SET bbox = ST_Envelope(ST_Force_2d(the_geom));

そして,次のように,&&演算子をgeom_columnに対して行っていたものをbboxに変更します.

SELECT geom_column 
FROM mytable 
WHERE bbox && ST_SetSRID('BOX3D(0 0,1 1)'::box3d,4326);

もちろん,mytableの行を変更または追加したら, bboxを「同期」するようにしなければなりません.最もすっきりした方法はトリガです.アプリケーションを変更してbboxカラムの現状を保持するか,テーブル更新後にいつもUPDATEクエリを実行するかでも対応できます.

6.2. ジオメトリインデクスでCLUSTERを実行する

読み込むことがほとんどで,かつほとんどのクエリでひとつのインデクスを使うようなテーブルのために, PostgreSQLはCLUSTERコマンドを提供しています.このコマンドは,全てのデータ行を,インデクス基準にあわせて物理的に再整理するので,二つの性能の利点を生みます.ひとつは,インデクス範囲の走査のために,データテーブルのシーク回数が劇的に減少することです.ふたつめは,いくつかの小さなインデクス間隔に集中する場合には,データ行が分布するデータページがより少なくなるので,より効率的なキャッシュを持つことです. (この点で,PostgreSQLマニュアルのCLUSTERコマンドのドキュメントを読むように仕向けられていると感じて下さい.)

しかし,GiSTインデクスは単純にNULL値を無視するため現在のところPostGISのGiSTインデクスのクラスタリングはできず,次のようなエラーメッセージを得ます.

lwgeom=# CLUSTER my_geom_index ON my_table; 
ERROR: cannot cluster when index access method does not handle null values
(エラー: インデクスアクセスメソッドがNULL値を扱わない場合クラスタ化できません)
HINT: You may be able to work around this by marking column "the_geom" NOT NULL.
(ヒント: 列"the_geom"をNOT NULLとすることで,これを回避できるかもしれません)

ヒントメッセージにある通り,テーブルに"not null"制限を追加することで,この欠陥にとりあえず対応できます.例を示します.

lwgeom=# ALTER TABLE my_table ALTER COLUMN the_geom SET not null; 
ALTER TABLE

もちろん,ジオメトリカラムで実際にNULL値が必要な場合,この対応はできません.さらには,制限を追加するには上の方法を使わなければならず, "ALTER TABLE blubb ADD CHECK (geometry is not null);"のようなCHECK制限は使えません.

6.3. 次元変換の回避

ときどき,テーブルで3次元,4次元のデータを持つのに,常にOpenGIS準拠のasText()またはasBinary()関数を使ってアクセスして 2次元ジオメトリを出力させるようなことが起きます.内部でforce_2d()関数を呼んでいるために発生しますが,これは,大きなジオメトリでは重大なオーバヘッドを誘引することになります.このオーバヘッドを回避するには,一度追加された次元を前もって落とし,かつこれを永続化するのが適当かも知れません.

UPDATE mytable SET the_geom = ST_Force_2d(the_geom); 
VACUUM FULL ANALYZE mytable;

AddGeometryColumn() を使ってジオメトリカラムを追加した場合,ジオメトリの次元に関する制限があることに注意してください.この制限を迂回するには,制限の削除が必要になります. geometry_columnsテーブル内のエントリを更新して,その後で制限を再作成することを忘れないで下さい.

大きなテーブルの場合, WHERE節,およびプライマリキー若しくは他の適切な基準によってテーブルの一部へのUPDATEを制限させて,UPDATEの実行の間に単に"VACUUM;"と実行することで, UPDATEをより小さい塊に分割するのが賢いやり方かもしれません.これにより,テンポラリディスクスペースが劇的に減少します.さらに,次元混合のジオメトリを持つ場合, "WHERE dimension(the_geom)>2"でUPDATEを制限することで, 2次元で書かれているジオメトリの再書き込みをスキップさせることができます.

6.4. コンフィギュレーションのチューン

この技法は,FOSS4G 2007カンファレンスでのKevin Neufeldのプレゼンテーション「PostGISパワーユーザのための技法(Tips for the PostGIS Power User)」から得たものです. PostGISの使用(たとえば,「静的データと複雑な解析」対「よくアップデートされるデータと多数のユーザ」など)に依存して,これらの変更によって,クエリがはっきり速度向上するようになります.

詳細情報(およびよりよい書式)については,オリジナルのプレゼンテーションがhttp://2007.foss4g.org/presentations/view.php?abstract_id=117にあります.

6.4.1. スタートアップ

これらの設定はpostgresql.conf内にあります.

checkpoint_segment_size (この設定は新しいバージョンのPostgreSQLでは非推奨)は,checkpoint や WAL ではじまる多数の設定に入れ替えられました.

  • WALファイル数 = 16MBごと; デフォルトは 3

  • 書き込みが多い等大きなデータベース負荷がある場合は,データベースごとに少なくとも10か30を設定します.他の記事としてGreg Smith: Checkpoint and Background writerがあります.

  • おそらくxlogを別のディスクデバイスに格納して下さい.

constraint_exclusion

  • デフォルト: off (PostgreSQL 8.4より前,8.4以上はpartitionに設定)

  • これは一般的にテーブルパーティショニングに使われます.PostgreSQL 8.4より前の場合は,"on"を設置して,クエリプランナに対して,求めるような最適化をさせます.8.4の場合は"partition"がデフォルトです.継承された階層の中にあり,プランナに他のペナルティを与えない場合は,プランナに制限を考慮したテーブル解析を強制します.PostgreSQL 8.4以上では,こちらが理想的になります.

shared_buffers

  • デフォルト: 32MB以下

  • 使用可能なRAMの1/3から3/4程度を設定します.

6.4.2. 実行時

work_mem (ソート処理や複雑なクエリに使われるメモリ)

  • デフォルト: 1MB

  • 大きなデータベース,複雑なクエリ,RAMが多い場合には上げます.

  • 同時使用が多数,またはRAMが少ない場合には下げます.

  • 多数のRAMを持ち,開発者が少数なら,次のようにします.

                        SET work_mem TO 1200000;
                    

maintenance_work_mem (VACUUM, CREATE INDEX 等で使用)

  • デフォルト: 16MB

  • 通常では低すぎます - メモリのスワップの間,入出力を縛り,オブジェクトをロックします.

  • 32MBから256MBが推奨です. 接続ユーザ数に依存します. 多数のRAMを持ち開発者が少ない場合は次のようにします.

                        SET maintainence_work_mem TO 1200000;