第22回 JPUG しくみ+アプリケーション勉強会

今日は第22回 JPUG しくみ+アプリケーション勉強会のために市ヶ谷へ行ってきた。

今回はおいらも発表。発表したのはxml_fdwネタ。

今回の勉強会は3枠。
最初の枠は「PHPを使ったSQLインジェクション対策」
これは前後編になっており、今回はエスケープシーケンスに特化した話。エスケープ用の関数などもDBコネクタ別にいろいろあって、なかなかややこしい・・・

2枠目がおいらの発表(「xml_fdw XMLファイルをPostgreSQLの情報源として扱う外部データラッパの試作 」)。
今回はネットブックも最後まできちんと動いたので大きなトラブルはなし。
ただプレゼン自体はまだ下手だなあ・・・まだまだ場数が足りないのか、資料の作り方がプレゼン向きになっていないのか。反省点は多い。
あと、K原さんから「ソースはPGXNにアップするのがいい」という宿題(?)を受けた。でも英文の説明資料なんて書けないお・・・

3枠目は「なんちゃってEarly Lock Release --実装と評価-- 」
前回の勉強会でEarly Lock Releaseに関する提案をしていたが、それを仮実装して測定したもの。
測定結果自体を見ると理論通りにはいかず性能向上効果はなかったが・・・それにしても、この実装のためにPostgreSQLのソースだけでなくLinux Kernelソースに手を入れているあたりが凄い。その発想はなかった。

勉強会のあとは例によって懇親会へ。
懇親会での話題はこんな感じかなあ・・・。

・FDWの話。異常系を真面目に作りこむのはつらいね・・・と。
 ・そこから派生してPostgreSQLのXMLが今ひとつ流行らないのは何で?という話
・結婚式の二次会などで行うゲームも最近はSaaS化している。
・SQLインジェクションネタで色々盛り上がる人たち。
・FDWでWHERE句pushdownを考える時にもSQLインジェクション対策はきちんと検討が必要。
 ・XPathのconditonをpushdownしようとするときのSQLインジェクション対策はかなり面倒そう。
  →単一/二重引用符のどちらでも引用が可能だから。
・ELRに関するH川さんとS木さんの白熱した議論
 (詳細は不明、つーかおいらでは理解がついていけない)
・pgpool-IIとPeaceMakerの使い分け
・Postgres-XCの高可用性は本当に大丈夫なのかしらん。

2012-01-21 14:04:03.000005 by Toshi Harada

PostgreSQL で、STATISTICS (列の統計精度) を変更した箇所を忘れたら

お世話になっております、サイオス 那賀です。

PostgreSQL では、特定のテーブルの特定のカラムごとに、統計情報を取得する際の精度を指定することで、大きなテーブルでは荒くなりがちな統計情報の精度を調整することができるようになっています。


postgres=# ALTER TABLE users ALTER COLUMN name SET STATISTICS 500;
ALTER TABLE

しかし、いろいろやっていると、データベース全体のどこに指定をしたか忘れてしまいます。そんな時は、以下のようにすれば変更箇所を一覧することができます。


postgres=# SELECT c.relname, a.attname, a.attstattarget
FROM pg_attribute AS a, pg_class AS c
WHERE c.oid = a.attrelid and a.attstattarget > 0;

relname | attname | attstattarget
---------+---------+---------------
users | name | 500
(1 row)

ところでこの値のデフォルトである "default_statistics_target" パラメータは、VACUUM の効率やマシン性能の向上に合わせて、徐々に上がっています。

18.6 問い合わせ計画 - PostgreSQL 8.3 文書

default_statistics_target(integer)…デフォルトは10です。

18.6 問い合わせ計画 - PostgreSQL 8.4 文書

default_statistics_target(integer)…デフォルトは100です。

以前は、10 では小さすぎるので調整をすると性能が上がりましたが、100 もあれば、よほど大きなテーブルでなければ問題なくなった模様。

では。

2011-12-15 00:00:00.000003 by SIOS OSS Tech

xlogdumpによるトランザクションログの解析

xlogdumpは、PostgreSQLのトランザクションログ(WAL)ファイルに含まれているWALレコードの内容を読み取り、人間に分かる形で表示するユーティリティです。本記事では、xlogdumpのインストール方法や使い方を解説します。

2011-12-13 09:54:41.000001 by Let's Postgres

PostgreSQL の SQL やプロシージャでランダムデータの入ったテーブルを生成する

お疲れ様です、サイオス 那賀です。

テーブルの抱えるサンプルデータの行数が 2~3 行だと、何をどうしてもインデックスなど使われないのでつまらない時に、自動的に生成した沢山のランダムデータを INSERT する方法です。連番 10000 個につき、ランダムな 20 文字からなる名前をそれぞれに持つ "users" テーブルを作成してみます。

SQL のみで実行

まずは、バージョン 9.0 以降での例です。


DROP TABLE users CASCADE;

CREATE TABLE users (id integer PRIMARY KEY, name text);

INSERT INTO users (id, name) (
SELECT id,
string_agg(substr(s, ceil(random() * length(s))::int, 1),'')
FROM (
SELECT 'abcdefghijklmnopqrstuvwxyz'
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'::text s, *
FROM generate_series(1, 10000) AS id, generate_series(1, 20)
) AS tmp
GROUP BY id
);

CREATE INDEX ON users (name); -- 9.0 から、インデックス名は省略可能

ここでは、新しく導入された string_agg() 集約関数を使用しているため、8.4 以前では動きません。サブクエリをそれぞれ実行してみれば、何をしているかは分かると思います。

ランダムな文字列が生成されていることと、充分にデータが大きいのでインデックスが使われていることを確認します。


postgres=# SELECT * FROM users LIMIT 10;
id | name
----+----------------------
1 | bCphDuvZliwsKjSciOZA
2 | oYTQPutKQqYRTnYwIuwU
3 | DSmnbfqkTpKioEZeZsOP
4 | JNHCbFYJauDDnPRpVizO
5 | xkXMPWQOoFEXslAtRycS
6 | TGvhwnxrvWgThdFXawMo
7 | bqmtCNNulQmfwInTWKkr
8 | HrlPvRMvnyJpPWJrJwLV
9 | mYbJGoCCZNUGefvzXiUl
10 | GEAwBkNlGzgTxiCexfGw
(10 rows)

postgres=# EXPLAIN SELECT * FROM users WHERE name = 'hoge';
QUERY PLAN
------------------------------------------------------------------------------
Bitmap Heap Scan on users (cost=4.64..75.57 rows=50 width=36)
Recheck Cond: (name = 'hoge'::text)
-> Bitmap Index Scan on users_name_idx (cost=0.00..4.63 rows=50 width=0)
Index Cond: (name = 'hoge'::text)
(4 rows)

postgres=#

バージョン 8.4 でも、配列に対する集約関数である array_agg() 関数が使えるので、以下のようにできます。


DROP TABLE users CASCADE;

CREATE TABLE users (id integer PRIMARY KEY, name text);

INSERT INTO users (id, name) (
SELECT id, array_to_string(
array_agg(a[ceil(random() * array_length(a, 1))]),'' )
FROM (
SELECT ARRAY[
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
] AS a, *
FROM generate_series(1, 10000) AS id, generate_series(1, 20)
) AS tmp
GROUP BY id
);

CREATE INDEX users_name_idx ON users (name);

8.3 以前では、array_agg() も無いので、集約関数では無理だと思います。ストアドプロシージャを使います。

ストアドプロシージャで実行

9.0 以降には DO コマンドがあり、無名関数の実行ができますので "DO $$~" のように実行できますが、8.4 以前では、一旦名前をつけて関数を定義する必要があります。

以下は、8.3 でも動きます。


DROP TABLE users CASCADE;

CREATE TABLE users (id integer PRIMARY KEY, name text);

CREATE OR REPLACE FUNCTION tmp() RETURNS void AS
$$
DECLARE
i INTEGER;
id INTEGER;
name text;
s text := 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
BEGIN
FOR id IN 1 .. 10000 LOOP
name := '';
FOR i IN 1 .. 20 LOOP
name := name || substr(s, ceil(random() * length(s))::int, 1);
END LOOP;
INSERT INTO users VALUES(id, name);
END LOOP;
END
$$ LANGUAGE plpgsql;

SELECT tmp();

DROP FUNCTION tmp();

CREATE INDEX users_name_idx ON users (name);

では。

2011-12-13 08:00:00.000001 by SIOS OSS Tech

PHPから見たPostgreSQLの数値データ型(数値リテラル)

PHPを使ってPostgreSQLへ数値を埋め込む場合の、範囲チェックやフォーマットチェックの方法や、その他にも気を付けなければならないポイントを説明します。

2011-12-13 06:05:03.000001 by Let's Postgres

Planet PostgreSQL chases Advent Calendar!

お気づきの方も多いと思いますが、Planet PostgreSQL JPにてAdvent Calendarを追跡中です。・・・が、どうもYahoo PipseとGAEの組み合わせが悪いようでPipseの更新を自動で反映できず、たまに私がデータの更新日付を1年前に戻して再取得させている・・・という涙ぐましいお話。

ちなみにYahoo Pipseは下記の要領で動いています。

ATNDのコメントRSSを取得

各リンクURLを収集

各URLから当該ページのRSSを取得

タイトルまたは本文にPostgreSQL Advent Calendarの文字列が含まれるエントリを抽出

新しいRSSとして再構成

という流れになっています。初めてPipse触ったんですが、見た目のファンシーさの割にできることがわかりにくく・・・結局3時間ぐらいかかったので自前でプログラム書いた方が早かったんじゃというツッコミもありつつ、まあできたので良しと。上記の通り文字列抽出をしているのでAdvent Calendar本体の記事から程なく別目的で同様の文字列を含むエントリをアップした場合誤認識する可能性があります。なんでURLマッチしないんだと言われそうですが、いろいろあって結論からいうとPipseではUNIONできてもJOINできないんですねこれが。

というわけで各エントリをアップ頂いている皆々様には感謝の言葉に尽きますが、Yahoo Pipseからのアクセスがトップになっているからといって私に後ろ指ささないように・・・

2011-12-10 07:21:50.000005 by Hitoshi Harada

DOMAINを使ってみよう : PostgreSQL Advent Calendar #10

このエントリは、PostgreSQL Advent Calendar : ATNDの12/10分です。

今回はPostgreSQLの型拡張機能の一つである、ドメイン機能を使って何が出来るのかを簡単に紹介したいと思います。
と、その前に・・・

データの制約

当たり前のことですが、RDBMSの列には「データ型」というものが設定されています。でも、データ型だけでは厳密な制約を課すことができないケースもありえます。

  • ex. integer型というだけでは、その列に「負数は論理上入らない」という制約はつけられない。
  • ex. text型というだけでは、その列に(電話番号のように)「数字とハイフンしか入らない」という制約はつけられない。

こうした意味的なチェックというのはアプリケーションでも勿論行えますが、データベースの機能としても存在しています。

PostgreSQLで使える制約

もちろん、PostgreSQLにもデータ型以外の様々な制約機能が存在します。

  • 検査制約
    • 式によって制約を規定する。
    • 例:この列は0より大きい値でなければならない。
  • 非NULL制約
    • NULLを許容しない制約を付与する。
  • 一意性制約
    • 全てのレコードで値がユニークであることを規定する。
  • プライマリーキー(非NULL制約+一意性制約)
  • 外部キー
    • 別テーブルのカラムと一致することを規定する。
  • 排他制約

ドメインは上記の「検査制約」を付与した型を作成する機能です。
PostgreSQLではユーザが自由に型を作成することが出来ます。型の作成方法には色々な種類がありますが、その一つのパターンとして、型を作成するときに、以下ような制約を付与することができます。

  • 値の範囲を列挙(ENUM)として規定した型を作る。
  • 値のドメイン(設定可能な値集合)を指定した型を作る。

さて、個々の列に対して前述の検査制約を付与することは元々可能なのですが、では制約付きの型を作ると何が嬉しいか?という疑問もあるかもしれません。
一番の理由は「制約付きの型を作るのはメンテナンス性向上のため」なのかなと思っています。
例えば、E-mailアドレスなど、あちこちのテーブルに散在しそうな一般的なデータなどは、ドメインとして制約を記述しておくことで、制約自体のルール変更箇所をまとめられます。
あと、教科書に従ったデータベース設計をするなら、それなりに出番はあるんじゃないかとも・・・(概念設計時にカラムの取りうる値域をきちんと定義したら、それが列挙なりドメインなりになるのではないかと)

ドメインを作成してみる。

前置きが長くなりましたが、ここからは実際にドメインを作成してみます。

CREATE DOMAIN

ドメインを作る場合には、CREATE DOMAINというコマンドを使います。このコマンドは基本型だけでなく、既に作成したドメインに対しても使うことができます(つまり、ドメインはネストできます)。
形式は

CREATE DOMAIN name [ AS ] data_type
[ COLLATE collation ]
[ DEFAULT expression ]
[ constraint [ ... ] ]

また、constraintは
[ CONSTRAINT constraint_name ] { NOT NULL | NULL | CHECK (expression) }

という形式です。詳細はCREATE DOMAINを見てください。
では実際に作成してみます。

3の倍数と3が付く数字のときだけエラーになる整数型

そういえば、ちょっと前に「世界のナベアツ」が「3の倍数と3が付く数字のときだけアホになります」というネタをやってましたが、それにあやかって(?)「3の倍数と3が付く数字のときだけエラーになる」整数型のドメイン「nabeatsu」を定義してみようと思います。

まず、こんな感じでドメインを作成してみます。

CREATE DOMAIN nabeatsu AS INTEGER
CHECK (
NOT (VALUE % 3 = 0 OR
substring(VALUE::text from '3') is not null)
);

CHECK (・・・)の部分で、「3の倍数と3が付く数字のときだけエラーになる」チェックをかけています。

ドメインを作成したので、早速使ってみましょう。

CREATE TABLE hoge (
id int,
data nabeatsu
);


psqlの \d コマンドで確認すると、data列は基底となったintegerではなく、作成したドメインnabeatsuの型として表示されます。

Table "public.hoge"
Column | Type | Modifiers
--------+----------+-----------
id | integer |
data | nabeatsu |


テーブルを定義したので、実際にデータを挿入して、本当にdata列が「3の倍数と3が付く数字のときだけエラーになる」のか見てみます。

INSERT INTO hoge VALUES (1, 1);
INSERT 0 1
INSERT INTO hoge VALUES (2, 3);
ERROR: value for domain nabeatsu violates check constraint "nabeatsu_check"
STATEMENT: INSERT INTO hoge VALUES (2, 3);
psql:nabeatsu.sql:20: ERROR: value for domain nabeatsu violates check constraint "nabeatsu_check"
INSERT INTO hoge VALUES (3, 6);
ERROR: value for domain nabeatsu violates check constraint "nabeatsu_check"
STATEMENT: INSERT INTO hoge VALUES (3, 6);
psql:nabeatsu.sql:21: ERROR: value for domain nabeatsu violates check constraint "nabeatsu_check"
INSERT INTO hoge VALUES (4, 10);
INSERT 0 1
INSERT INTO hoge VALUES (5, 13);
ERROR: value for domain nabeatsu violates check constraint "nabeatsu_check"
STATEMENT: INSERT INTO hoge VALUES (5, 13);
psql:nabeatsu.sql:23: ERROR: value for domain nabeatsu violates check constraint "nabeatsu_check"

この例では、dataに対して1, 3, 6, 10, 13という数値を挿入しようとしていますが、3, 6, 13は「3の倍数と3が付く数字」なのでエラーになります。成功ですね。
このときのエラーは
ERROR: value for domain nabeatsu violates check constraint "nabeatsu_check"
のように表示されます。constraintの名称は「ドメイン名 + "_" + check」という名称になるようです。

XMLの妥当性検証を行うXML型

これだけじゃアレなので、もう少し実用的そうなものを考えてみます。

PostgreSQLにもXML型があり、XMLデータを挿入したり、XPathで検索したり出来ます。
現状PostgreSQL本体の機能としては、XMLの形式として正しいかどうか(Wel-formedな形式かどうか)は、チェックしていますが、XMLの妥当性検証(XMLの構造、値の内容等が正しいものかを検証する)を行う機能は残念ながら入っていません。
(XMLの妥当性検証については、それだけで膨大な説明が必要なので詳細は割愛します・・・)

ということで今度はXMLの妥当性検証を行うドメインをつくってみようと思います。
これはSQL定義だけでは出来ないので、事前に以下のようなPostgreSQL関数をC言語で作成しておきます。

  • 入力としてXML型とXML Schema(妥当性検証の定義が記述されたXML)を入力として、trueかfalseを返却する関数を定義する。
  • 関数内でlibxml2のXML Schema Validation APIを呼び出して妥当性検証を行う。
  • 妥当性検証が成功したらtrueを、失敗したらfalseを返却する。

この関数のコードは割愛しますが、C言語で組んでも100 stepもないような簡単なものです。

次に上記のC言語関数 valid_xml をCREATE FUNCTIONコマンドを使ってPostgreSQLに登録します。

CREATE OR REPLACE FUNCTION valid_xml(xml, text)
RETURNS boolean
AS '$libdir/valid_xml', 'valid_xml'
LANGUAGE 'C' IMMUTABLE STRICT;


