# PostgresForest 4.0 開発者ガイド

目次

# 1 はじめに

## 1.1 対象読者

このドキュメントは、PostgresForestデータベースにおいて動作させるアプリ ケーションプログラムの設計および開発を行う開発者を対象としています。

本ドキュメントでは、読者はPostgreSQLの管理に関する一般的な知識を持って いることを前提としています。PostgreSQLに関する情報は、 PostgreSQLのドキュメント (http://www.postgresql.jp/document/pg815doc/html/) 等を 参照してください。

# 2 アプリケーション開発における留意点

PostgresForestを用いたアプリケーションを開発する場合、基本的には PostgreSQL単体で動作するアプリケーションと同様に開発をすることができま す。

PostgresForestに特有な、アプリケーション開発者の留意すべき点は、以下の 4点に大別されます。

1. 複数ノード間におけるデータの整合性の留意点
2. テーブルのパーティション化にともなうSQL文の制約
3. エラーの発生とそのハンドリング
4. ノード間デッドロック

## 2.1 複数ノード間におけるデータの整合性

PostgresForestでは、複数のDBノードにSQL文を発行することで高可用性およ び分散処理を実現します。

SERIAL型、OID、CURRENT_TIMESTAMPやnow()関数などは、アプリケーションか ら使用されると、それぞれのDBサーバ上で自動的に値が生成されます。これら の値は、複数のDBサーバ上で使用されると、その値が必ずしも同じ値になると は限らず、結果として一貫性の取れない状況が発生する可能性があります。

PostgresForestでは、複数のサーバ上で自動生成されるこれらの値の整合性を 保つ方法を提供していませんので、これらの機能を使っている場合には、アプ リケーション側で対処を行う必要があります。

## 2.2 テーブルのパーティション化にともなうSQL文の制約

PostgresForestでは、単一のテーブルを分割して複数サーバに配置し、アプリ ケーションからは（仮想的に）単一のテーブルとしてアクセスすることが可能 ですが、パーティションの配置の仕方によってはSQL文の利用に制限が生じる ことがあります。

具体的には、パーティション化したテーブルのパーティションを分散させて配 置した場合、JOINやサブクエリなどの利用に制限が発生します。

## 2.3 エラーの発生とそのハンドリング

PostgresForestでは、アプリケーションにエラー（SQLException）が返却され るケースは、大別して

- アプリケーションSQLのエラー（サポートしていないSQL、テーブルが存在しない、など）
- ノードの障害などで処理を継続できない場合
- ノード間デッドロックが発生した場合（後述）

があります。

これらのエラーが発生した場合には、アプリケーション側からトランザクショ ンを明示的にロールバックする必要があります。

## 2.4 ノード間デッドロック

SQLベースのレプリケーションを実装したPostgresForestでは、複数のトラン ザクションから同一レコードの更新を行うと、ノード間デッドロックが発生す る場合があります。ノード間のデッドロックが発生すると、アプリケーション に制御が戻らず、そのアプリケーションは処理を継続することができなくなり ます。

そのようなトランザクションを発行する場合、SELECT FOR UPDATEやLOCKなど のSQL文に、あらかじめ「NOWAIT」を付けて実行するか、あるいは利用する PostgreSQL （サーババックエンド）にロックのタイムアウトを検出する同梱 のパッチを適用する必要があります。

# 3 テーブル設計の指針

## 3.1 テーブル種別の選び方

PostgresForestでは、以下の二種類のテーブル種別を利用することができます （実際の作成方法については、「PostgresForest管理者ガイド」を参照）。

### 3.1.1 多重化テーブル

すべてのDBノードで同一内容のテーブルを持ちます。検索処理は特定のDBノー ドへ行い（ラウンドロビン、または固定）、更新処理はすべてのDB ノードの テーブルへ送られます。

### 3.1.2 パーティション化テーブル

特定の列の値（例えば時刻やIDなど）を基準に、テーブルにあるレコードを複 数のパーティションに分割したテーブルを「パーティションテーブル」と呼び ます。この時、テーブル分割の基準とした列を「パーティションキー」と呼び ます。

パーティション化テーブルを選択する場合、テーブルを分割することによって、 使用できるSQL文などに一部制約が発生します。詳細は「パーティション化テー ブルにおける制約事項」を参照してください。

なお、すべてのパーティションを一台のノードで保持する配置したテーブルを 「パーティション１テーブル」、パーティションを分割して各ノードに配置し たテーブルを「パーティション２テーブル」と呼びます。

### 3.1.3 パーティションキーの選び方

テーブルのパーティションを行うと、レコードの更新処理などにおいて以下の ような制約が発生します。そのため、これらの制約を考慮した上でパーティショ ンキーとなる列を選択する必要があります。

1. プライマリーキー必須・値の変更不可

> パーティション化テーブルでは、プライマリーキーが必須となります。また、プラ イマリーキーの値をUPDATEすることはできないため、プライマリーキーの値を 変更したい場合には、DELETEおよびINSERTを行う必要があります。

2. パーティションキーの値の変更（UPDATE）不可

> パーティションテーブルでは、パーティションキーの値によって、レコードが 格納されるパーティションが決定されます。パーティションキーの値の変更に よって格納パーティションが変更される可能性があるため、現在のところ、 UPDATE文によるパーティションキーの変更できません。DELETEおよびINSERTを 行う必要があります。

3. プライマリーキーとパーティションキー

> プライマリーキーとパーティションキーに、ぞれぞれ別の列を使用した場合、 プライマリーキーのユニーク性を保持できなくなる可能性があります。

## 3.2 テーブル設計時の留意事項

### 3.2.1 同一レコードへの更新集中

特定のレコードへのUPDATEが集中すると、更新処理に必要なロックをすべての ノードで取得できない場合が発生し、エラーが多発する原因となります。特に、 PostgresForestでは複数ノードにおいて同時にロックを獲得しなければならな いため、同一レコードへのロックの操作が集中すると、ノード間のデッドロッ クあるいはロック獲得の失敗となります。そのため、同一レコードへのUPDATE 処理が集中する状況を可能な限り避ける必要があります。

### 3.2.2 シーケンス（シリアル型）の使用

PostgresForestにおいては、シーケンス（シリアル型）の使用は、DBノード間 でその値が異なる可能性があるため、その使用を推奨しません。シーケンスと 同等の採番の仕組みが必要な場合には、採番テーブルを作成し、別途採番の SQL文を発行する必要があります。

実装方法の詳細については、第X章「SQL」の「シーケンス（シリアル型）」の 項を参照してください。

# 4 データベースへの接続

## 4.1 接続方法

PostgresForestのドライバは、JDBCドライバとして実装されているため、デー タベースへ接続する場合には、通常のJDBCアプリケーションと同様に Connectionオブジェクトを生成する必要があります。

なお、JDBC接続する際の接続文字列（JDBC URL）は、PostgresForest専用の設 定項目を指定する必要があります。

## 4.2 接続文字列の書式

PostgresForestでは、JDBCの接続文字列としてPostgresForest特有の設定情報 を指定する必要があります。

設定項目 | 書式 | 備考
GSC情報 | //[サーバ名]:[ポート番号]/[GSC名] | １つ以上必須
ユーザDB名 | PostgreSQLのDB名書式（英小文字および数字のみ） | 必須
GSC設定名 | 文字列（英数字のみ） | 省略時は "FOREST_DEFAULT_CONFIG"

書式:

    jdbc:postgresforest:GSC=<GSC情報>[[,<GSC情報>]…][;CONFIG=<CONFIG名>]@<ユーザDB名>

設定方法サンプル:

    Class.forName("org.postgresforest.Driver");
    Connection con = DriverManager.getConnection(
           "jdbc:postgresforest:GSC=//host1:5432/gsc,//host2:5432/gsc;CONFIG=myconfig@testdb",
           "forest", "passwd");

接続文字列の「CONFIG=」を変更することで、GSCのコンフィギュレーションを 使い分けることができます。例えば、オンラインのWebトランザクションの場 合にはタイムアウトを30秒程度とし、バッチ処理の場合にはタイムアウトを5 分にする、などの切り替えが可能となります。

## 4.3 DataSourceにおける設定

TomcatなどでDataSourceを用いてデータベースへの接続を行う場合には、以下のように設定を行います。

Tomcat4.1での設定サンプル:

    <Resource name="jdbc/testdb" auth="Container"
              type="javax.sql.DataSource"/>
    <ResourceParams name="jdbc/testdb">
      <parameter><name>username</name><value>forest</value></parameter>
      <parameter><name>password</name><value>forest</value></parameter>
      <parameter><name>driverClassName</name>
        <value>org.postgresforest.Driver</value></parameter>
      <parameter><name>url</name>
        <value>jdbc:postgresforest:GSC=//host1:5432/gsc,//host2:5432/gsc@testdb</value></parameter>
    </ResourceParams>

Tomcat5.5での設定サンプル:

    <Resource name="jdbc/testdb" auth="Container" type="javax.sql.DataSource"
                 maxActive="100" maxIdle="30" maxWait="10000"
                 username="forest" password="forest" driverClassName="org.postgresforest.Driver"
                 url="jdbc:postgresforest:GSC=//host1:5432/gsc,//host2:5432/gsc@testdb" />

## 4.4 Autocommitモード

PostgresForestでは、原則としてautocommitモードをfalseとして使用する必 要があります。:

    con.setAutoCommit(false);

PostgresForestでは、DBノード間の整合性を保つために全ノードに対する COMMIT処理をユーザが指示したタイミング（commit()メソッド呼び出し時）で 同時に行います。そのため、autocommitモードがonの状態で自動的にCOMMITが 行われてしまうと、一部のDBノードでエラーが発生した場合に整合性が崩れる 原因となります。

# 5 SQLの実行

## 5.1 SQL処理の概要

PostgresForestでは、ユーザーアプリケーションからのSQL文を独自に解析し、 SQL文の分散処理・並列処理などを内部で行っています。そのため、構文解析 の仕様、およびPostgresForestのパーティションテーブルに対する処理の仕様 上、SQL文の一部において利用上の制約があります。

### 5.1.1 多重化テーブルに対する処理

処理対象が多重化テーブルの場合、仮想化モジュールは以下のような動作となります。

- 更新系（INSERT/DELETE/UPDATEコマンド）のSQL文は全DBノードに対して発行
- 検索系（SELECTコマンド）は１ノードに対して実行（負荷分散）
- SELECT ... FOR UPDATEは全DBノードに対して発行（全ノードでロックを獲得するため）

ストアドプロシージャの呼び出しでSELECT文を使用する場合には、いずれか１ ノードでしか実行されないため、更新を伴うストアドプロシージャの実行には 注意が必要です。具体的には、SELECT文に「FOR UPDATE」を付加することで、 全DBノード上で実行することが可能となります。

### 5.1.2 パーティションテーブルに対する処理

対象がパーティションテーブルの場合、以下のような動作となります。

- 更新系のSQL文の場合

> - INSERTの場合

> > - 処理対象となるパーティションを特定して書き換えを行い、当該パーティションのみに発行

> - UPDATE/DELETEの場合

> > - 書き換えを行わずに、全パーティションに対して発行（RULEで書き換えられる）
> > - パーティション２の場合、対象となるパーティションを特定して書き換えを行い、当該パーティションのみに発行

- 検索系のSQL文の場合

> - 処理対象となるパーティションを特定できる場合

> > - モジュール内においてSQL文を書き換えた後、該当パーティションのみに対して発行

> - 処理対象となるパーティションを特定できない場合

> > - 書き換えを行わずに、全パーティションに対して実行（UNION VIEWに対して実行）

> - 集約関数のうち、sum()/count()/max()/min()については複数のノードに配置された各パーティション上で並列処理を行い、結果を集約してアプリケーションに返却

「処理対象となるパーティションを特定できる場合」とは、具体的にはWHERE 句においてパーティションキーで値を指定されている場合を指します。

例えば、uidカラムをパーティションキーとしてパーティション化している場 合、以下のSQLは「パーティションを特定できるクエリ」となります。:

    SELECT * FROM t1 WHERE uid=1001;

    SELECT * FROM t1 WHERE uid IN (1001, 1002, 1003);

逆に、以下のようなクエリの場合は、パーティションを特定することができないクエリとなります。:

    SELECT * FROM t1;

    SELECT * FROM t1 WHERE uid > 1000;

なお、パーティションテーブルにおいては、各パーティションのDBノードへの 配置方法によっては、使用できるSQL文に対して制約が発生する場合があります。

## 5.2 全般的な制約事項

### 5.2.1 now()・current_timestamp・random()・SERIAL型

PostgresForestではSQLベースのレプリケーション機構を採用しているため、 サーバの時間取得関数など、DBノード上で自動生成される値は、ノードごとに 値が異なる可能性がある。

そのため、これらの機能を必要とする場合には、アプリケーション側で生成す る、あるいは採番テーブルを作成してアプリケーションに採番させる、などの 仕組みが必要となる。

### 5.2.2 同一レコードの更新によるノード間デッドロック

同一レコードを同時に複数のトランザクションが更新しようとすると、DBノー ドへのSQL到着順序によってはノード間のデッドロックが発生する可能性があ る。

ノード間で発生したデッドロックの解消は、PostgreSQLに対して「ロックタイ ムアウトパッチ」を適用することで可能となる。その際のアプリケーションの エラー処理などの方法については、第X章「エラー処理」の「ノード間デッド ロック発生時のエラー処理」項を参照のこと。

## 5.3 SELECTコマンドにおける制約と留意点

### 5.3.1 JOIN処理

JOIN処理の対象テーブルにパーティション2テーブルを含むことはできません。

### 5.3.2 SELECT FOR UPDATE

アプリケーションがSELECT FOR UPDATEを発行すると、PostgresForestのJDBC ドライバはレコードロックを取得するために、該当SQL文を全DBノードに発行 する。

### 5.3.3 SELECT INTO / INSERT SELECT

検索結果を利用してテーブルを作成するSELECT INTO/INSERT SELECT構文は使用できません。

### 5.3.4 システムカタログ（pg_* テーブル）へのSELECT

システムカタログへの検索処理はサポートしていません。

### 5.3.5 継承テーブルへのSELECT

継承関係にあるテーブル名を一括して指定するワイルドカード「tablename*」 は使用不可。

### 5.3.6 サブクエリ

パーティションテーブルを使用している場合には、サブクエリを使用することができない。 非パーティションモードで動作している場合、サブクエリを使用することができる。

## 5.4 INSERTコマンド

### 5.4.1 SELECT文による値の設定

SELECT文による値の設定は不可（サブクエリとなるため）。

※パーティションテーブル使用時

## 5.5 UPDATEコマンドにおける制約と留意点

### 5.5.1 パーティションキーの値は更新不可

パーティション化テーブルにおいて、パーティションキーに指定した属性の値 について、UPDATE 文で変更することはできない。ドライバから以下のような メッセージを含むエラー（例外）が返却される。:

    The partition attribute (COLNAME) is specified to be t\he column to change.

### 5.5.2 パーティションテーブルのプライマリーキーの値は更新不可

パーティション化テーブルにおいて、プライマリーキーの値は更新不可。

## 5.6 DELETEコマンド

### 5.6.1 SELECT文による値の設定

SELECT文の結果による行指定を用いた削除は不可（サブクエリとなるため）。

## 5.7 その他のSQLコマンド

### 5.7.1 DDLコマンド

PostgresForestでは、仮想化モジュール（JDBCドライバ）を通したDDLコマン ドの実行はサポートしていない。CREATE TABLEなどのDDLコマンドを実行する 場合には、環境構築ツールを使用してDDLコマンドを発行する必要がある。

### 5.7.2 TRUNCATEコマンド

TRUNCATEコマンドは、PostgresForest 4.0.1 以降でサポートされている。

### 5.7.3 その他のコマンド

LOCKやSETなどのコマンドについては、全ノードに対して発行される。

## 5.8 PostgresForestにおけるシーケンス機能の実装方法

シーケンス（シリアル型）については、PostgresForestにおいてはノードごとに値が変わってくる可 能性があるため、使用を推奨しない。

シリアル型を用いて採番をする必要がある場合、採番用のテーブルを作成し、以下のようにして実装 することを推奨する。

採番テーブル作成方法:

    CREATE TABLE seq ( seqname VARCHAR(16) PRIMARY KEY, seqval INTEGER );
    INSERT INTO seq VALUES ( ‘testseq’, 0 );

シーケンス更新用トランザクション:

    BEGIN;
    SELECT seqval FROM seq WHERE seqname = ‘testseq’ FOR UPDATE NOWAIT;
    UPDATE seq SET seqval = seqval + 1 WHERE seqname = ‘testseq’;
    COMMIT;

「SELECT FOR UPDATE NOWAIT」は、該当レコードのレコードロックを獲得しよ うとするが、ロックを獲得できなかった場合にエラーが返却される。 PostgresForest環境においてノード間においてロック獲得の競合が発生すると、 ノード間デッドロックが起こりうるが、NOWAITを付加することによって、ロッ ク獲得エラー（SQLException）がアプリケーションに返却される。

よって、エラーが発生した場合にアプリケーションからリトライを行うことで、 シーケンスと同等の採番の仕組みを実現することができる。PostgresForestに おけるNOWAITを用いたシーケンスの実装は、以下のようになる。詳細は、 PostgresForest配布パッケージの examples/Sequence/ 以下のサンプルコード を参照のこと。

なお、SQLExceptionのgetMessage()で取得できるエラーメッセージは以下のようになる。:

    ERROR: could not obtain lock on row in relation "seq"
      Location: heapam.c:2157:heap_lock_tuple
     ServerSQLState: 55P03

また、getSQLState()によって取得できるコードは「55P03」となる。この例外をキャッチした場合 には、トランザクションをロールバックし、再度リトライすることができる。

# 6 エラー処理

## 6.1 エラー処理概要

各DBノードで発生したエラー（例外）については、

1. アプリケーションにエラーとして返却するもの
2. PostgresForest内部で処理してアプリケーションには正常を返却するもの

とに大別される。

エラー種別の判別は、エラーメッセージの文字列およびエラーコードをもって行われる（PostgreSQL において発生し得るエラーコードの一覧は、PostgreSQLオフィシャルマニュアル「付録A. PostgreSQLエラーコード」を参照）。

エラーが発生した場合に、エラーが発生したノードを障害発生とみなして切り離した上で正常な結果 を返してきたノードだけを使って処理を続行させるか、あるいは他のノードも含めてロールバックを 行うかは設定可能であり、デフォルトの設定がdisterror.propertiesファイルに記述されている。

## 6.2 切り離し対象エラーとロールバック対象エラー

以下のエラーが発生した場合には、PostgresForestでは該当エラーを障害と判断し、発生ノードを切 り離した上で、エラーが発生していないノードだけで処理を継続する。アプリケーションに対しては、 「正常」が返却される（例外は発生しない）。

エラーメッセージ
socket\sclosed
Connection\sreset
The\sbackend\shas\sbroken\sthe\sconnection.
Relation\s.*\sdoes\snot\sexist
Software\scaused\sconnection\sabort

エラーステータス
08.*

以下のエラーが発生した場合には、切り離しをせずにすべてのノードをロール バックして、アプリケーションにエラーを返却する。

エラーメッセージ
the\scurrent\stransaction\sis\sgoing\sto\sbe\srolled-back\sbecause\sof\slock\stimeout.

## 6.3 ノード間デッドロック発生時のエラー処理

複数のDBノード上でロック獲得が競合し、ノードをまたいでデッドロック状態 になる場合、ロックのタイムアウト検出の設定を行うことで、デッドロック状 態を解消することができる（要ロックタイムアウトパッチ）。

ノードをまたいだデッドロックが検出され、トランザクションがロールバック されると、アプリケーションに対しては以下のエラーが返却される。

エラーメッセージ | エラーステータス
the current transaction is rolled-back because of lock timeout. | 40Q01

アプリケーションでは、SQL例外をキャッチして上記例外かどうかを調べるこ とによって、ノード間デッドロックの発生およびその解消を確認することがで きる。エラー種別の判別は、SQLException のgetMessage()あるいは getSQLState()メソッドを使って行う。

上記例外が発生すると、該当のトランザクションはエラー発生状態となり、ア プリケーションからはロールバックしかできない状態となる。

## 6.4 エラーコード一覧

PostgreSQLオフィシャルマニュアル「付録A. PostgreSQLエラーコード 」を参照。

# 7 メタ情報へのアクセス

## 7.1 サーバステータスの取得方法

PostgresForestのドライバを用いて、サーバステータスなどを取得するには、 以下のクラスおよびメソッドを用いる。:

    ResultSet rs = conn.getGscMetaData().getServer();
    while ( rs.next() )
    {
        int serverId = rs.getInt(1);
        int status = rs.getInt(3);
    }

## 7.2 その他メタデータへのアクセス

PostgresForsetのメタデータ（GSC情報）へのアクセスには、以下のクラスおよびメソッドを用いる。

クラス名 | メソッド名 | 引数 | 戻り型
Connection | getGscMetaData |  | GscMetaData
GscMetaData | getBrokenLog |  | ResultSet
GscMetaData | getBrokenLog | int serverId | ResultSet
GscMetaData | getConfig |  | ResultSet
GscMetaData | getConfig | String configId | ResultSet
GscMetaData | getGSC |  | ResultSet
GscMetaData | getHash |  | ResultSet
GscMetaData | getServDB |  | ResultSet
GscMetaData | getServer |  | ResultSet
GscMetaData | getTablePart |  | ResultSet
GscMetaData | getTablePartDtl |  | ResultSet