Hatena::Grouppostgresql

PostgreSQL 雑記 このページをアンテナに追加 RSSフィード

2010-10-22PL/Proxyで手軽に分散クエリ このエントリーを含むブックマーク このエントリーのブックマークコメント

PostgreSQL はいわゆるパラレルクエリをサポートしていないので、素のままでは重いクエリを複数のCPUを使って分散処理することができません。もしかすると、PL/Proxy を使えば手軽に分散クエリを書けるのではないかと思い、試してみました。

まずは呼び出し元の coordinator データベースと、処理を行う worker データベースを作成します。本当は同じDBにしたかったのですが、「同じ名前の関数を呼ぶ」という PL/Proxy の特性上、最低限2つに分けないと呼び出しが無限に続いてしまうのです。(← 実際にやってしまい、接続エラーになるまで無限ループに……)

$ createdb coordinator
$ createdb worker

PL/Proxy は関数ごとに処理を振り分けるのですが、複数の関数を用意するのも面倒なので、今回は「任意のSQLを実行する関数」を登録することにします。worker に与えた文字列を動的SQLとして実行する関数を登録します。

$ psql worker
=# CREATE FUNCTION parallel_query(sql text)
RETURNS SETOF RECORD AS $$
BEGIN
    RETURN QUERY EXECUTE sql;
END;
$$ LANGUAGE plpgsql;

続いて、coordinator に PL/Proxy をインストールし、クエリを分散するための関数を登録します。単に RUN ON ALL するだけです。

$ psql coordinator -f plproxy.sql

$ psql coordinator
=# CREATE FUNCTION parallel_query(sql text)
RETURNS SETOF RECORD AS $$
    CLUSTER 'mycluster';
    RUN ON ALL;
$$ LANGUAGE plproxy;

また、クラスタの情報の登録には SQL/MED サポートを利用できるので、CREATE SERVER で登録します。各サーバの設定は、p<N> または partition_<N> の形式で接続文字列を指定するようです。

=# CREATE SERVER mycluster FOREIGN DATA WRAPPER plproxy OPTIONS (
  p0 'dbname=worker application_name=p0',
  p1 'dbname=worker application_name=p1',
  p2 'dbname=worker application_name=p2',
  p3 'dbname=worker application_name=p3'
);
=# CREATE USER MAPPING FOR postgres SERVER mycluster;

ここで application_name を設定しているのは2つの意味があります。1つは、同じDBに接続してしまうので、自分がどの領域の担当かを伝えることです。もう1つは、PL/Proxy が賢すぎるのか、全く同じ接続文字列の場合は同一クラスタであると認識して、接続を1つしか使わないのを回避することです。もちろん、異なるデータベースや異なるインスタンスに接続するような設定も使えます。最初からデータを複数のデータベース分割していたり、マシンを複数台用意できるならば、そちらのほうが効率的かもしれません。


さて、設定はたったこれだけ。coordinator にログインしてクエリを投げてみます。

coordinator=# SELECT * FROM parallel_query($$
  SELECT current_database(), current_setting('application_name'), sum(i)
   FROM generate_series(1, 100000) AS t(i)
  $$) AS t(dbname name, name text, n bigint);
 dbname | name |     n
--------+------+------------
 worker | p0   | 5000050000
 worker | p1   | 5000050000
 worker | p2   | 5000050000
 worker | p3   | 5000050000
(4 rows)

worker データベースに4並行で処理されていることが分かりました! 実際には、current_setting('application_name') の値に基づいて (もしくは適当に引き継いだ状態変数に応じて) 異なるデータ領域を担当することで、同一DBであっても複数のCPUを活かすことが可能と思われます。