で、ドメインを定義して、そのドメインを用いたテーブル定義を行います。
test=# CREATE DOMAIN test_xsd AS xml
test-# CHECK (
test(# valid_xml(VALUE, '/tmp/test.xsd')
test(# );
CREATE DOMAIN
test=# CREATE TABLE vxml_table (data test_xsd);
CREATE TABLE
test=# \d vxml_table
Table "public.vxml_table"
Column | Type | Modifiers
--------+----------+-----------
data | test_xsd |

valid_xml関数の第2引数には、XML Schemaファイルのパスを記述します。例えば今回の例では、以下のようなXML SchemaファイルがPostgreSQLサーバの /tmp/test.xsd に存在するとします。

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<xs:element name="A">
<xs:complexType>
<xs:sequence>
<xs:element ref="B"/>
<xs:element ref="C"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="B" type="xs:NCName"/>
<xs:element name="C" type="xs:NCName"/>
</xs:schema>

XML Schemaの細かい説明は省きますが、この定義は要するに A要素の下にはB要素→C要素が順に存在しなければならないというルールを示しています。

では、XMLを挿入してみます。
test=# INSERT INTO vxml_table VALUES ('<A><B>bb</B><C>cc</C></A>');
INSERT 0 1
test=# INSERT INTO vxml_table VALUES ('<A><B>bb</B><D>dd</D></A>');
ERROR: value for domain test_xsd violates check constraint "test_xsd_check"
test=#

最初の例も2番目の例もXMLの形式としては正しいのですが、2番目のXMLはエラーになっていまいます。このエラーはドメインのCHECK制約によるものなのですが、なぜCHECK制約に引っかかったのかというと、2番目のXMLは「A要素のD要素がある」から、XMLスキーマ定義に合わなかったと判断されたためです。成功ですね。
PostgreSQL本体では、XML Schemaによる妥当性検証機能はありませんが、ドメインを使うことで、アプリケーション側ではなく、PostgreSQL側で妥当性検証を行う拡張も可能になりました。
ドメイン万歳ですね!



明日の担当は sakai_k さんです。よろしくお願いします。

2011-12-09 20:38:57.000004 by PostgreSQL Advent Calendar 2011

GUIでデータベースを簡単にいじれるpgAdminとっても便利ですね: PostgreSQL Advent Calendar #9

このエントリはPostgreSQL Advent Calendar用にPostgreSQLネタです。
とはいえ僕はWebアプリ屋でPostgreSQLが便利だな~と思ってる程度のレベルなので
他の方々のようなコアな話ではないのでご了承くださいませ(´・ω・`)


僕自体Linuxコマンドラインを叩くこと自体嫌いではないのですが、
やっぱり色がついてたりマウスが使えたり、なによりも「便利」なGUIは大好きです。
(psqlとか読みにくくてあんまり好きじゃないです・・・)
MySQLの場合は代表的なもので「phpMyAdmin」がありますが、
ブラウザ用なのでtextareaの入力が不便だったり、そもそPHPをサーバーに入れておかないといけないとか、セキュリティのことを考えると別サーバーにおくかー?とか色々細かいことを考えるとめんどくさくて好きではありません。
(ブラウザ型が好きな人にはごめんなさい。なおPostgreSQLにもphpPgAdminてのあります)

そんな僕はPostgreSQLを好んで使っている理由のひとつに
pgAdmin

というWindows/Mac/Linux系デスクトップで使える無償のネイティブアプリが存在するからです。(※1)(※2)

しかし、このサイト…
英語ONLYだし、どこからダウンロードしたらいいかわからないし、
グーグル様に[pgadmin]と普通に聞いても『pgAdminIII』(※3)という言葉がノイズで混じってきて何が本当の情報かよくわからないし(´・ω・`)
ソフトウェア自体は大体日本語化されてる(多分。ヘルプはされてないですね)ので英語わからないから英語のものには触らない!って人でも大丈夫です。

というわけで前置きが長くなりましたが、今回のトピックは
『pgAdminで簡単にPostgreSQLデータベースを操作するTIP集と参考URL』でっす。

まずはお使いのPCにインストールしましょう。
画面キャプチャを張ろうかと思ったんですが、すでに書いているページがあったので紹介

PostgreSQL用GUI管理ツールpgAdminを入れてみた - プログラマーな日々

上記記事の時点ではPostgreSQLは9.0だったはずで、現在(2011年12月9日時点)ではPostgreSQL自体が9.1なので1.14.2のようです。

なおpgAdminは結構PostgreSQLのバージョンアップにあわせて更新されます。(中の人エロイ)
PostgreSQLの内部コマンドがバージョン違いがあるとダンプできなかったりするので、PostgreSQL本体がバージョンアップしたときはサイトを確認してアップグレードをおススメしますです。

さて、次は接続です。
通常PostgreSQLは外部からの接続を許すために何箇所か本体のconfをいじらないといけません。
しかしクライアントマシンにインストールされたpgAdminを許可しようと思ったら
全てのアドレスからの接続許可を与えるしかありません
(踏み台やVPNという手もあるでしょうけどめんどくさいことはしない方針の場合)

なので僕のおススメの方法はSSHトンネリングで接続です。
SSHトンネルの接続については公式ドキュメントで書かれていますが、
なんのこっちゃいとおもうので、、、
簡単に説明すると

(Do Rubyさんの記事からお借りした画像)

という感じですでに開いているSSHポートを利用して

  • データのやり取りを暗号化
  • サーバー側の余計なポートは開けないで済む

と一石二鳥なわけです。
「SSHのパスワードログインは危ないじゃないか!」
といえる人はSSH公開鍵認証の設定をしてください。
あとWEBアプリ屋さんならアプリ専用のDBユーザーともうひとつ同じ名前のUNIXログインユーザーを作ることをおススメします(`・ω・´)

さて肝心のSSHポート転送用ですが、大概のサーバーサイドエンジニアならコンソールソフト(puttyとか)入れてると思うのでそれについてると思います。
僕はTera Term(UTF-8対応後継版)を利用しているので
こちらのサイトのように設定していきます。
ただし、PostgreSQLのデフォルトのサーバーポートは5432なので
デフォルトのままなら

こんな感じの設定を入れます。(※4)(※5)
そしてSSHクライアントでデータベースユーザーと同じユーザーでSSH接続後
pgAdminでPostgreSQLサーバーに接続します。
サーバの追加はこんな感じで設定

あとはつなぎたいサーバーをダブルクリックで接続できるはずです!

さて肝心の使い方ですが…
直感的に使えるはずなので説明しなくても大丈夫のはず!!
決してめんどくさくて端折ってるわけでないですよ(遠い目)
まあ使い方だけであと何本か書けると思うんですよ。
多分きっと誰か書いてくれるはず!
(お前書けよという話はスルー)

と、これじゃまずいので使ったことない人用に最初につまずくだろう部分をいくつかピックアップ

  • スキーマー宣言を特にしないでいる場合は

    データベース→[データベース名]→スキーマー→public
    の中に見たいテーブルがあるはずです。
    当然スキマー設定してあるテーブルならpublicではなく指定した名前から探してください。
  • しかしスキーマーの表示項目でいくつかデフォルトで表示されていない項目があります(自作の集約関数宣言とか。使わない人にはまったく関係ないですが・・)
    表示項目の設定は
    上部メニューのファイル→オプション タブの「ブラウザ」にあります

あとはとりあえず直感でいけると思うデス(`・ω・´)

他にもこれは便利だなーと思う機能紹介です

  • pgAdminでPostgreSQLのテーブル定義書を出力する-プログラマーな日々
    テーブルとデータ系の資料を出せといわれたときにコメントがちゃんと書いてあるととっても便利です。
    コメント書くのがめんどくさいって思うかもしれませんが…
    運用をうまくやればテーブルに一元化されているのはひとつの便利な方法だと思います
  • クエリーツールからのアナライズ解釈とグラフィカルな表示
    普通クエリーの解釈を確認するときはexplain analyzeをSQL文の先頭につけて実行して結果を解析するわけですが…見にくい!(見てるとなれるのですが…)
    また解析結果を資料にしにくいものですもあります。
    ここで

    こんな画面がでるので簡単に偉い人にそれっぽい資料が作れちゃう!( ゚д゚ )
    データ出力のタブをみるとexplain analyzeの結果が返ってくるので
    わざわざ書く必要もありません。結構調査に便利。

他にも便利な機能ありますが、触っているとすぐみつけられるとおもうのでまず試してみてください!

というわけで『pgAdmin便利だよ!』っていいたかっただけなんです。
今日の記事にコアなこと期待してたらごめんなさい。
たいした内容ではないですがここまで読んでくれた方ありがとうございます。

現在コメント欄を整備していない関係であけていないので
内容に質問・指摘などがある方は@cstyles_jpに連絡いただけたらわかる範囲でお答えします。よろしくお願いします。


明日の担当は
象好き猫好きラーメン好きの@nuko_yokohamaです。


※1  有償なら株式会社スマートスタイルさんが取り扱っているNavicatという各DB対応のGUIソフトウェアがあります
PostgreSQL版を使ったことありますが、pgAdminに近い感じで、なかなか便利でした。
※2 ちなみにMySQLにもMySQL Administratorなるものがあるのですが、直感的に使いにくくてやめました。(Navicatさんのほうが使いやすい)
GUIなのにこの使いにくさならphpMyAdminでいいよって…思ってしまいました。。
※3 インストールされるソフトウェアの名前は『pgAdmin Ⅲ』サイトの名前は『pgAdmin』。
Ⅲがこれまた環境依存文字なのでIII(大文字アイ×3)にふれたり。。
経緯はよくわからないのですが…いっそ全然違う名前にしてくれればよかったのにー(‘A`)
※4 複数サーバーの設定を入れる場合はクライアント側のポート番号をずらして設定すればOKです。もちろんクライアント側が使っていないポート番号で設定してください。
※5 自分のPCにPostgreSQLをインストールしている場合はローカルの5432を普通は使ってると思うので5433とか違う番号を設定しましょう

2011-12-09 00:00:16.000004 by PostgreSQL Advent Calendar 2011

PostgreSQL レプリケーションの Q&A PostgreSQL Advent Calendar #8

この記事は、PostgreSQL Advent Calendar の 12/8 担当分です。

只今 12/8 21 時。12/8 も残り 3 時間というところで何もいいネタが思い浮かばなかったので、とりあえずレプリケーション関係について Q&A を殴り書きしていきます。

  • Q. PostgreSQL 内蔵のレプリケーションってある?
  • A. あります。バージョン 9.0 から使えます。
  • Q. レプリケーションの同期モードは?
  • A. v9.0 は非同期のみ、v9.1 以降は同期と非同期を選択できます。
  • Q. スタンバイでは SQL を実行できる?
  • A. 参照形の SQL だけですが、スタンバイで実行できます。実行できる/できない SQL は、マニュアルに記載されています。
  • Q. どうやってフェイルオーバさせるの?
  • A. スタンバイで、trigger_file というパラメータに設定したファイルを作成することで、スタンバイをマスタに切り替えることができます。v9.1 であれば、trigger_file の作成以外に、pg_ctl という監視コマンドに対して promote というサブコマンドを指定して実行するだけで切り替えることができます。  
  • Q. マスタが故障したときに自動的にフェイルオーバさせるとかできる?
  • A. PostgreSQL だけではできません。pgpool-II や Pacemaker などのクラスタ製品と組み合わせる必要があります。
  • Q. Pacemaker と組み合わせることってできるの?
  • A. PostgreSQL のレプリケーションを制御するための RA (リソースエージェント) を作成しないと組み合わせられません。ただ、Linux-HA Japan のコミュニティで、そんなような RA を作る動きがあります。 
  • Q. 一部のテーブルだけレプリケーションさせるってできる?
  • A. できません。データベース全体がレプリケーション対象になります。ただ、v9.1 では UNLOGGED TABLE という WAL を発生させないテーブルを定義でき、このテーブルはレプリケーションされません。UNLOGGED TABLE のテーブルは WAL を一切発生させないため、PostgreSQL がクラッシュしたとき、リカバリをしてもデータは復元できません。クラッシュ時にデータが失われてもいいから、レプリケーションさせたくないといったテー ブルがあれば、UNLOGGED TABLE を使えます。
  • Q. 同期レプリケーションってどこまでデータを保証するの?
  • A. マスタで実行されたトランザクションは、そのトランザクションの WAL (トランザクションログ) がスタンバイのディスクに同期書き込み (fsync) されるまで待ちます。つまり、クライアントから見ると、トランザクションのコミットの成功応答を受け取ったとき、マスタとスタンバイの両方のディスクに WAL が書き込まれていることを保証できます。WAL がスタンバイのディスクに書き込まれているということは、マスタが故障しても、スタンバイにフェイルオーバすることで、コミットされたデータを見ることが できます。
  • Q. 同期レプリケーションで、コミットされたデータをすぐにスタンバイで見られる?
  • A. 基本的にはすぐに見られます。けど、見られないこともあります。↑で回答しているように、同期レプリケーションでは、コミットが完了した時点で、そのトラ ンザクションの WAL がスタンバイのディスクに書き込まれていることまでしか保証しません。その WAL がリカバリされ、スタンバイで参照 SQL を実行して見られるようになっているかどうかまでは責任を持ちません。ただし、ディスクに書き込まれた WAL は速やかにリカバリされるので、基本的にはすぐに参照 SQL を投げてもスタンバイからデータを見られます。
  • Q. WAL がリカバリされるまで待つ同期モードって作らないの?
  • A. v9.1 の開発途中にはそんなような話もありました。けど、まずは一番シンプルな (WAL を同期書き込みするまで待つ) 同期モードを開発して、徐々に複雑なモードを入れていこうということで、開発は一旦保留になっています。
  • Q. スタンバイが WAL を同期書き込みまでじゃなくて、受信するまでトランザクションを待たせるっていう同期モードを作ったら、同期レプリケーションのオーバーヘッドって下がるのでは?
  • A. たぶん下がります。そんなパッチのプロトタイプは作成済で、後は完成させて性能検証してコミュニティに投稿するだけなのですが、時間がありません。。。。
  • Q. 一部のトランザクションだけ同期レプリケーションさせるってできる?
  • A. synchronous_commit というパラメータを使ってできます。synchronous_commit は、トランザクションごとに設定変更できるパラメータです。synchronous_commit が on に設定されている場合、トランザクションは、マスタとスタンバイに WAL が同期書き込みされるまでトランザクションを完了させません。設定値を local にすると、マスタに WAL が同期書き込みされた時点でトランザクションは完了となり、レプリケーションを待ちません。設定値を off にすると、トランザクションはマスタとスタンバイの両方について WAL の同期書き込みを待ちません。postgresql.conf では、synchronous_commit を local に設定しておいて、同期レプリケーションしたいトランザクションについてだけ SET synchronous_commit TO on と設定した上で実行することで、そのトランザクションだけレプリケーションを待つようにできます。
  • Q. カスケードレプリケーションってできる?
  • A. v9.1 以前ではできません。来年夏頃リリース予定の v9.2 から使える予定です。
  • Q. レプリケーションってどんな経路でデータがマスタからスタンバイに伝わるの?
  • A. 次のような経路です。 
    • 1. バックエンド (SQL を実行するプロセス) が、トランザクションをコミットするときに、そのトランザクションの WAL をマスタのディスクに同期書き込みする 
    • 2. バックエンドは、walsender (WAL をマスタからスタンバイに送信するプロセス) に WAL の転送を要求する ・非同期レプリケーションだと、この時点でトランザクションは完了し、コミットの成功応答がクライアントに返る。以降の WAL の転送などは、トランザクションとは独立に実行される ・同期レプリケーションだと、スタンバイから応答があるまでレプリケーション待ちの状態になる
    • 3. 要求を受けて、walsender は WAL をディスクから読み込み、WAL をスタンバイに送信する
    • 4. スタンバイにおいて、マスタから送信された WAL を walreceiver (WAL を受信するプロセス) が受信する
    • 5. walreceiver は、受信した WAL をディスクに同期書き込みする
    • 6. walreceiver は、startup (WAL をリカバリするプロセス) に WAL のリカバリを要求する
    • 7. 要求を受けて、startup は WAL をディスクから読み込み、リカバリする (7 の実行順序は 8 や 9 の後になることもある)
    • 8. walreceiver は、どこまで WAL を書き込んだのか応答をマスタに返す
    • 9. マスタにおいて、スタンバイから返された応答を walsender が受信する ・同期レプリケーションだと、応答を受けて、walsender はレプリケーション待ちのトランザクションを再開させ、完了させる
  • Q. WAL を送信するたびに walsender が WAL をディスクから読み込んでるけど、I/O 負荷高くならない?
  • A. あまり高くなりません。基本的に walsender が読み込むのは、バックエンドが書き込んだばかりの WAL です。そのような WAL はデータがファイルキャッシュに残っているため、読み込んでも I/O 負荷はそれほど高くなりません。ただし、古いデータのスタンバイをマスタに組み込むときなど、ファイルキャッシュから追い出されているような古い WAL を転送する必要がある状況では I/O 負荷は増えてしまいます。
  • Q. 同期レプリケーションで、スタンバイが故障したらどうなる?
  • A. マスタで実行中のトランザクションは停止します。同期レプリケーションは、マスタとスタンバイの両方に WAL を書き込むまでトランザクションを完了させないことを確実に保証しています。この保証を遵守するために、スタンバイが故障して WAL を書き込めない状況でも、スタンバイに WAL を書き込めるまでトランザクションを待たせます。つまり、新しいスタンバイが現れて、WAL を書き込めるようになるまでトランザクションは待たされます。
  • Q. それだと、ノード2台で可用性構成を組んでるとき、ノード1台が故障しただけで処理が止まって、可用性下がるんじゃ?
  • A. はい、そのような状況だと可用性は下がります。ノード1台でも処理を続行したいのであれば、一時的にレプリケーションを非同期モードに設定する必要があり ます。pgpool-II や Pacemaker のクラスタ製品と組み合わせて使っているのであれば、スタンバイ切り離し時やフェイルオーバ時の動作として、同期モードを変更するように作り込んでおけ ば、故障発生時でもノード1台でスムーズにトランザクションを続行できそうです。
  • Q. なんで↑みたいな仕様にしたの?
  • A. 実はこの仕様については v9.1 の開発で散々どうすべきかコミュニティ内で揉めました。結局、最も信頼性の高い仕様でまずは同期レプリケーションをリリースして、より柔軟なものは後で 徐々に入れていこうという話になり、↑の仕様となりました。ノード1台で処理を続行させる仕様だと、その1台が故障したときに完全にコミットされたデータ が失われてしまうリスクがあります。このデータ損失のリスクと、可用性が下がるリスクを天秤にかけたとき、データ損失の方がリスクが高いということにな り、データ損失を回避する信頼性の高い仕様がまずは選ばれたということになります。柔軟な動作モードについては、v9.2 以降に期待してください。
  • Q. スタンバイで SQL 実行したとき、リカバリと競合することがあるって聞いたけど? 
  • A. はい、スタンバイでは SQL 実行とリカバリが競合することがあります。競合が発生すると、SQL が終わるまでリカバリが止まってしまったり、競合した SQL が途中でキャンセルされたりします。競合の詳細は、マニュアルを参照してください。
  • Q. 競合を回避するには?
  • A. 競合しないように PostgreSQL のパラメータを設定します。v9.0 では設定が非常に難しいのですが、v9.1 では簡単で hot_standby_feedback というパラメータをスタンバイ側で有効にするだけです。ただし、この設定では1つ注意があります。スタンバイでロングトランザクション (開始されたまま長時間完了しないままとなっているトランザクション、SQL) が発生すると、その間、マスタで VACUUM が実行されても不要領域が回収されなくなります。スタンバイでロングトランザクションが発生しないように注意しましょう。
  • Q. スタンバイがどこまで WAL を受信した (ディスクに同期書き込みした) か知りたい 
  • A. スタンバイで pg_last_xlog_receive_location() 関数を呼んでください。v9.1 だと、マスタの pg_stat_replication ビューの flush_location 列からも分かります。
  • Q. スタンバイがどこまで WAL をリカバリしたか知りたい
  • A. スタンバイで pg_last_xlog_replay_location() 関数を呼んでください。v9.1 だと、マスタの pg_stat_replication ビューの replay_location 列からも分かります。ただし、replay_location だけは情報が古い (PostgreSQL のパラメータ wal_receiver_status_interval の設定時間だけ古くなる) ため注意してください。あと、v9.1 だと、pg_last_xact_replay_timestamp() という関数を呼んで、どの時刻のトランザクションまでリカバリされたか知ることができます。
  • Q. ↑の関数やビューで表示される文字列の意味が分からない
  • A. その文字列は、WAL のバイト位置を "8桁16進数/8桁16進数" という形式で表した LSN というものです。LSN だとよく分からないという人は、マスタで、LSN を引数に pg_xlogfile_name() という関数を呼んで、LSN を WAL ファイル名に変換しましょう。 LSN 同士の引き算については、ここの記事が参考になります。
  • Q. スタンバイでリカバリを一時停止できる?
  • A. できます。pg_xlog_replay_pause() を呼んででリカバリを一時停止したり、pg_xlog_replay_resume() を呼んでリカバリを再開したりできます。
  • Q. フェイルオーバ後、故障したノードをどうやってレプリケーションに復帰させるの? 
  • A. 新しいマスタからオンライン物理バックアップを取得・展開して、スタンバイとしての設定をした上でノードを起動します。そうすると、自動的にスタンバイはマスタに接続し、レプリケーションが再開されます。
  • Q. 復帰させるのにバックアップの取得って必須なの? 面倒。。
  • A. 必須です。バックアップの取得は確かに v9.0 以前では面倒ですが、v9.1 だと pg_basebackup というコマンド一発で簡単に取得できます。 
  • Q. けど、データベースサイズが大きいからバックアップしたくない
  • A. rsync で差分バックアップしましょう。
  • Q. レプリケーション中にスタンバイが落ちたとき、そのスタンバイをどう復帰させる? 
  • A. 基本的には、その落ちたスタンバイを再起動するだけです。自動的に前回受信したところからレプリケーションが再開されます。ただし、スタンバイとマスタの 差が非常に大きい場合、スタンバイが必要とする WAL ファイルがマスタから既に消えている可能性があります。この場合は、マスタからオンライン物理バックアップを取得・展開して、スタンバイを再構成しなけれ ばなりません。
今日のところは、この辺で Q&A 打ち止めにします。また、Q&A の要望があれば、書き足していきたいと思います。。。

明日の PostgreSQL Advent Calendar 担当は、高津英輔さんです。よろしく~

2011-12-08 17:04:08.000003 by PostgreSQL Advent Calendar 2011

Synchronized Snapshot お試し:PostgreSQL Advent Calendar #7

このエントリはPostgreSQL Advent Calendarの12/7分です。

現在開発中のPostgreSQL 9.2でSynchronized Snapshot(同期スナップショット?)なる機能が導入されます。開発の動機はpg_dumpallで複数のデータベースのバックアップを一貫性のある内容で取りたいということのようで、あまり派手な機能ではないのですが、開発中の pgsql_fdw と組み合わせて面白いことできないかな、と思って調べたので内容を整理してみます。

そもそも「Snapshot(スナップショット)」って何?

PostgreSQLにおける「スナップショット」とは、MVCC で使われる情報の一つで、大雑把に言えばあるトランザクションからどの時点でのデータが可視であるかを判定する基準です。Synchronized Snapshotでは、このスナップショットを複数のトランザクションで共有することによって同一内容のデータを参照できるようになります。

スナップショットを同期する流れ

参照するデータの準備

まず、参照するデータを用意します。デフォルトの分離レベルはREAD COMMITTEDなので、この時点で他のトランザクションから1,000件が参照できます。

<Session 1-データ操作>
postgres=# CREATE TABLE foo (
postgres-# 	id int,
postgres-# 	name text,
postgres-# 	CONSTRAINT foo_pkey PRIMARY KEY (id)
postgres-# );
NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "foo_pkey" for table "foo"
CREATE TABLE
postgres=# INSERT INTO foo SELECT id, to_char(id, '00000000') FROM generate_series(1, 1000) id;
INSERT 0 1000
同期元トランザクションの開始

まず、基準となるトランザクションがいないことには話が始まりませんので開始します。今回はデータを作成したトランザクションとは別に同期元トランザクションを開始してみます。この時点で参照できるのは1,000件です。

<Session 2-同期元>
postgres=# BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN
postgres=# SELECT count(*) FROM foo;
 count
-------
  1000
(1 row)
スナップショットのエクスポート

同期元のトランザクションでpg_export_snapshot()関数を実行してスナップショットをエクスポートします。関数の戻り値がスナップショット識別子なので、後で使うまで覚えておきましょう。なお、READ COMMITTED分離レベルの場合は、トランザクション進行中に複数回実行すると異なるスナップショット識別子が返り、複数の別トランザクションでそれぞれの時点のデータを参照することもできます。

<Session 2-同期元>
postgres=# select pg_export_snapshot();
 pg_export_snapshot
--------------------
 000007CE-1
(1 row)
データ追加

同期元とは別のセッションで、先ほどのテーブルにデータを1,000件追加してコミットしておきます。同期元トランザクションはREAD COMMITTED分離レベルなので、参照できるデータは2,000件になります。

<Session 1-データ操作>
postgres=# INSERT INTO foo SELECT id, to_char(id, '00000000') FROM generate_series(1001, 2000) id;
INSERT 0 1000
<Session 2-同期元>
postgres=# SELECT count(*) FROM foo;
 count
-------
  2000
(1 row)
同期先トランザクションの開始

別のセッションでトランザクションを開始します。が、同期元との間に分離レベルの制約があります。

同期元同期先
SERIALIZABLEREPEATABLE READまたはSERIALIZABLE
REPEATABLE READREPEATABLE READ
READ COMMITTEDREPEATABLE READ

ここでは同期元がREAD COMMITTEDなので、REPEATABLE READを使います。

<Session 3-同期先1>
postgres=# BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN
スナップショットのインポート

同期先のトランザクションで先ほど取得したスナップショット識別子を指定してpg_import_snapshot()関数を実行します…と言いたいところですが、インポートにはSET TRANSACTION SNAPSHOT文を使うことになっています。エクスポートした時点と同じく参照できるのは1,000件が参照ですね。

<Session 3-同期先1>
postgres=# SET TRANSACTION SNAPSHOT '000007CE-1';
SET
postgres=# SELECT count(*) FROM foo;
 count
-------
  1000
(1 row)

なお、SET TRANSACTION SNAPSHOT文はそのトランザクションの最初のクエリでなければならない、という制約があります。私は最初にpsqlでSET TRANSACTION文を打つときに補完機能を使ってしまい、裏でSELECT文が発行されてトランザクション開始からやりなおすはめになりました…

別のスナップショットをエクスポート/インポート

同期元トランザクションから2,000件参照できる状態でエクスポートしたスナップショットを使うと、同じデータが参照できます。

<Session 2-同期元>
postgres=# select pg_export_snapshot();
 pg_export_snapshot
--------------------
 000007BB-1
(1 row)
<Session 4-同期先2>
postgres=# BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN
postgres=# SET TRANSACTION SNAPSHOT '000007CE-1';
SET
postgres=# SELECT count(*) FROM foo;
 count
-------
  2000
(1 row)

まとまってないまとめ

  • トランザクション分離レベルの制約があるので、利用シーンは限られるかも。まだ英語のみですが、詳細はPostgreSQL: Documentation: Manuals: System Administration Functionsにあります。
  • psqlの補完機能は裏でSQL文を発行するので、SET TRANSACTION SNAPSHOT文に使っちゃダメ。
  • スナップショットは実は$PGDATA/pg_snapshotにテキストファイルで保存されている。書き換えは試してませんがきっと未定義動作でしょう。
  • あくまで「スナップショット同期」であって同一トランザクションになるわけではないので、別トランザクションが更新した未コミットのデータは見えない(当たり前ですが)。これができればpgsql_fdwとか非同期クエリとか組み合わせてパラレルクエリができるかな、と思ってたんですがそうは問屋が卸さないようです。

このエントリ読んだ方で何か面白い使い道を思いついた方がいたら、ぜひ教えてください。m(_ _)m

明日のPostgreSQL Advent Calendarは〜♪

fujii_masaoさんです。

2011-12-07 21:06:00.000002 by PostgreSQL Advent Calendar 2011

PostgreSQLで統計解析 PL/R 基礎編 : PostgreSQL Advent Calendar #6


PostgreSQL Advent Calendar 12/6 です。

ネタを決めるにあたり、analyticsをテーマに何かないものかと探してみたら、
PL/R(R手続き型言語)なるものがあるそうで。

Rは統計解析分野で人気のプログラミング言語。
オープンソースで、コミュニティの活動も活発なようです。
参考:R Advent Calendar 2011 : ATND

では、PL/Rを導入して、ちょこっと触ってみます。


Install
今回構築した環境は下記。
・Mac OS X 10.6 Snow Leopard
・gcc 4.2.1
・postgresql 9.0.4
・R 2.14.0
・plr-8.3.0.13

Windows,Linuxへのインストールは下記をご参考
R と PostgreSQL - RjpWiki

まずRをインストール。
(macのhomebrewを使用)

brew install r
Error: This formula requires a fortran compiler
失敗。fortranコンパイラをインストールして再チャレンジ。
brew install gfortran
brew install r
OK。インストール時に一般的なインストール先に
リンクさせた方がいいよとメッセージが出るので、
sudo ln -s "/usr/local/Cellar/r/2.14.0/R.framework" /Library/Frameworks
これでRのインストールは完了。
なお、パッケージインストールも可能。
参考:R for Mac OS X

次にPL/Rをインストール。
ソースは→ http://joeconway.com/plr/ から入手。
su - postgres
cd /path-to-postgres_source/contrib #ここにPL/Rのソースを配置
tar zxf plr-8.3.0.13.tar.gz
cd plr
export R_HOME=/Library/Frameworks/R.framework/Resources
make
make install
PL/Rのインストール完了。テスト用のDBを作成して言語を登録。
createdb r_test
cd /path-to-pgsql/contrib
psql r_test < plr.sql
psql -c "SELECT * FROM pg_language WHERE lanName = 'plr'" r_test #確認
これで導入完了。


Try working

PL/Rのドキュメントにある集約関数をテストしてみます。
(中央値を返す集約関数medianを作成)
create or replace function r_median(_float8) returns float as '
median(arg1)
' language 'plr';
CREATE AGGREGATE median (
sfunc = plr_array_accum,
basetype = float8,
stype = _float8,
finalfunc = r_median
);

SELECT median(v)
FROM (values(1.0),(2.5),(5.7),(3.4),(-0.9)) AS t(v);
-- => select 2.5
集約関数はfloat8を配列に集約して最後にr_median(_float8)を呼び出し、
Rの関数medianを計算しています。

ポイントはデータ型の違い。
R言語はベクトル処理言語であり、データはベクトルで持ちます。
例えば、上記のmedianの計算は、Rでは下記のように行います。
x <- c(1.0, 2.5, 5.7, 3.4, -0.9) #ベクトルを作成してxに代入
median(x) # => [1] 2.5
postgresからRへの引数の受け渡しについては、
・スカラ ⇒ 単一要素のベクトル(例外あり)
・ 一次元配列 ⇒ 複数要素のベクトル
・二次元配列 ⇒ 行列
・三次元配列 ⇒ 三次元配列
・三次元以上の配列 ⇒ サポートせず
・ 複合型 ⇒ データフレーム
となります。
参考:Passing Data Values

では最後に、統計解析の基礎として相関係数を計算する関数を作成してみます。
参考:R-Source
CREATE OR REPLACE FUNCTION correlation(_float8, _float8) RETURNS float8 AS '
cor(arg1, arg2)
' LANGUAGE 'plr';
列xと列yの相関係数を計算する場合は下記で。
SELECT correlation(array_agg(x),array_agg(y))
FROM (values(0.7,1.9),(-1.6,0.8),(-0.2,1.1),(-1.2,0.1),(-0.1,-0.1)
      ,(3.4,4.4),(3.7,5.5),(0.8,1.6),(0.0,4.6),(2.0,3.4)) AS t(x,y);
-- => select 0.795102...

以上、簡単にさわってみました。
それほど敷居は高くないように感じますが、どういう用途に使ったものか...


Be continued
応用編に続く ...のか?


Next
明日はs87さんです。よろしくお願いします。

2011-12-06 01:39:00.000001 by PostgreSQL Advent Calendar 2011

引越し

今後、技術ネタは、はてなの日々の記録 別館に移動することにしますた。

2011-12-05 23:22:28 by Toshi Harada

RPMで複数バージョンのPostgreSQLをインストールしてみよう

本章は、PostgreSQL Advent Calnedar #5の記事です。 本章では、RPMを使った複数バージョンのPostgreSQLのインストールについて解説します。特に、最近のコミュニティが配布しているRPMは以前のものとライブラリなどのインストール場所が若干異なっています。そのあたりの話題も含めて解説します。

2011-12-05 12:34:38 by Let's Postgres

JOINの書き方3種

PostgreSQLはテーブルの結合 (ジョイン, JOIN) が得意です。ぶっちゃけ、JOINを使わないのであれば他のDBを使ったほうが良いと思っているくらいです。で、そんなJOINなのですが、書き方に流派がいくつかあるようです。以下のようなテーブルを考えて:

CREATE TABLE item (item_id integer, price integer);
CREATE TABLE list (list_id integer, item_id integer, amount integer);

1. FROM にテーブルをならべて WHERE で条件を書く。Oracle流?

SELECT list_id, item.item_id, amount * price
  FROM item, list WHERE item.item_id = list.item_id

2. JOIN ~ ON 構文。実は括弧は要らない。

SELECT list_id, item.item_id, amount * price
  FROM item JOIN list ON item.item_id = list.item_id

3. JOIN ~ USING () 構文。こちらは括弧が必須。

SELECT list_id, item_id, amount * price
  FROM item JOIN list USING (item_id)

個人的には、3.がキータイプ数を減らせておススメ。「列名はテーブルを跨っても一意な名前を付ける必要がある」という制約もありますが、それはそれでミスを避けやすい気がします。また JOIN を使う 2 と 3 の書き方は、後から LEFT JOIN 等に変更する必要が出た場合でも、SQL文を大幅に書き換えずに済むのが地味に嬉しいです。ただ、SQL Server 等、USING が使えないDBもあるようなので、業界ではマイナーな書き方かもしれません。

2011-12-04 15:00:00.000006 by Takahiro Itagaki

[OSS/Linux]Leaky Views と Security Barrier : PostgreSQL Advent Calendar #4

このエントリはPostgreSQL Advent Calendarに参加しています。12/4(日)担当也。ヨーロッパ中部時間ではまだ12/3(土)ですが。


RDBMSで行レベルのアクセス制御を実現する方法として、利用者に対して直接のアクセス権を付与せずに、特定のビューを通してだけアクセスを許可するのはしばしば使われるテクニックです。

ですが、場合によっては不可視な行の中身を参照できてしまうというのは、あまり広く認知されている訳ではないようです。

ので、問題のポイントと、現在開発中の PostgreSQL v9.2 に提案しているアイデアをご紹介します。

ユーザ定義関数のCOST値による問題

ここでは、以下の表を例に考えてみます。

customerテーブル

列名制約
cidintprimary key
cnametextnot null
cmailtext
cpasswdtext

customerテーブルには全顧客の情報が格納されているため、利用者は自分自身の情報しか見る事ができないよう設定しましょう。

(便宜上 PostgreSQL ユーザ名が cname に対応するものとします)

postgres=# CREATE VIEW my_account AS SELECT * FROM customer
                  WHERE cname = getpgusername();
CREATE VIEW
postgres=# GRANT SELECT ON my_account TO public;
GRANT

本来、このテーブルには 3ユーザ分の情報が格納されているのですが、

postgres=# SELECT * FROM customer;
 cid | cname |       cmail       | cpasswd
-----+-------+-------------------+----------
 101 | alice | alice@example.com | abcdef
 102 | bob   | bob@example.com   | xyz123
 103 | eve   | eve@example.com   | deadbeaf
(3 rows)

確かに、自分自身の情報しか参照できないように見えます。

postgres=# SET SESSION AUTHORIZATION alice;
SET
postgres=> SELECT * FROM customer;
ERROR:  permission denied for relation customer
postgres=> SELECT * FROM my_account;
 cid | cname |       cmail       | cpasswd
-----+-------+-------------------+---------
 101 | alice | alice@example.com | abcdef
(1 row)

しかし、利用者がSQL関数を定義できる場合、面白い事が起こります。

publicスキーマはデフォルトでCREATE権限を全体に与えている事に注意!)

postgres=> CREATE FUNCTION f_leak(text) RETURNS bool LANGUAGE plpgsql
           COST 0.00000001
           AS 'BEGIN RAISE NOTICE ''f_leak => %'', $1; RETURN true; END';
CREATE FUNCTION

postgres=> SELECT * FROM my_account WHERE f_leak(cmail);
NOTICE:  f_leak => alice@example.com
NOTICE:  f_leak => bob@example.com
NOTICE:  f_leak => eve@example.com
 cid | cname |       cmail       | cpasswd
-----+-------+-------------------+---------
 101 | alice | alice@example.com | abcdef
(1 row)

おっと、何か見えてはならないモノが見えたようです。

どういう事なのでしょうか、ちょっと EXPLAIN で調べてみましょう。

postgres=> EXPLAIN SELECT * FROM my_account WHERE f_leak(cmail);
                           QUERY PLAN
-----------------------------------------------------------------
 Seq Scan on customer  (cost=0.00..20.85 rows=1 width=100)
   Filter: (f_leak(cmail) AND (cname = (getpgusername())::text))
(2 rows)

この実行計画はVIEWの本体である customer テーブルをスキャンしていますが、利用者が付与した f_leak() とVIEWの条件を順にチェックしています。

問題は、副作用を持つ f_leak() の実行コストが非常に小さな値に設定されているため、オプティマイザは cname = getpgusername() より先にf_leak()を実行して不必要な条件の判断を省略した方が得策であると判断して、関数の実行順序を並べ替えている事です。その結果、不可視であるべき行の内容が引数としてf_leak()に渡され、それが利用者に漏えいしている訳です。

JOINと条件句の分配に伴う問題

同様に、VIEWによる行レベルアクセス制御を破るシナリオはもう一つ知られています。

先ほどの customer テーブルに加えて、もう一つテーブルを追加して考察を進めてみましょう。

creditテーブル

列名制約
cidintreferences customer(cid)
numbertext
expireddate

この credit テーブルは顧客のクレジットカード番号を保持しています。先ほどの my_account ビューと同様に、自分自身のレコードだけを参照できるようなVIEWを定義してみましょう。

postgres=# SELECT * FROM customer;
 cid | cname |       cmail       | cpasswd
-----+-------+-------------------+----------
 101 | alice | alice@example.com | abcdef
 102 | bob   | bob@example.com   | xyz123
 103 | eve   | eve@example.com   | deadbeaf
(3 rows)

postgres=# SELECT * FROM credit;
 cid |       number        |  expired
-----+---------------------+------------
 101 | 1111-2222-3333-4444 | 2014-02-28
 102 | 5555-6666-7777-8888 | 2013-10-30
 102 | 1234-5678-1234-5678 | 2015-06-30
 103 | 0987-6543-2109-8765 | 2014-08-31
(4 rows)

postgres=# CREATE VIEW my_credit AS SELECT cname, cmail, credit.*
           FROM customer NATURAL JOIN credit WHERE cname = getpgusername();
CREATE VIEW
postgres=# GRANT SELECT ON my_credit TO public;
GRANT

おや、やっぱり何かおかしいようです。

postgres=# SET SESSION AUTHORIZATION alice;
SET
postgres=> SELECT * FROM my_credit;
 cname |       cmail       | cid |       number        |  expired
-------+-------------------+-----+---------------------+------------
 alice | alice@example.com | 101 | 1111-2222-3333-4444 | 2014-02-28
(1 row)

postgres=> SELECT * FROM my_credit WHERE f_leak(number);
NOTICE:  f_leak => 1111-2222-3333-4444
NOTICE:  f_leak => 5555-6666-7777-8888
NOTICE:  f_leak => 1234-5678-1234-5678
NOTICE:  f_leak => 0987-6543-2109-8765
 cname |       cmail       | cid |       number        |  expired
-------+-------------------+-----+---------------------+------------
 alice | alice@example.com | 101 | 1111-2222-3333-4444 | 2014-02-28
(1 row)

もう一度 EXPLAIN で実行計画を眺めてみましょう。

postgres=> EXPLAIN SELECT * FROM my_credit WHERE f_leak(number);
                              QUERY PLAN
----------------------------------------------------------------------
 Hash Join  (cost=20.89..43.96 rows=2 width=104)
   Hash Cond: (credit.cid = customer.cid)
   ->  Seq Scan on credit  (cost=0.00..21.60 rows=387 width=40)
         Filter: f_leak(number)
   ->  Hash  (cost=20.85..20.85 rows=3 width=68)
         ->  Seq Scan on customer  (cost=0.00..20.85 rows=3 width=68)
               Filter: (cname = (getpgusername())::text)
(7 rows)

困ったことに、『creditテーブルをf_leak()条件でスキャンした結果』と『customerテーブルをcname = getpgusername()条件でスキャンした結果』がJOINされています。

オプティマイザはJOINすべき行を最小化するよう条件句を分配するのですが、f_leak()関数は credit テーブルの number 列のみ、cname = getpgusername() 条件は customer テーブルの cname 列のみに依存しています。そのため、JOINの完了を待つ事なく個々のテーブルをスキャンする時点で条件句を実行した方が、JOINすべき行数を減らす事ができます。

その結果、副作用を持つf_leak()がcreditテーブルのスキャン計画に push-down され、最初の例と同様に、不可視であるべき行の内容がf_leak()に渡され、それが利用者に漏えいしてしまっています。

この2つの問題は共に、オプティマイザがVIEWの境界を越えて関数の実行順序を入れ替えている事が原因です。これは性能観点からは優れた実装ですが、セキュリティを目的としたVIEW定義という観点では問題です。

一方で、VIEW内部で使われている関数を全て評価してから、その外部から与えられた関数を評価するという実装は、安全ですが、性能上無視できない性能劣化をもたらします。例えば、1万行 x 1万行のテーブルをJOINする場合、外部から与えられた関数をテーブルスキャンの時点で評価する事で片方の行数を1万行から100行に絞り込めるとしたら、9900万行分のJOIN処理を省略する事ができます。

次に、PostgreSQL v9.2に向けて提案されている Leaky View 問題への対策を紹介しましょう。

VIEW の security_barrier 属性と最適化の抑制

ここからは、私の提案している「Fix Leaky View Problemパッチ」の解説です。

前節で考察したように、VIEWを行レベルアクセス制御の目的で利用する場合には、パフォーマンスとセキュリティのトレードオフが存在します。安全側に倒せば許容できない程の性能劣化を招く可能性があり、一方、性能最適であれば情報漏えいの危険があります。

Fix Leaky Views Problem パッチは、CREATE VIEW構文でWITH(...)句を用いてオプション値を指定することを許容します。構文は以下の通りです。

CREATE VIEW view_name [WITH (options[,...])] AS select_statement;
options:
  security_barrier[= true|false]

security_barrier オプションは、VIEWが行レベルアクセス制御を目的として定義されていることを示す属性です。これを指定することで、一部のクエリ最適化を抑制する事が可能になります。

この設計に至るまでには長い議論があったのですが、結局、パフォーマンスとセキュリティのどちらが重要であるのかを判断できるのはVIEWを定義する人のみである、というシンプルな結論にたどり着いたのでした。

VIEWにsecurity_barrier属性が付与されている時、VIEWの内側で使用されている全ての関数・条件句は、VIEWの外側から与えられた関数・条件句よりも先に実行される事が保証されます。

では、実際に試してみましょう。以下で定義する my_account_secure と my_credit_secure は、先ほどの2つの例で使用したVIEWにsecurity_barrier属性を付加したものです。

postgres=# CREATE VIEW my_credit_secure WITH (security_barrier) AS
           SELECT cname, cmail, credit.* FROM customer NATURAL JOIN credit
           WHERE cname = getpgusername();
CREATE VIEW
postgres=# GRANT SELECT ON my_account_secure TO public;
GRANT
postgres=# CREATE VIEW my_account_secure WITH (security_barrier) AS
           SELECT * FROM customer WHERE cname = getpgusername();
CREATE VIEW
postgres=# GRANT SELECT ON my_credit_secure TO public;
GRANT

動作結果は以下のようになりました。"f_leak => ..." と表示されている内容は、クエリによって本来参照可能なデータの範囲内に収まっている事が分かります。

postgres=# SET SESSION AUTHORIZATION alice;
SET
postgres=> SELECT * FROM my_account_secure WHERE f_leak(cmail);
NOTICE:  f_leak => alice@example.com
 cid | cname |       cmail       | cpasswd
-----+-------+-------------------+---------
 101 | alice | alice@example.com | abcdef
(1 row)

postgres=> SELECT * FROM my_credit_secure WHERE f_leak(number);
NOTICE:  f_leak => 1111-2222-3333-4444
 cname |       cmail       | cid |       number        |  expired
-------+-------------------+-----+---------------------+------------
 alice | alice@example.com | 101 | 1111-2222-3333-4444 | 2014-02-28
(1 row)

では、VIEWにsecurity_barrier属性を付加することで、クエリ実行計画にどのように変化しているのでしょうか?先ほどと同じように、EXPLAIN構文で調べてみましょう。

postgres=> EXPLAIN SELECT * FROM my_account_secure WHERE f_leak(cmail);
                               QUERY PLAN
-------------------------------------------------------------------------
 Subquery Scan on my_account_secure  (cost=0.00..20.88 rows=1 width=100)
   Filter: f_leak(my_account_secure.cmail)
   ->  Seq Scan on customer  (cost=0.00..20.85 rows=3 width=100)
         Filter: (cname = (getpgusername())::text)
(4 rows)

f_leak()関数の評価は cname = getpgusername() 条件で customer テーブルをスキャンした後に行われる事が分かります。オプティマイザは security_viwe 属性を持ったVIEWの内側に条件句を push-down しなくなりました。

もう一つの例も同様です。

postgres=> EXPLAIN SELECT * FROM my_credit_secure WHERE f_leak(cmail);
                                 QUERY PLAN
----------------------------------------------------------------------------
 Subquery Scan on my_credit_secure  (cost=20.89..46.96 rows=2 width=104)
   Filter: f_leak(my_credit_secure.cmail)
   ->  Hash Join  (cost=20.89..46.90 rows=6 width=104)
         Hash Cond: (credit.cid = customer.cid)
         ->  Seq Scan on credit  (cost=0.00..21.60 rows=1160 width=40)
         ->  Hash  (cost=20.85..20.85 rows=3 width=68)
               ->  Seq Scan on customer  (cost=0.00..20.85 rows=3 width=68)
                     Filter: (cname = (getpgusername())::text)
(8 rows)

パッチ自体の動作原理は極めて単純です。

PostgreSQLは、一旦、VIEWに対するクエリを内部的にサブクエリに書き換えます。その後、オプティマイザがクエリ実行計画を作成する際に、"シンプル"なサブクエリ(OFFSET/LIMIT句を含まない…など)であれば、性能最適の観点からサブクエリをJOINを用いてフラット化(Pull-Up)します。

その後で、条件句はオプティマイザによって性能上最適な位置に振り分けられるため、VIEWの内側/外側といった区別はもはや意味を持たなくなります。

VIEWのsecurity_barrier属性は、この際の条件に作用します。RangeTblEntry構造体のsecurity_barrierは、関連するサブクエリがVIEWに由来し、かつ、VIEWのsecurity_barrier属性がtrueである場合にセットされます。

以下の処理では、security_barrier属性が false だとpull_up_simple_subquery()は呼ばれないため、サブクエリのフラット化は抑制されます。

--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -543,6 +543,7 @@ pull_up_subqueries(PlannerInfo *root, Node *jtnode,
         */
        if (rte->rtekind == RTE_SUBQUERY &&
            is_simple_subquery(rte->subquery) &&
+           !rte->security_barrier &&
            (containing_appendrel == NULL ||
             is_safe_append_member(rte->subquery)))
            return pull_up_simple_subquery(root, jtnode, rte,

さらにもう一ヶ所。条件句に与える引数が特定のサブクエリにだけ依存している場合、オプティマイザはこの条件句の実行をサブクエリ処理の中に移動(Push-Down)しようとしますが、同様にサブクエリが security_view 属性つきのVIEWに由来する時は、これをスキップします。

@@ -763,6 +769,7 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
      Node       *clause = (Node *) rinfo->clause;

      if (!rinfo->pseudoconstant &&
+         !rte->security_barrier &&
          qual_is_pushdown_safe(subquery, rti, clause, differentTypes))
      {
          /* Push it down */

この2ヶ所の処理を追加することによって、これまで見たような、VIEWを行レベルアクセス制御の目的に使用する場合の問題を回避する事ができます。

FUNCTION の leakproof 属性

Leaky View問題はVIEWのsecurity_barrier属性によって解決する事ができるのですが、これは一部のクエリ最適化を無効化するために、場合によっては、そのためのコストが看過できないほど大きい事もあります。

例えば、アプリケーションの設計上、以下のようなVIEWを定義し、VIEWの外側から条件句(主キーによる絞込みなど)を与えて使いたいというケースを考えてみましょう。

CREATE VIEW valid_credit WITH (security_barrier) AS
    SELECT * FROM credit WHERE card_is_valid(number, expired);

SELECT * FROM valid_credit WHERE cid = <customer-id>;

この場合、card_is_valid関数と、VIEWの外部から与えた cid = <customer-id> を用いて credit テーブルをスキャンした結果が利用者には返されます。ですが、VIEWにはsecurity_barrier属性が設定されているため、常にcard_is_valid関数が先に実行されます。

この制限は cid 列にインデックスが設定されていても同様です。したがってインデックス・スキャンが選択されるべき状況でも全件スキャンが選択されてしまいます。ああ困った、困った。

Fix Leaky View ProblemパッチはPart-1とPart-2から構成されており、Part-1は前述の security_barrier 属性の実装を、Part-2ではその例外を設定する機能を実装しています。

Part-2によって、先ほどのオプティマイザへの変更は一部修正されます。

--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -769,7 +769,8 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
        Node       *clause = (Node *) rinfo->clause;

        if (!rinfo->pseudoconstant &&
-           !rte->security_barrier &&
+           (!rte->security_barrier ||
+            !contain_leakable_functions(clause)) &&
            qual_is_pushdown_safe(subquery, rti, clause, differentTypes))
        {
            /* Push it down */

サブクエリがsecurity_barrier属性付きのVIEWに由来するとき、このif文は条件句のPush-Downを抑止しますが、Part-2パッチは条件句(clause)が leakable-functions (つまり情報を漏えいする可能性のある関数)を含んでいなければ、サブクエリへの条件句のPush-Downを許可するように修正します。

では、関数が情報を漏えいする可能性の有無をどのように設定するか。

それには、CREATE FUNCTION構文に新たに追加されるLEAKPROOF属性を使用します。

例えば、以下のように使用します。LEAKPROOFを指定することで、この関数に情報漏えいの恐れがないという事を明示的に指定できますが、これは同時に、潜在的に不可視の行の内容を参照することを可能にするため、関数のLEAKPROOF属性をセットするには特権ユーザの権限が必要です。

(SE-PostgreSQLでも db_procedure:{install}権限をチェックする予定です)

CREATE FUNCTION is_positive(int) RETURNS bool LANGUAGE plpgsql
    LEAKPROOF
    AS 'BEGIN RETURN $1 > 0; END';

一部のビルトイン関数の中でも、明らかに情報漏えいのリスクがない関数については、デフォルトでLEAKPROOF属性がセットされています。

(全部で2400個程あるため、網羅的なチェックはこれからですが…。)

例えば、32bit Integer同士の大小比較を行う int4gt 関数は、以下のように実装されています。

Datum
int4gt(PG_FUNCTION_ARGS)
{
    int32       arg1 = PG_GETARG_INT32(0);
    int32       arg2 = PG_GETARG_INT32(1);

    PG_RETURN_BOOL(arg1 > arg2);
}

この実装に情報漏えいの危険はありませんので、DB初期化時にLEAKPROOF属性はセットされています。

その他にも、現在のパッチでは各種ビルトインタイプの等価・大小比較演算子の実装として利用されている関数にLEAKPROOF属性がついています。実際に試してみましょう。

postgres=# SET SESSION AUTHORIZATION bob;
SET
postgres=> SELECT * FROM my_credit;
 cname |      cmail      | cid |       number        |  expired
-------+-----------------+-----+---------------------+------------
 bob   | bob@example.com | 102 | 5555-6666-7777-8888 | 2013-10-30
 bob   | bob@example.com | 102 | 1234-5678-1234-5678 | 2015-06-30
(2 rows)

ユーザ bob は2枚のクレジットカードを持っています。リッチメンですね。

では、2つの条件句を付加してみます。一つは先ほどのf_leak()関数、もう一つは expired < '2014-01-01' という Date 型の大小比較演算です。

postgres=> SELECT * FROM my_credit_secure WHERE f_leak(number) AND expired < '2014-01-01';
NOTICE:  f_leak => 5555-6666-7777-8888
 cname |      cmail      | cid |       number        |  expired
-------+-----------------+-----+---------------------+------------
 bob   | bob@example.com | 102 | 5555-6666-7777-8888 | 2013-10-30
(1 row)

NOTICEメッセージが一行だけ表示されているという事は、大小比較演算はf_leak()関数よりも先に実行されたようです。EXPLAINで実行計画を見てみましょう。

postgres=> EXPLAIN SELECT * FROM my_credit_secure WHERE f_leak(number) AND expired < '2014-01-01';
                                QUERY PLAN
---------------------------------------------------------------------------
 Subquery Scan on my_credit_secure  (cost=1.06..27.06 rows=1 width=104)
   Filter: f_leak(my_credit_secure.number)
   ->  Hash Join  (cost=1.06..27.04 rows=2 width=104)
         Hash Cond: (credit.cid = customer.cid)
         ->  Seq Scan on credit  (cost=0.00..24.50 rows=387 width=40)
               Filter: (expired < '2014-01-01'::date)
         ->  Hash  (cost=1.05..1.05 rows=1 width=68)
               ->  Seq Scan on customer  (cost=0.00..1.05 rows=1 width=68)
                     Filter: (cname = (getpgusername())::text)
(9 rows)

見ての通り、expired < '2014-01-01' 条件句が credit テーブルのスキャンに結びついているのと比較して、f_leak()関数はmy_credit_secureビューの内側にPush-Downされていません。これが LEAKPROOF 属性の有無による違いです。もし credit テーブルにインデックスが設定されていれば、Push-Downされた条件句により、全件スキャンの代わりにインデックス・スキャンが選択されるかもしれません。

まとめ

確かこの問題は、かれこれ2年以上議論を続けてきた息の長い問題です。

2009年9月4日のセキュアOS塾『SE-PostgreSQL vs Oracle Label Security』の資料の中で言及があります。(p.34)

http://sepgsql.googlecode.com/files/090904-jsosjk04-sepgsql-vs-ols.pdf

開発コミュニティとしての方向性は、概ね上で紹介した形で収束しつつありますが、まだ v9.2 の新機能として紹介できるかどうか、は分からない状況です。が、SE-PostgreSQLの行レベルアクセス制御機能を実現するためにもマージしておきたい機能ですので、なんとかcommitできるよう頑張りたいところです。

最後に『じゃあ、既存のシステムではどうやって対策したら良いのよ?』という質問に対して一つTIPSを紹介しておきたいと思います。

Q. PostgreSQL v9.1以前のバージョンでLeaky View問題を防ぐにはどうしたらよいか?

A. クエリに OFFSET 0 を付ける

オプティマイザがサブクエリをフラット化、または、条件句をPush-Downする時、サブクエリにOFFSET/LIMIT句が含まれている場合はそれを断念する、という事を思い出してください。

OFFSET 0は結果セットの先頭から値を読むという意味ですので、本来は何の意味もありません。ですが、ここまで説明した条件句の実行順序に起因する問題を防ぐには簡便な方法です。

ただし、関数のLEAKPROOF属性に相当する機能はありませんので、その点でトレードオフは必要になります。


PostgreSQL Advent Calendar向けに記事を書くにあたり、MySQL、MS SQL Server、Oracle Databaseなど他のRDBMSの挙動はどうなっているのか調べたかったのですが、時間がありませんでした。特に Oracle は勝手にWHERE句に条件をくっつけるVirtual Private Databaseという機能を持っていますので気になります。

これらは、追って調査したいと思います。きっと。いつの日か。アディオス、アミーゴ。


さて、翌 12/5(月) は笠原さんです。よろしく〜

2011-12-03 15:37:23.000005 by PostgreSQL Advent Calendar 12

PostgreSQL Advent Calendar

なんかノリでエントリしちゃった PostgreSQL Advent Calendar なんだけど、今までのみんなの記事が気合入っていて、すげープレッシャーな件・・・
なんか俺がUpする記事だけレベル低っ!&gdgdな記事になりそーな予感なんだけど・・・
みんな許してね☆(ゝω・)vキャピッ

2011-12-03 13:13:09.000005 by Toshi Harada

目的別ガイド:内部解析編

PostgreSQLがどのように動作しているのか、内部の動作を知って、開発やトラブルシュートに役立てましょう。ソースコードを読み解き方も紹介します。

2011-12-03 07:35:14.000005 by Let's Postgres

SELECT * FROM Web-API : PostgreSQL Advent Calendar #3

PostgreSQL Advent Calendar 12/3 です。

PostgreSQL 9.1ではSQL/MED規格の一部である 外部データラッパ (FDW: Foreign Data Wrapper) がサポートされました。これを使うと、Postgresの普通のテーブル以外の外部リソースもテーブルの形で検索、参照ができます。その拡張機能の一つに www_fdw があります。これは、Web APIの結果をテーブルとして取得するための、汎用フレームワークです。今回は、この www_fdw を使って、Google API のジオコーディング (地名 → 緯度・経度 変換) をしてみます。

環境構築

PostgreSQL 9.2dev で試しましたが、もちろん 9.1 でも動作すると思います。なお、XML機能を使うので、PostgreSQL本体を野良ビルドしている方は configure 時に --with-libxml をお忘れなく。

www_fdw を PGXN から取得するため、pgxn client を先にインストールしています。その後、www_fdw のダウンロード & インストールをし、テスト用に www_db データベースにログインします。

$ sudo easy_install pgxnclient
$ sudo yum install curl-devel
$ pgxn install www_fdw
$ pg_ctl start
$ createdb www_db
$ psql -d www_db

もし pgxn install の際に pg_config が見つからない旨のエラーが出る場合には、PATHが初期値にリセットされてるのかもしれません。自分は一時的にデフォルトパスに pg_config を置いてごまかしました。

$ sudo ln -s $PGDIR/9.2/bin/pg_config /bin/pg_config

Google Geocoding FDW の作成

www_fdw は Web API を使うための汎用フレームワークで、以下の2つをやってくれます。個別のAPIに対応させるには若干のカスタマイズが必要で、特に変換関数を用意するケースは多いと思われます。

  • WHERE句の条件をURLに変換し、HTTPリクエストを投げる。
  • HTTPレスポンスをテーブルにマッピングするために、変換関数を通す。

まずはEXTENSIONをインストールし、外部サーバを定義します。Google ジオコーディングAPIでは結果の形式が XML と JSON を選べますが、今回は、後で値を取り出せるよう xml を指定しておきます。なお、plv8jsを使えば、JSONからでも手軽に値を取り出せるかもしれません。

=# CREATE EXTENSION www_fdw;
=# CREATE SERVER google_geo_server FOREIGN DATA WRAPPER www_fdw OPTIONS (
     uri 'http://maps.google.com/maps/geo?output=xml',
     response_type 'xml',
     response_deserialize_callback 'google_geo_deserialize'
   );
=# CREATE USER MAPPING FOR current_user SERVER google_geo_server;
=# CREATE FOREIGN TABLE google_geo (
     q        text,
     address  text,
     north    float8,
     south    float8,
     east     float8,
     west     float8
   ) SERVER google_geo_server;

さて、マッピングの中心になるのが google_geo_deserialize 関数です。今回はXML出力を想定しているので、(xml型) ⇒ (テーブル型) の変換を行います。若干ごちゃごちゃしていますが、実際には、愚直にXMLからフィールドを取り出しているだけです。

=# CREATE FUNCTION google_geo_deserialize(options WWWFdwOptions, response xml)
    RETURNS SETOF google_geo AS $$
SELECT
  (
    (xpath('/ns:kml/ns:Response/ns:name/text()', $2,
       ARRAY[ARRAY['ns', 'http://earth.google.com/kml/2.0']]))[1]::text,
    (xpath('/Placemark/address/text()', x))[1]::text,
    (xpath('/Placemark/ExtendedData/LatLonBox/@north', x))[1]::text::float8,
    (xpath('/Placemark/ExtendedData/LatLonBox/@south', x))[1]::text::float8,
    (xpath('/Placemark/ExtendedData/LatLonBox/@east', x))[1]::text::float8,
    (xpath('/Placemark/ExtendedData/LatLonBox/@west', x))[1]::text::float8
  )::google_geo
FROM unnest(
  xpath('/ns:kml/ns:Response/ns:Placemark', $2,
    ARRAY[ARRAY['ns', 'http://earth.google.com/kml/2.0']])
) AS x
$$ LANGUAGE sql IMMUTABLE STRICT;

ジオコーディングを使ってみる

それでは実際に google_geo 外部表にアクセスしてみます。列 q に地名がずらりと並んでいるような仮想的なテーブルを想像してください。その中の地名の1つを取り出すイメージです。

=# \x
=# SELECT * FROM google_geo WHERE q = '東京タワー';
-[ RECORD 1 ]---------------------------------------
q       | 東京タワー
address | Tokyo Tower, Minato, Tokyo 105-0011, Japan
north   | 35.6676749
south   | 35.6495431
east    | 139.7614544
west    | 139.7294396

無事、緯度経度が取り出せました。さらに座標で検索を行う場合には point や box 型として扱ってもよいですね。

まとめ

www_fdw を使うと、PostgreSQL から直接 Web API を使うことができます。「入力はURLパラメータ、出力はXML」というタイプのAPIならば手間無く扱えます。また、それら以外の形式でも入出力のマッピング関数をカスタマイズできます。汎用フレームワークのため、ぎりぎりのチューニングは難しいかもしれませんが、HTTPを話せる多くのサービスと繋げられるのは夢が膨らみますね。twitter_fdw on www_fdw や、HTTPサーバモードのあるNoSQL DB (groonga 等) への簡易的なアクセス方法としても使えそうです。

2011-12-02 15:00:00.000004 by Takahiro Itagaki

Heroku Postgresを触ってみた PostgreSQL Advent Calendar #2

PostgreSQL Advent Calendar 12/2。もちろん日本時間で進行してます。

さてあまりディープな話しても仕方ないかなと思い、今話題絶頂渋谷女子高生の間でも噂になっていると言われるHeroku Postgresを早速使ってみた感想。ご存じない方のために念のため説明しておくと、HerokuがクラウドサービスとしてPostgreSQL単品で使えるサービスをこの間から提供しています。Database As A Serviceというやつか。

Heroku Postgres

まずはログイン。Webサービスの方は無料から始められるけど、Postgresは最低月200ドルの有料サービスなのでクレジットカードを登録。Postgresの方とWebとでシングルサインオンです。

ログインしてデータベース作成。マジで2クリックぐらいです。できあがるとこんな感じ。

f:id:umitanuki:20111201230105p:image

フルAjaxでさくさく動きます。データベース名やユーザ名、パスワードはランダム文字列が勝ってに割り振られますが、右上のConnection Settingの矢印からpsqlを選ぶとコピペでそのままシェルキックできる文字列を作ってくれます。ついでにJDBCやActiveRecord用文字列も作ってくれます。

SSL必須なのでソースインストールでpsql使っているようなマニアックな方はぜひ--with-opensslでビルドしなおしてください。っていうかこのオプション指定したことなかったアルよ。あ、おしゃれな六本木のお兄さんはもちろんpgAdminとかで接続してね。

psqlでログインすると何のからくりもないバニラPostgresです。\cでtemplate1にもつながります。おもむろにCREATE DATABASEすると権限がないとおこられます。\lはこんな感じ。

dv2jnpm7g28edxm=> \l
                                               List of databases
      Name       |      Owner      | Encoding |   Collate   |    Ctype    |          Access privileges          
-----------------+-----------------+----------+-------------+-------------+-------------------------------------
 dv2jnpm7g28edxm | uv25d4bx5cii4b0 | UTF8     | en_US.UTF-8 | en_US.UTF-8 | uv25d4bx5cii4b0=CTc/uv25d4bx5cii4b0+
                 |                 |          |             |             | collectd=c/uv25d4bx5cii4b0
 postgres        | postgres        | UTF8     | en_US.UTF-8 | en_US.UTF-8 | postgres=CTc/postgres
 template0       | postgres        | UTF8     | en_US.UTF-8 | en_US.UTF-8 | =c/postgres                        +
                 |                 |          |             |             | postgres=CTc/postgres
 template1       | postgres        | UTF8     | en_US.UTF-8 | en_US.UTF-8 | =c/postgres                        +
                 |                 |          |             |             | postgres=CTc/postgres
(4 rows)

9.0でDO構文使えますので

dv2jnpm7g28edxm=> do $$ declare begin raise 'hoge'; end; $$;
ERROR:  hoge

とかも動きました。

システムカタログもpg_authidとか以外は普通に見えるので、pg_pltemplateをのぞいてみました。

dv2jnpm7g28edxm=> select * from pg_pltemplate;
  tmplname  | tmpltrusted | tmpldbacreate |      tmplhandler       |        tmplinline        |   tmplvalidator   |    tmpllibrary    | tmplacl 
------------+-------------+---------------+------------------------+--------------------------+-------------------+-------------------+---------
 plpgsql    | t           | t             | plpgsql_call_handler   | plpgsql_inline_handler   | plpgsql_validator | $libdir/plpgsql   | 
 pltcl      | t           | t             | pltcl_call_handler     |                          |                   | $libdir/pltcl     | 
 pltclu     | f           | f             | pltclu_call_handler    |                          |                   | $libdir/pltcl     | 
 plperl     | t           | t             | plperl_call_handler    | plperl_inline_handler    | plperl_validator  | $libdir/plperl    | 
 plperlu    | f           | f             | plperl_call_handler    | plperl_inline_handler    | plperl_validator  | $libdir/plperl    | 
 plpythonu  | f           | f             | plpython_call_handler  | plpython_inline_handler  |                   | $libdir/plpython  | 
 plpython2u | f           | f             | plpython_call_handler  | plpython_inline_handler  |                   | $libdir/plpython2 | 
 plpython3u | f           | f             | plpython3_call_handler | plpython3_inline_handler |                   | $libdir/plpython3 | 
(8 rows)

ん?先日の話だと「安全じゃない(untrusted)言語なんて導入できない」とか言ってたような。

dv2jnpm7g28edxm=> create language plperl;
ERROR:  could not access file "$libdir/plperl": No such file or directory

なるほど。共有オブジェクト置いてないんですね。っていうかそれならpg_pltemplateからはずしとけよって気もしますが。パッケージインストールなのかな?

そのうちここにplv8jsが並ぶわけですね。わかります。

dv2jnpm7g28edxm=> create table t(a, b, c) as select i, i % 100, repeat('x', 100 + i % 100) from generate_series(1, 1000000)i;
SELECT 1000000
Time: 23165.819 ms

dv2jnpm7g28edxm=> select pg_size_pretty(pg_database_size('dv2jnpm7g28edxm'));
 pg_size_pretty 
----------------
 189 MB
(1 row)

Time: 107.376 ms

dv2jnpm7g28edxm=> select count(*) from t;
  count  
---------
 1000000
(1 row)

Time: 937.279 ms

手元のMacBookでやったらCTASは4秒ぐらいで終わったのでやっぱり書き込みはちょっと遅いですね。もちろんWAL設定が手元はデフォルトなんですが。ただ参照は手元と同じぐらいかちょっと早いぐらい。キャッシュが1.7Gでテーブル作成は自由にできて参照もこのぐらい出るのならば、なんかもうこれでいいかって気がします。ていうかこれで最低ランクなんですが、最高ランクのMechaって誰が月6400ドルも払って数十ギガのキャッシュのせるのか。面白いから用意してみたって匂いがぷんぷんするw。

さてさて目玉はレプリケーション。ポチットな。

f:id:umitanuki:20111201230106p:image

一回失敗しましたが、二回目で完成。ログもAjaxかWebSocketか知らないけどWeb画面開いたまま更新されていきます。もちろんこのFollowerも一台につき200ドル/月。

バックアップもワンクリック。リストアは新しいDBインスタンスになる模様。金稼ぐなー。バックアップのダウンロードもリンクのクリックでダウンロード始まります。形式は-Fcですね。

あ、ちなみに料金は月極の時間単位なので、11月31日の夜にDB作って早速2ドル請求が来ました。逆に言うとこうして実験してインスタンス破棄すれば200ドルもいらないわけです。ていうかこのぐらいの性能でレプリケーション、バックアップ、リストア、ログ管理等等を全く気にしなくていいとすれば、月200でサーバ+DBAなんか雇えないわけで、十分ペイする気がします。

そんなわけで長くなりましたが、Heroku Postgres使ってみました。

明日はitagaki.takahiroさんです。よろしう。


P.S.

pg_settingsをコピペしときます。興味のある人はご覧あれ。

dv2jnpm7g28edxm=> select name, setting from pg_settings;
              name               |                                                                  setting                                                                   
---------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------
 allow_system_table_mods         | off
 application_name                | psql
 archive_command                 | test -f /etc/postgresql/9.0/main/wal-e.d/ARCHIVING_OFF || envdir /etc/postgresql/9.0/resource6279_heroku_com/wal-e.d/env wal-e wal-push %p
 archive_mode                    | on
 archive_timeout                 | 60
 array_nulls                     | on
 authentication_timeout          | 60
 autovacuum                      | on
 autovacuum_analyze_scale_factor | 0.1
 autovacuum_analyze_threshold    | 50
 autovacuum_freeze_max_age       | 200000000
 autovacuum_max_workers          | 3
 autovacuum_naptime              | 60
 autovacuum_vacuum_cost_delay    | 20
 autovacuum_vacuum_cost_limit    | -1
 autovacuum_vacuum_scale_factor  | 0.2
 autovacuum_vacuum_threshold     | 50
 backslash_quote                 | safe_encoding
 bgwriter_delay                  | 200
 bgwriter_lru_maxpages           | 100
 bgwriter_lru_multiplier         | 2
 block_size                      | 8192
 bonjour                         | off
 bonjour_name                    | 
 bytea_output                    | hex
 check_function_bodies           | on
 checkpoint_completion_target    | 0.7
 checkpoint_segments             | 40
 checkpoint_timeout              | 300
 checkpoint_warning              | 30
 client_encoding                 | UTF8
 client_min_messages             | notice
 commit_delay                    | 0
 commit_siblings                 | 5
 constraint_exclusion            | partition
 cpu_index_tuple_cost            | 0.001
 cpu_operator_cost               | 0.0005
 cpu_tuple_cost                  | 0.003
 cursor_tuple_fraction           | 0.1
 custom_variable_classes         | 
 DateStyle                       | ISO, MDY
 db_user_namespace               | off
 deadlock_timeout                | 1000
 debug_assertions                | off
 debug_pretty_print              | on
 debug_print_parse               | off
 debug_print_plan                | off
 debug_print_rewritten           | off
 default_statistics_target       | 100
 default_tablespace              | 
 default_text_search_config      | pg_catalog.english
 default_transaction_isolation   | read committed
 default_transaction_read_only   | off
 default_with_oids               | off
 effective_cache_size            | 191250
 effective_io_concurrency        | 1
 enable_bitmapscan               | on
 enable_hashagg                  | on
 enable_hashjoin                 | on
 enable_indexscan                | on
 enable_material                 | on
 enable_mergejoin                | on
 enable_nestloop                 | on
 enable_seqscan                  | on
 enable_sort                     | on
 enable_tidscan                  | on
 escape_string_warning           | on
 extra_float_digits              | 0
 from_collapse_limit             | 8
 fsync                           | on
 full_page_writes                | on
 geqo                            | on
 geqo_effort                     | 5
 geqo_generations                | 0
 geqo_pool_size                  | 0
 geqo_seed                       | 0
 geqo_selection_bias             | 2
 geqo_threshold                  | 12
 gin_fuzzy_search_limit          | 0
 hot_standby                     | on
 ignore_system_indexes           | off
 integer_datetimes               | on
 IntervalStyle                   | postgres
 join_collapse_limit             | 8
 krb_caseins_users               | off
 krb_srvname                     | postgres
 lc_collate                      | en_US.UTF-8
 lc_ctype                        | en_US.UTF-8
 lc_messages                     | en_US.UTF-8
 lc_monetary                     | en_US.UTF-8
 lc_numeric                      | en_US.UTF-8
 lc_time                         | en_US.UTF-8
 listen_addresses                | *
 lo_compat_privileges            | off
 local_preload_libraries         | 
 log_autovacuum_min_duration     | -1
 log_checkpoints                 | on
 log_connections                 | off
 log_destination                 | syslog
 log_disconnections              | off
 log_duration                    | off
 log_error_verbosity             | default
 log_executor_stats              | off
 log_hostname                    | off
 log_line_prefix                 | %u [ONYX] 
 log_lock_waits                  | off
 log_min_duration_statement      | 50
 log_min_error_statement         | error
 log_min_messages                | notice
 log_parser_stats                | off
 log_planner_stats               | off
 log_rotation_age                | 1440
 log_rotation_size               | 10240
 log_statement                   | none
 log_statement_stats             | off
 log_temp_files                  | -1
 log_timezone                    | UTC
 log_truncate_on_rotation        | off
 logging_collector               | on
 maintenance_work_mem            | 65536
 max_connections                 | 500
 max_files_per_process           | 1000
 max_function_args               | 100
 max_identifier_length           | 63
 max_index_keys                  | 32
 max_locks_per_transaction       | 64
 max_prepared_transactions       | 500
 max_stack_depth                 | 2048
 max_standby_archive_delay       | -1
 max_standby_streaming_delay     | -1
 max_wal_senders                 | 10
 password_encryption             | on
 plpgsql.variable_conflict       | error
 port                            | 5432
 post_auth_delay                 | 0
 pre_auth_delay                  | 0
 random_page_cost                | 4
 search_path                     | "$user",public
 segment_size                    | 131072
 seq_page_cost                   | 1
 server_encoding                 | UTF8
 server_version                  | 9.0.5
 server_version_num              | 90005
 session_replication_role        | origin
 shared_buffers                  | 53120
 silent_mode                     | off
 sql_inheritance                 | on
 ssl                             | on
 ssl_renegotiation_limit         | 524288
 standard_conforming_strings     | off
 statement_timeout               | 0
 superuser_reserved_connections  | 3
 synchronize_seqscans            | on
 synchronous_commit              | on
 syslog_facility                 | local0
 syslog_ident                    | resource6279_heroku_com
 tcp_keepalives_count            | 9
 tcp_keepalives_idle             | 7200
 tcp_keepalives_interval         | 75
 temp_buffers                    | 1024
 temp_tablespaces                | 
 TimeZone                        | UTC
 timezone_abbreviations          | Default
 trace_notify                    | off
 trace_recovery_messages         | log
 trace_sort                      | off
 track_activities                | on
 track_activity_query_size       | 1024
 track_counts                    | on
 track_functions                 | none
 transaction_isolation           | read committed
 transaction_read_only           | off
 transform_null_equals           | off
 unix_socket_group               | 
 unix_socket_permissions         | 511
 update_process_title            | on
 vacuum_cost_delay               | 0
 vacuum_cost_limit               | 200
 vacuum_cost_page_dirty          | 20
 vacuum_cost_page_hit            | 1
 vacuum_cost_page_miss           | 10
 vacuum_defer_cleanup_age        | 0
 vacuum_freeze_min_age           | 50000000
 vacuum_freeze_table_age         | 150000000
 wal_block_size                  | 8192
 wal_buffers                     | 1024
 wal_keep_segments               | 127
 wal_level                       | hot_standby
 wal_segment_size                | 2048
 wal_sender_delay                | 200
 wal_sync_method                 | fdatasync
 wal_writer_delay                | 200
 work_mem                        | 102400
 xmlbinary                       | base64
 xmloption                       | content
 zero_damaged_pages              | off
(196 rows)

うーん。shared_buffers=53120ってことは8KBかけて400MBですね。キャッシュって何のことなんだろう。。。

2011-12-02 07:28:29.000004 by Hitoshi Harada

SQLで素因数分解をする - Project Euler #3

2011-12-01 13:23:21.000003 by PostgreSQL Advent Calendar 12

PostgreSQL の無限再帰 WITH 句で思うこと

お世話になっております、サイオス 那賀です。

PostgreSQL の再帰 WITH 句で、無限長のフィボナッチ数列を作ってみます。


template1=# WITH RECURSIVE
f(a, b) AS (VALUES(0, 1) UNION SELECT b, a + b FROM f)
SELECT a FROM f OFFSET 0 LIMIT 20;

a
------
0
1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
(20 rows)

template1=#

この "LIMIT" の用法は、下記の通り可搬性が低いので推奨はできないようですが、ちょっと面白いので。

これが動作するのは、PostgreSQLの実装が、実際に親問い合わせで取り出されるのと同じ数のWITH問い合わせの行のみを評価するからです。 この秘訣を実稼動環境で使用することは勧められません。 他のシステムでは異なった動作をする可能性があるからです。(「WITH問い合わせ(共通テーブル式)」 - PostgreSQL 文書

「必要な部分しか持ってこないので、データ元が無限でも OK」というところが、ちょっと Haskell 等の遅延評価型言語における無限長リストを思い起こさせます。

無限長のフィボナッチ数列。


module Fib where
f = 0:1:zipWith (+) f (tail f)

その頭 20 個を取得。


*Fib> take 20 f
[0,1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181]
*Fib>

RDBMS で、複雑な結合の実行計画を賢く最適化して、実際に必要なデータだけをストレージからフェッチしてくれる機能というのは、どこか遅延評価の考え方に似ているように思われます。


CREATE TABLE foo (k integer PRIMARY KEY, v text);
TRUNCATE TABLE foo;
INSERT INTO foo VALUES(1, '1st foo');
INSERT INTO foo VALUES(2, '2nd foo');
INSERT INTO foo VALUES(3, '3rd foo');
INSERT INTO foo VALUES(5, '5th foo');
INSERT INTO foo VALUES(7, '7th foo');

CREATE TABLE bar (k integer PRIMARY KEY, v text);
TRUNCATE TABLE bar;
INSERT INTO bar VALUES(1, '1st bar');
INSERT INTO bar VALUES(2, '2nd bar');
INSERT INTO bar VALUES(4, '4th bar');
INSERT INTO bar VALUES(6, '6th bar');
INSERT INTO bar VALUES(8, '8th bar');

当たり前ですが、直積をとってから絞り込むような、手続き型処理的なアホなことはしていませんね。


template1=# EXPLAIN SELECT * FROM foo CROSS JOIN bar;
QUERY PLAN
--------------------------------------------------------------------
Nested Loop (cost=23.53..30303.83 rows=1512900 width=72)
-> Seq Scan on foo (cost=0.00..22.30 rows=1230 width=36)
-> Materialize (cost=23.53..35.83 rows=1230 width=36)
-> Seq Scan on bar (cost=0.00..22.30 rows=1230 width=36)
(4 rows)

template1=# EXPLAIN SELECT * FROM foo CROSS JOIN bar WHERE foo.k = bar.k;
QUERY PLAN
--------------------------------------------------------------------
Merge Join (cost=170.85..290.46 rows=7564 width=72)
Merge Cond: (foo.k = bar.k)
-> Sort (cost=85.43..88.50 rows=1230 width=36)
Sort Key: foo.k
-> Seq Scan on foo (cost=0.00..22.30 rows=1230 width=36)
-> Sort (cost=85.43..88.50 rows=1230 width=36)
Sort Key: bar.k
-> Seq Scan on bar (cost=0.00..22.30 rows=1230 width=36)
(8 rows)

template1=#

PostgreSQL、賢いですねぇ。実行計画の最適化こそがデータベースの真髄ですな。

ではまた。

2011-12-01 06:40:00.000003 by SIOS OSS Tech

PostgreSQL 死活監視のタイムアウト

お疲れぎみです、サイオス 那賀です。

スクリプト等から PostgreSQL のプロセスが正しく動作しているかどうかを監視するための方法としては、pg_ctl コマンドを使ってのチェックと、実際にポートにアクセスしてのチェックがあると思います。

まずはプロセスの確認です。pg_ctl コマンドの "status" サブコマンドで、ローカルで動作している PostgreSQL のプロセスの状態を取ることができます。


$ /usr/local/pgsql/bin/pg_ctl -D ~/pgdata-main/ status
pg_ctl: postmaster is running (PID: 10532)
Command line was:
/usr/lib/postgresql/8.4/bin/postgres "-D" "/home/knaka/pgdata-main"

"-D" オプションで指定したデータディレクトリ下には "postmaster.pid" ファイルがあり、内容は、外部からのコネクションを受け付けている Postmaster プロセスのプロセス番号 (PID) です。この PID に対応するプロセスが動作しているどうかの結果を返します。

このコマンドがブロックしてしまうことはないのでしょうか? 少しソースも見てみましょう。Postmaster の死活を見ているのは、下記の部分です。src/bin/pg_ctl/pg_ctl.c です。


static bool
postmaster_is_alive(pid_t pid)
{
...
if (kill(pid, 0) == 0)
return true;
...
}

kill(2) にシグナル番号 0 を投げるというのはどういう意味でしたっけ? man には以下のようにあります。

If sig is 0, then no signal is sent, but error checking is still per-formed; this can be used to check for the existence of a process ID orprocess group ID.

単にプロセスが存在しているかどうかしか見ていませんが、これがブロックすることはなさそうなので、DB の状態に拠らず安心して呼べますね。しかしこれでは、プロセスはあるけれど実際にはサービスが行われていなかった場合を検出できません。

そこで、ネットワークのポート (TCP/IP なりドメインソケットなりの) に接続して死活監視を行います (パスワードを訊かれないよう、パスワードファイルなりサービスファイルなりは事前に設定しておいてください)。


$ psql -h localhost -p 5433 -U admin template1 -l
List of databases
Name | Owner | Encoding | Collation | Ctype | Access privileges
-----------+-------+----------+-----------+-------+-------------------
eucdb | admin | EUC_JP | C | C |
main | admin | UTF8 | C | C |
postgres | knaka | UTF8 | C | C |
template0 | knaka | UTF8 | C | C | =c/knaka
(中略)
$ echo $?
0

# なおここで、"postgres" データベースに接続してチェックするのはやめましょう。世の中には "postgres" データベースを持たない PostgreSQL データベースがあります。"template1" が無いことはありません。

しかしながら、もしポートを開いたまま死んでいる PostgreSQL へ不用意に接続して状態を確認してしまうと、いつまでも待ってしまいます。netcat コマンドで listen ポートを開いて試してみましょう。


$ nc -l 15432
(待ち...)

別のコンソールから。


$ psql -h localhost -p 15432 -U postgres postgres
(待ち...)

はい、返ってきません。もし一定時間ごとに死活監視のコマンドを発行するようにしていたら大変です。プロセスは増え続け、いずれリソースを食い尽くします。

そんな時、たとえば SSH でリモートのコマンドを叩くような状態監視であれば、タイムアウトのパラメータを渡すことで対処します。


$ nc -l 10022

$ ssh -p 10022 localhost
これだと戻りませんが。

$ ssh -o "ConnectTimeout=5" -p 10022 localhost
(5 秒経過…)
Connection timed out during banner exchange
$ echo $?
255
$

"ConnectionTimeout" パラメータを渡すことで、ちゃんとエラーを返すようになりました。

一方、psql コマンドのドキュメントにはそういうオプションが見当たりません。しかし psql コマンドは、実際のデータベースへの接続には libpq ライブラリを用いており、タイムアウトの設定はそちらにあります。libpq が理解する環境変数を見てみますと、"PGCONNECT_TIMEOUT=~" というのがありますね。試してみましょう。


$ PGCONNECT_TIMEOUT=5 psql -h localhost -p 15432 -U postgres postgres
(5 秒経過…)
psql: timeout expired
$ echo $?
2

めでたくタイムアウトし、エラーのリターンコードを返すようになりました。

では

2011-11-28 05:30:00 by SIOS OSS Tech

SQL Server 2012の記事がひどすぎる件

まあ今に始まったことではないかもしれませんが。。。

SQL Server 2012の全貌が見えてきた!新ライセンス体系、高可用性、見える化、インメモリ、そしてビッグデータ(2/3):企業のIT・経営・ビジネスをつなぐ情報サイト EnterpriseZine (EZ)

Enterpriseエディションならではの機能としてカラムベースのインデックス処理技術である「カラムストアインデックス」がある。Excel/SharePointのアドインである多次元インメモリ機能「PowerPivot」のカラムベースエンジン(VertiPack)をベースに、列(カラム)単位のインデックス格納を実現、データの圧縮率を大幅に高め、パフォーマンスを劇的に向上させる機能だ。斎藤氏によれば「件数にもよるが、フルテーブルスキャンに比べて100倍以上は高速になる」とのこと。日次集計など参照処理を行うデータウェアハウジングにおいて大幅な処理性能向上が期待できる。

カラム単位のインデックスって普通じゃね?インデックスって普通カラムに対して貼るものだよね??五味さんって以前もPostgreSQLかなんかの記事で意味不明なことを書いていた記憶があるからまあ記者なんてそんなもんだよねと思っていたら、Microsoftのページにも「Columnstore Index」って書いてあったなんだかなと思ったり。

SQL Server Columnstore Index FAQ - TechNet Articles - Home - TechNet Wiki

でよく読んでみると列圧縮(カラムコンプレッション)のことなんだと思う。ただしGreenplumのそれと違って元のテーブルはそのまま保存して列圧縮したデータを別に作るんだろうと思われる。なので「インデックス」と呼んでいるんだと思うんだけど、それって本質的にインデックスじゃないよね?? どっちかっていうとVerticaのマテリアライズドビューに近い気がする。だいたいあれですよ、Clustered Indexって奴だって、それ意味あるの??みたいな話でしょ。

ちなみに列圧縮はBIデータベースでは割とホットでして、元々列指向のVerticaやSybaseはともかく、Netizza、Teradata、Greenplumもサポート(予定)しており、ないと列強に加われないという明治時代の軍艦みたいなものです。あ、Oracle(Exadata)もサポートするとかしたとか。

Teradata Columnar and Teradata 14 compression : DBMS?2 : DataBase Management System Services

Jim Gray亡き今というか彼がいたからSQL ServerがBIっぽくなっちゃっただけで、MySQLみたいに「そういうのは関係ないですから」っていう方向を進めばよかったのに。

ジム・グレイ - Wikipedia

閑話休題。

AlwaysOnでは、最大4台のセカンダリサーバに複製可能で、自動/手動によるフェールオーバーが可能だ。セカンダリは同期モードと非同期モードに設定することができ、同期モードに設定すると「アクティブセカンダリ」として稼働させることができる。いつでもプライマリに取って代われるセカンダリ、という位置づけだ。これにより処理を負荷分散させながらコンピューティングリソースを使うことが可能で、高い可用性を維持できるという。デモでは東京⇔大阪間でのフェールオーバーが行われた場合、ダウンタイム15秒程度でセカンダリがプライマリのロールを引き継いで稼働するデモが実演された。ほぼリアルタイムの同期といっていいだろう。

15秒!?15秒でリアルタイムって呼んでいいの??何千件というトランザクションが消える(失敗する)と思うんですけど、いいんですね。それでいいんですね。WALをディスクに書くか、メモリに書くかですったもんだ議論しているPostgreSQLのレプリケーションはがんばりすぎですかね。もちろんフェイルオーバーが透過的にできるのは大事なことだと思いますけど、15秒ってひどくないですか。しかも最大4台とか。「カスケードしてもいい?」とかおちゃめなことを言ってるFujiiさんが泣きます。

まー総じてSQL Serverのテクノロジっていつの時代もこんなかんじで「えっ?」って話が多いような気がするけど、実際自分がその業界に行ってみてわかった、SQL ServerをBIで使ってるところはないに等しい(笑) Teradata、Oracle、Netizza、Greenplum、たまにSybaseとかAsterとかVerticaとかいう話は聞くけど、SQL Serverはないね。それでも大企業のバックエンドでWindowsサーバーと抱き合わせで使ってる人たち、かわいそうになあ、っていつも思うけど。Jとか。レプリケーションの怠慢っぷりに何度となく泣かされておりましたもので。まったくMSの誇大広告には目に余るものがある・・・

2011-11-25 05:26:51.000004 by Hitoshi Harada

PostgreSQL Advent Calendar のお知らせ

PostgreSQL Advent Calendarに関するお知らせとカレンダーへの参加募集告知です。2011年12月1日から、毎日異なる人がPostgreSQLに対する話題をblogなどの記事にし、リレーしていくイベントです。参加者募集中ですので、興味のある方は是非参加してみてください!

2011-11-23 16:01:10.000002 by Let's Postgres

PostgreSQL9.2の新機能を拾ってみる

PG9.1と9.2の索引ページを比べて、9.2の新機能を拾ってみました。この他、マルチコアでのスケーラビリティが大幅に向上しているはずですが、性能なので索引には載りませんね。

時間があるときに追ってみます。

2011-11-21 15:00:00 by Takahiro Itagaki

PostgreSQL を SNMP で監視する

お世話になっております、サイオス 那賀です。

pgsnmpd は、RFC1697 (RDBMS-MIB) に準拠した、PostgreSQL 用の SNMP エージェントです。CentOS 5 にインストールして試してみます。

まずは、ビルドに必要なパッケージをインストールします。


[root@co5v ~]# yum install -y gcc net-snmp-devel zlib-devel openssl-devel libxslt-devel pam-devel libtermcap-devel

ビルドします。Postgres Plus は、以前の記事で入れた通りです。pg_env.sh で pg_config にパスを通しておかないとビルドができませんので注意してください。


[root@co5v ~]# . /opt/PostgresPlus/9.0SS/pg_env.sh
[root@co5v ~]# tar zxvf pgsnmpd-1.0.tgz
(中略)
[root@co5v ~]# cd pgsnmpd-1.0/
[root@co5v pgsnmpd-1.0]# make
(中略)
[root@co5v pgsnmpd-1.0]#

適当な設定を書いてみます。


[root@co5v pgsnmpd-1.0]# cat test.conf
com2sec readwrite default public
group MyRWGroup v2c readwrite
view all included .1 80
access MyRWGroup "" any noauth exact all all none
agentaddress localhost:10161
[root@co5v pgsnmpd-1.0]#

実行してみます。


[root@co5v pgsnmpd-1.0]# ./pgsnmpd -c test.conf -C "dbname=postgres host=localhost user=postgres password=edb"
Could not set search path because of some problem with the set query
[init_smux] bind failed: Permission denied
PGSQL-SNMP-Ver1.0 is up and running.

net-snmp-utils を入れて、試しに叩いてみます。


[root@co5v ~]# yum install -y net-snmp-utils
(中略)
[root@co5v ~]# snmpwalk -v 2c -c public localhost:10161 mib-2.39
SNMPv2-SMI::mib-2.39.1.1.1.2.1 = OID: SNMPv2-SMI::enterprises.27645.1
SNMPv2-SMI::mib-2.39.1.1.1.2.11866 = OID: SNMPv2-SMI::enterprises.27645.11866
SNMPv2-SMI::mib-2.39.1.1.1.2.11874 = OID: SNMPv2-SMI::enterprises.27645.11874
(中略)
SNMPv2-SMI::mib-2.39.1.9.1.2.11874.1 = Hex-STRING: 07 DB 0A 1F 0F 1E 27 A8 2B 09 00
SNMPv2-SMI::mib-2.39.1.9.1.2.16393.1 = Hex-STRING: 07 DB 0A 1F 0F 1E 27 A8 2B 09 00
SNMPv2-SMI::mib-2.39.1.9.1.2.16393.1 = No more variables left in this MIB View (It is past the end of the MIB tree)
[root@co5v ~]# snmpwalk -v 2c -c public localhost:10161 mib-2.39.1.2.1.2.1
SNMPv2-SMI::mib-2.39.1.2.1.2.1 = STRING: "PostgreSQL 9.0.2 on x86_64-unknown-linux-gnu, compiled by GCC gcc (GCC) 4.1.2 20080704 (Red Hat 4.1.2-46), 64-bit"
[root@co5v ~]#

正直申し上げると、RDBMS-MIB による監視に馴染みがないので実用的かどうかは分からないのですが、パスワード認証なしでも状態が取れる点にメリットはあるかと思います。

2011-11-17 01:47:00.000003 by SIOS OSS Tech

PostgreSQL の構造とソースツリー(3)

2011-11-15 06:29:47.000001 by Let's Postgres

PostgreSQL の構造とソースツリー(2)

2011-11-15 06:17:05.000001 by Let's Postgres

PostgreSQL の構造とソースツリー(1)

PostgreSQLの全体構造とソースツリーの概要を説明します。おおまかな構造と、ソースコードの構成を見ていきます。デバッガを使って実際のコードの実行の流れを追う方法も紹介します。前提としているバージョンは PostgreSQL 9.1.x です。

2011-11-15 06:16:54.000001 by Let's Postgres

PG9.2 範囲型

PostgreSQL 9.2 で「範囲型 (Range Types)」が採用されます。範囲型は、{ 下限, 上限 } をペアで扱うデータ型で、数直線上の「範囲」を表現することができます。また、配列と同様、要素として各種スカラー型を選べます。整数の範囲、数値の範囲、日時の範囲など、用途にあわせて型を選択できます。

9.0 で追加された「排他制約」は、これまでは主に地理データ (box, circle 等) の重なりのチェックに使えましたが、9.2 では「数値や時間の幅」を保証することもできるようになります。用途としては、「有効期限開始 / 終了」を持っているようなデータは結構多いような気がしますし、用途も広がったのではないでしょうか。

以下、範囲型、範囲型関数、排他制約を使った簡単な例です。[1] と [2] は、同じ部屋で予約時間帯が重なっているのでエラーになりますが、[1] と [3] は部屋が異なるので問題ないことがわかります。

=# CREATE EXTENSION btree_gist;
=# CREATE TABLE reservation (
     room   text,
     during tsrange, -- range of timestamp
     EXCLUDE USING gist (room WITH =, during WITH &#38;&#38;)
   );
=# INSERT INTO reservation VALUES ('123A', '[2010-01-01 14:00, 2010-01-01 15:00)'); -- [1]
=# INSERT INTO reservation VALUES ('123A', '[2010-01-01 14:30, 2010-01-01 15:30)'); -- [2]
ERROR:  conflicting key value violates exclusion constraint "reservation_room_during_excl"
=# INSERT INTO reservation VALUES ('123B', '[2010-01-01 14:30, 2010-01-01 15:30)'); -- [3]
-- lower/upperで下限/上限を取り出し
=# SELECT room, lower(during), upper(during) FROM reservation;
 room |        lower        |        upper
------+---------------------+---------------------
 123A | 2010-01-01 14:00:00 | 2010-01-01 15:00:00
 123B | 2010-01-01 14:30:00 | 2010-01-01 15:30:00
(2 rows)

2011-11-14 15:00:00 by Takahiro Itagaki

現場で役立つ実践ノウハウWeb開発の「べし」「べからず」(開発編)

データベースの「べし」「べからず」集の、開発編です。ループ処理、データ取得件数、ビューの使用について解説していきます。

2011-11-14 01:52:46 by Let's Postgres

現場で役立つ実践ノウハウWeb開発の「べし」「べからず」(試験編)

データベースの「べし」「べからず」集の、試験編です。SQL実行計画の確認、テストデータの用意、チューニングやバックアップ/リストアの心構えを解説していきます。

2011-11-14 01:52:43 by Let's Postgres

Herokuオフィスに行ってPostgreSQLとpl/v8の話聞いて来た

f:id:umitanuki:20111108222542j:image

f:id:umitanuki:20111108222513j:image

f:id:umitanuki:20111108223759j:image

f:id:umitanuki:20111108223824j:image

f:id:umitanuki:20111108224911j:image

SFPUG二回目。今日はHerokuオフィスで直々にPostgreSQLの話を聞いてきましたよ。

彼らはpure PostgreSQLを使っていて、ソースには全く手を入れていない。が、デモを見る限りAmazonにPostgreSQLインスタンスをワンクリックで立ち上げてWALもばっちり保存(自作のWAL-Eとやらを使ってるらしい)。ストリーミングレプリケーションとホットスタンバイもWebの管理画面からワンクリックで追加。DBのスナップショットもワンクリック。まさにこれぞ象の群れと呼ぶべきかもしれない。

写真貼りますけどオフィスはサンフランシスコ市中にあっておしゃれなレンガ作りの建物を3フロアゆったり使ってオサレな感じ。雰囲気的にはCookPadが近いと思うけど、Ruby使いはいつもオサレだね。そんなオサレな連中がPostgreSQLに注目しているというのがまたホット。MySQLには全く興味がないってさ。

そんな彼らが今最も注目しているのがpl/v8。今俺触ってないって何回も言ってるのに「よくするにはどうしたらいいか」「このプロジェクトこそが今我々に最も必要だ」などとアツく語られてしまいました。やっぱHerokuのターゲットが小〜中規模のWebアプリをさくさく作ることにあるため、JavaScriptこそが最も使い勝手がいいらしい。おまけに速い(と彼らは思ってる)。メンバーの一人が作ったプレゼン↓

http://pgeu-plv8.herokuapp.com/

そんなわけでシャレで始めたplv8ですがHerokuだけでなく随分いろんな人が興味持ってるらしいしplv8開発者は絶賛期待されております。9.2か9.3にはJSONデータ型も入れてHerokuのデファクトにしたいってさ。そうなるとpl/perlやpl/pythonどころじゃなくなるかもねー

んだば

2011-11-09 08:04:54.000002 by Hitoshi Harada

PostgreSQL をバックエンドにして Zabbix を動かす

お疲れ様です、サイオス 那賀です。

Zabbix は、広く利用されている、オープンソースの統合監視ツールです。単純な監視ツールである NagiosMunin などと比べて、より詳細な監視が可能なエージェント型の構成をとり、プロキシやノードによる分散も考慮するなど、これ一つでとりあえず必要充分と言えるのではないでしょうか。

関連リンク:

さて、Zabbix はそのデータ保持に使用する RDBMS として PostgreSQL, MySQL, Oracle, SQLite をサポートしています。ところが世の Zabbix の書籍やインストール手順は、ほとんどが MySQL 用なので、せっかくですから PostgreSQL 用を書いてみようと思います。開発も含めて MySQL が Zabbix のプライマリ DB 扱いなので分は悪いのですが、それほど複雑なクエリは発行していないようですので、問題はないでしょう。

今回は CentOS 5 上に、日本のユーザ会配布の YUM レポジトリから Zabbix 1.8 を導入して構築をしてみます。各バイナリのバージョン等は、適宜現状のものに読み替えてください。また、同じ PostgreSQL でも、今回は米 EnterpriseDB 社Postgres Plus を使用してみます。

YUM によるインストールについてはこちらに案内があります。


[root@co5v ~]# rpm -Uvh http://www.zabbix.jp/binaries/relatedpkgs/rhel5/x86_64/zabbix-jp-release-5-3.noarch.rpm
(中略)
[root@co5v ~]# alternatives --display zabbix-jp-release
zabbix-jp-release -ステータスは自動です。
リンクは現在 /usr/share/zabbix-jp-release/zabbix-jp-1.6.repo を指しています。
/usr/share/zabbix-jp-release/zabbix-jp-1.1.repo - 優先項目 10
/usr/share/zabbix-jp-release/zabbix-jp-1.4.repo - 優先項目 20
/usr/share/zabbix-jp-release/zabbix-jp-1.6.repo - 優先項目 40
/usr/share/zabbix-jp-release/zabbix-jp-1.8.repo - 優先項目 30
現在の「最適」バージョンは /usr/share/zabbix-jp-release/zabbix-jp-1.6.repo です 。
[root@co5v ~]# alternatives --config zabbix-jp-release

4 プログラムがあり 'zabbix-jp-release' を提供します。

選択 コマンド
-----------------------------------------------
1 /usr/share/zabbix-jp-release/zabbix-jp-1.1.repo
2 /usr/share/zabbix-jp-release/zabbix-jp-1.4.repo
*+ 3 /usr/share/zabbix-jp-release/zabbix-jp-1.6.repo
4 /usr/share/zabbix-jp-release/zabbix-jp-1.8.repo

Enter を押して現在の選択 [+] を保持するか、選択番号を入力します:4
[root@co5v ~]# yum clean all
Loaded plugins: fastestmirror
Cleaning up Everything
Cleaning up list of fastest mirrors
[root@co5v ~]# yum install -y zabbix-agent zabbix-server-pgsql zabbix-web-pgsql
(中略)
[root@co5v ~]# chkconfig iptables off
[root@co5v ~]# service iptables stop
ファイアウォールルールを適用中: [ OK ]
チェインポリシーを ACCEPT に設定中filter [ OK ]
iptables モジュールを取り外し中 [ OK ]
[root@co5v ~]# chkconfig httpd on
[root@co5v ~]# service httpd start
httpd を起動中: [ OK ]
[root@co5v ~]# chkconfig zabbix-server on
[root@co5v ~]# service zabbix-server start
Starting zabbix server: [ OK ]
[root@co5v ~]# chkconfig zabbix-agent on
[root@co5v ~]# service zabbix-agent start
Starting zabbix agent: [ OK ]
[root@co5v ~]#

次に Postgres Plus です。上記 URL から辿れる Postgres Plus のユーザーサイトへログインし、「ダウンロード」から "Postgres Plus Standard Server 9.0 for Linux 64 ビット" を入手します。

次に、CentOS 上に root でログインします。Postgres Plus にはウィザード形式の GUI インストーラもついているのですが、慣れてくると面倒なので、今回はコマンドラインから必要な情報を全て与えて、自動的にインストールをしてみます。オプションの詳細については、"--help" を参照してください。


[root@co5v ~]# chmod 755 postgresplus-9.0.2-1-linux-x64.bin
[root@co5v ~]# ./postgresplus-9.0.2-1-linux-x64.bin \
--mode unattended \
--superpassword edb \
--enable-components dbserver \
--disable-components \
slony,pgJdbc,postgis,psqlOdbc,npgsql,pgbouncer,pgmemcache,pgagent \
--dbInstallTune 0 \
--updateNotification 0 \
--existingEmail dummy@example.com \
--existingpassword dummypass \
--locale ja_JP.utf8

[root@co5v ~]# chkconfig --list postgresql-9.0
postgresql-9.0 0:off 1:off 2:on 3:on 4:on 5:on 6:off
[root@co5v ~]# service postgresql-9.0 status
pg_ctl: server is running (PID: 13099)
/opt/PostgresPlus/9.0SS/bin/postgres "-D" "/opt/PostgresPlus/9.0SS/data"
[root@co5v ~]#

もし ident しかないようでしたら、password なり md5 なりでパスワード認証をするようにしておきます。Postgres Plus では、デフォルトで local への all のアクセスを MD5 認証について許可するようになっているので不要ですが、一応以下のように明示的に設定し、サービスを再スタートさせて反映させておきます。標準的な RPM ベースの PostgreSQL ですと、ident 認証のみになっていると思います。


[root@co5v ~]# cp /opt/PostgresPlus/9.0SS/data/pg_hba.conf /opt/PostgresPlus/9.0SS/data/pg_hba.conf.orig
[root@co5v ~]# vi /opt/PostgresPlus/9.0SS/data/pg_hba.conf
[root@co5v ~]# diff -uNr /opt/PostgresPlus/9.0SS/data/pg_hba.conf.orig \
/opt/PostgresPlus/9.0SS/data/pg_hba.conf
--- /opt/PostgresPlus/9.0SS/data/pg_hba.conf.orig
+++ /opt/PostgresPlus/9.0SS/data/pg_hba.conf
@@ -73,6 +73,7 @@
# TYPE DATABASE USER CIDR-ADDRESS METHOD

# "local" is for Unix domain socket connections only
+local zabbix zabbix md5
local all all md5
# IPv4 local connections:
host all all 127.0.0.1/32 md5
[root@co5v ~]# service postgresql-9.0 restart
Restarting PostgreSQL 9.0:
waiting for server to shut down.... done
server stopped
waiting for server to start.... done
server started
PostgreSQL 9.0 restarted successfully
[root@co5v ~]#[root@co5v ~]#

次に、Zabbix 用のアカウントとデータベースを作成します。MySQL だとスキーマでネームスペースを作るところですが、そこは PostgreSQL ですので、データベース "zabbix" を作り、そこへデータを格納するようにします。ロールも "zabbix" とします。createuser の "-P" オプションで、新しいロールのためのパスワードを設定するようにします。うっかりクセで "-W" を指定しないように――"-W" は接続のためのパスワードであり、新ユーザのパスワードではありません。


[root@co5v ~]# echo localhost:5432:postgres:postgres:edb > ~/.pgpass
[root@co5v ~]# chmod 600 ~/.pgpass
[root@co5v ~]# . /opt/PostgresPlus/9.0SS/pg_env.sh
[root@co5v ~]# createdb -U postgres zabbix
[root@co5v ~]# createuser -U postgres --pwprompt --no-superuser --no-createrole --no-createdb zabbix
Enter password for new role: zabbix
Enter it again: zabbix
[root@co5v ~]# psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE zabbix TO zabbix"
GRANT
[root@co5v ~]# echo localhost:5432:zabbix:zabbix:zabbix >> ~/.pgpass
[root@co5v ~]# psql -x -U zabbix zabbix -c "SELECT now()" # 試しに
-[ RECORD 1 ]----------------------
now | 2011-XX-XX 10:52:13.482851+09

[root@co5v ~]#

Zabbix の初期データを流しこんで、前準備は終了です。


[root@co5v ~]# psql -U zabbix zabbix /usr/share/doc/zabbix-server-1.8.*/schema/postgresql.sql
(中略)
[root@co5v ~]# psql -U zabbix zabbix /usr/share/doc/zabbix-server-1.8.*/data/data.sql
(中略)
[root@co5v ~]# psql -U zabbix zabbix /usr/share/doc/zabbix-server-1.8.*/data/images_pgsql.sql
(中略)
[root@co5v ~]#

後は、http://~/zabbix/ に接続して、設定を行います。今回は、第 4 ステップ "Configure DB connection" での設定は以下のようになりました。"Test connection" をクリックして、接続が "Ok" となれば次へ進みます。

Type:PostgreSQL
Host:localhost
Port:0 ("0" でデフォルトの 5432 を利用)
Name:zabbix
User:zabbix
Password:zabbix

それ以降の設定、使い方は、他の DB と同様です。web コンソールの初期パスワードは "Admin/zabbix" です。

同じ Zabbix でも、バックエンドを PostgreSQL にすることで、PostgreSQL ならではのオンライン物理バックアップや、ストリーミング・レプリケーションによる代替機の用意など、他 DB とは違った運用が考えられるのではないかと思います。

2011-11-03 02:35:00.000003 by SIOS OSS Tech

現場で役立つ実践ノウハウWeb開発の「べし」「べからず」(運用編)

データベースの「べし」「べからず」集の、運用編です。サーバログ、性能、リソースの監視、統計情報のメンテナンス、アップグレードを見込んだ運用計画を解説していきます。

2011-11-02 05:26:07.000002 by Let's Postgres

現場で役立つ実践ノウハウWeb開発の「べし」「べからず」(設計編)

データベースの「べし」「べからず」集の、設計編です。データの増加量を考慮した設計と、パラメータ設定について解説していきます。

2011-11-02 03:46:44.000002 by Let's Postgres

現場で役立つ実践ノウハウWeb開発の「べし」「べからず」

ある技術を習得する際、まず最初は「べし」を吸収していくことが重要視されます。一方「べからず」はある程度基本知識や周辺知識を身につけ、自分が危機的状況に陥らないと、なかなか理解ができないものです。また、自分の環境に合った形で応用力をつける必要もあります。データベースの「べし」「べからず」を整理して解説しています。

2011-11-02 03:43:19.000002 by Let's Postgres

JPUGしくみ文化会勉強会@市ヶ谷

今日はJPUGの勉強会のために市ヶ谷へ遠征。

市ヶ谷の駅で降りるのは初めてだ。

今日のお題は3つ。

  • EXPLAINING EXPLAIN(3)
    • これまでのおさらい+最新情報+チューニング例
  • ロックとWAL
    • さまざまなレイヤでの排他制御の話
    • LWLock横取り問題
    • かなりムズイ話だが、なかなか面白い。続きが聞きたいところ。
  • 全文検索
    • 前回のおさらい
    • pg_tgrmの良い点・悪い点
      • 2文字以下だとインデックスが効かない問題があるのか・・・日本語だと厳しい。
    • textsearch_sennaの問題点(更新・削除に完全に対応出来ない)
    • みんなが求める全文検索は何か?というディスカッション

勉強後は恒例の懇親会。今回は2次会まで行く。

  • 次回の発表にエントリしておく。テーマはFDW。
  • 山手線内側はやっぱりオサレだよね・・・
  • @cstyles_jp 氏の超絶な開発現場の話が面白すぎるw
    • でもそんな開発は俺には無理。技術的にも精神的にも無理。
  • 本って書いてもなかなか売れないね~
    • そこで萌え絵コラボですよ。
    • 「もし女子高生がPostgreSQLのStorage Managerを読んだら」
      • 誰か書いてくれw
  • アウェイで飲んで横浜へ戻るのはやっぱりきつい・・・
    • つーか、中央線がなんかgdgdだったし。
    • アキバで京浜東北線に乗り換えて帰還。磯子に運ばれないように意識を保つのが大変w

2011-10-29 04:24:41.000005 by Toshi Harada

第21回しくみ勉強会:全文検索(後編)

10/29 (土) の「第21回しくみ分科会+アプリケーション分科会勉強会」で、全文検索の続きを話します。pg_trgmにも無視できない問題があった話と、そろそろ引退させたいものの代わりが見つからない textsearch_senna を中心に。

2011-10-26 15:00:00.000002 by Takahiro Itagaki

WEB+DB 65 PostgreSQL 9.1 特集

技術評論社 WEB+DB PRESS Vol.65 で、PostgreSQL 9.1 特集を執筆しました。

  • 新機能レビュー
  • レプリケーション
  • 拡張機能

の3本立てです。

2011-10-23 15:00:00.000006 by Takahiro Itagaki

PostgreSQLで時差を計算する

最近は大陸間で電話することが多くて「今あっち何時?」的な脳みそが足りない自分は苦労するわけです。タイムゾーンで時差を教えてくれる手っ取り早い方法がPostgreSQLに内蔵されたOlson Databaseをつつくこと。

-- PDT (Pacific Daylight Time)で何時だっけ?
SELECT timezone('PDT', '2011-10-17 15:40:00');
      timezone       
---------------------
 2011-10-16 23:40:00
(1 row)

-- その9時間後は?
SELECT timezone('PDT', '2011-10-17 15:40:00 JST'::timestamptz + interval '9 hours');
      timezone       
---------------------
 2011-10-17 08:40:00
(1 row)

タイムゾーンの指定の仕方はpg_timezone_names/pg_timezone_abbrevsから。

TABLE pg_timezone_names;
TABLE pg_timezone_abbrevs;

Olsonデータはだいぶ昔から採用しているみたいですが、実装はまるで最近のFDWですね。

余談ですが、世界で一番早く朝を迎えるのはどこでしょう。

SELECT * FROM pg_timezone_names ORDER BY utc_offset DESC LIMIT 3;
        name        | abbrev | utc_offset | is_dst 
--------------------+--------+------------+--------
 Etc/GMT-14         | GMT-14 | 14:00:00   | f
 Pacific/Kiritimati | LINT   | 14:00:00   | f
 Pacific/Chatham    | CHADT  | 13:45:00   | t
(3 rows)

Kiritimati、GMT+14ってフライングしすぎ。

というわけで、時間の計算は下手なアプリをダウンロードするよりpsqlの方がよっぽど早い。ぜひお試しあれ。

2011-10-14 09:49:27.000004 by Hitoshi Harada

textsearch_senna 9.1 対応

9.1の途中で動かなくなっていた件にやっと追従。意外とすんなりは行きませんでした。

http://archives.postgresql.org/pgsql-committers/2011-10/msg00125.php

  • Relation->rd_operator がいつの間にかなくなっていた。
  • INIT_FORKNUM, MAIN_FORKNUM に少なくとも1ページを作成しておかないと怒られる。

特に後者の仕様って、何のつもりなんでしょう?ファイルが無いと問題があるケースでも見つかったんでしたっけ?

また、EXTENSION 化も考えたんですが、現状インストールスクリプト内で、pg_am への行の追加をしているんですよね。CREATE EXTENSION で INSERT の発行は可能そうなのですが、DROP EXTENSION は単なる依存オブジェクトの DROP なので、DELETE を発行できなさそう。テーブルを書き換えるタイプの EXTENSION は、9.1時点ではうまく対応できない気がします。

2011-10-13 15:00:00.000003 by Takahiro Itagaki

PostgreSQL 9.1 リリース

2011年9月12日、PostgreSQL 9.1.0 が正式リリースされました。同期レプリケーション、UNLOGGED テーブル、位置情報のインデックス、真の SERIALIZABLE 分離レベル、WITH句での更新処理、SE-PostgreSQL、拡張モジュール管理、SQL/MED 外部テーブルといった新機能が加わりました。エンタープライズ・ユーザからも期待されるリリースです。

2011-09-21 02:59:49.000002 by Let's Postgres

PostgreSQL 9.1.0 リリース

PostgreSQL 9.1.0 が無事リリースされました。

とりあえず一番の注意は、文字列のエスケープに関する「Change the default value of standard_conforming_strings to on」ですね。リリースノートにもでっかくWarningがあるんですが、そこまでしてまで設定を変えたかった部分なのか…。

最近は仮想化がらみで VMware vFabricRed Hat Enterprise Virtualization 用のデータベースとしてニュースにものぼり、単に "Poor Man's Oracle" 以上の対抗馬になりつつあるんじゃないでしょうか。

2011-09-11 15:00:00.000006 by Takahiro Itagaki

oracle_fdw, mysql_fdw, ...

Foreign Data Wrapper for Oracle (Beta) released」というニュースが流れたので、チェックしてみました。

oracle_fdwPostgreSQL 9.1 の SQL/MED API を利用した拡張機能 (EXTENSION) で、Oracle のテーブルを、PostgreSQL からビューのように扱うことができるようです。コードを見る限り WHERE 句の push-down (条件の引き継ぎ) を頑張っているようで、各種演算子や関数の一部も Oracle 側に SQL として受け渡せるとのこと。データ型変換も一通り揃っており、「ちょっと Oracle のデータを持ってきて PostgreSQL で加工/検索」という用途には十分使えそうです。

ライセンスもBSDなので、今後のC言語ベースの FDW のお手本にもなりそうです。ちなみに、mysql_fdw も開発中のようですが、こちらは未完成で、クエリも全部 "SELECT * FROM tbl" になってしまいます。そして、未だに postgresql_fdw (PostgreSQLPostgreSQL) は出てきていないという不思議な状況……。

2011-08-28 15:00:00.000006 by Takahiro Itagaki

CHAR(11) コンファレンス参加レポート

イギリスで開催された PostgreSQL の高可用対応やレプリケーションのコンファレンス CHAR に参加してきました。CHAR とは、Clustering, High Availability, Replication の略語で、 PostgreSQL データベースを複数組み合わせて高可用を実現したり、 レプリケーションなどを介して性能のスケールアウトを狙ったりする技術を発表、共有するための国際会議です。

2011-08-19 09:51:44.000004 by Let's Postgres

PostgreSQL Conference in China 2011参加レポート

2011年7月14日から、中国ではじめて開催されたPostgreSQL Conferenceに参加してきました。HAアーキテクチャの分類や、SANやクラウド環境での大規模システムの構成例、SQL Serverからの移行などが講演されました。熱心に質問を受けることもしばしばの、熱い雰囲気コンファレンスでした。

2011-08-19 09:48:29.000004 by Let's Postgres

PHPでのSQLインジェクション対策 - エスケープ・クォート編: pg_escape_****

SQLインジェクション対策に有用な、PHPの文字列やバイナリデータをSQLに埋め込む適切な方法を紹介します。pg_escape_string, pg_escape_bytea関数の使い方や、PostgreSQLのバージョンごとのデフォルト設定それぞれに適切な文字列のエスケープ方法を整理します。

2011-08-01 12:18:27 by Let's Postgres

[Mac][PostgreSQL]LionにHomebrewを使ってPostgreSQL8.4.8をインストールしてみる

https://raw.github.com/mxcl/homebrew/c32bea0f198170da22bf31a3a0cb7522153da44e/Library/Formula/postgresql.rb

にあるFormulaをpostgresql8.rbとして保存して下記のように書き換える。

class Postgresql <Formula
→
class Postgresql8 <Formula

  url 'http://ftp2.uk.postgresql.org/sites/ftp.postgresql.org/source/v8.4.4/postgresql-8.4.4.tar.bz2’
→
  url 'http://ftp2.uk.postgresql.org/sites/ftp.postgresql.org/source/v8.4.8/postgresql-8.4.8.tar.bz2’

  md5 '4bf2448ad965bca3940df648c02194df'
→  
  md5 '4603e8ea30cee97189b62b39022f2043'

  depends_on 'libxml2' if MACOS_VERSION < 10.6 # Leopard libxml is too old
→
  depends_on 'libxml2' if MACOS_VERSION < 10.7 # Leopard libxml is too old

    ENV.libxml2 if MACOS_VERSION >= 10.6
→
    ENV.libxml2 if MACOS_VERSION >= 10.7

インストール、DB作成、手動起動、ログ参照、DB接続、シャットダウンしてみる。

/usr/local/bin% brew install postgresql8
/usr/local/bin% mkdir /Users/bose999/postgresql8-data
/usr/local/bin% mkdir /Users/bose999/postgresql8-log
/usr/local/bin% /usr/local/bin/initdb /Users/bose999/postgresql8-data
/usr/local/bin% /usr/local/bin/pg_ctl -D /Users/bose999/postgresql8-data -l /Users/bose999/postgresql8-log/server.log start
/usr/local/bin% cat /Users/bose999/postgresql8-log/server.log
LOG:  database system was shut down at 2011-07-27 09:01:25 JST
LOG:  database system is ready to accept connections
LOG:  autovacuum launcher started
/usr/local/bin% psql -d postgres
psql (8.4.8)
Type "help" for help.

postgres=# select * from user;
 current_user 
--------------
 bose999
(1 row)

postgres-# \q
/usr/local/bin% /usr/local/bin/pg_ctl -D /Users/matakeda/postgresql8-data stop -s -m fast

2011-07-27 00:24:00.000002 by bose999

オープンソースカンファレンス2011 Kansai 講演資料&録音ファイル

2011年7月15, 16日に開催された オープンソースカンファレンス2011 Kansai@Kyoto では、PostgreSQL 関連セッションがいくつか開かれました。 講演者のみなさまから、講義資料と講義の録音ファイルを頂けたので公開させていただきます。

2011-07-17 23:24:41.000006 by Let's Postgres