はじめに
にゃーん
この記事は、PostgreSQL 10全部ぬこ Advent Calendar 2017 の24日目のエントリです。
まったく、クリスマス・イブの日だというのに、一体何をやっているんでしょうね俺は。
今日のお題は、libpqの改善項目の話。
libpqとは
libpqというのは、C言語で書かれたクライアントライブラリ。「今どきC言語なんかでアプリケーション組まねーよ」という人も多いだろうが、PostgreSQLへ接続する言語ライブラリの多くは、実はこのlibpqに依存している
- C++, Perl, Python, Tcl, PHP. Ruby, ...
例外はJDBCドライバくらいかな?
なので、libpqの変更というのは、他の言語でPostgreSQLアプリケーションを組んでいる人にも無関係ではない。もしかすると、各言語ライブラリのアップデートで、libpqの追加機能に対応するかもしれないし。
PostgreSQL 10で追加された機能
PostgreSQL 10でもlibpqへの改善項目が入っている。
- 複数の接続先指定が可能になった。
- libpq接続パラメータtarget_session_attrsが追加された。
- 接続文字列にパスワードファイル名を指定可能になった。
- PQencryptPasswordConn()でmd5だけでなSCRAM認証用のパスワードが生成可能になった。
今日は、追加された機能のうち、複数の接続先指定について検証してみる。
複数の接続先指定
PostgreSQL文書での複数の接続先の指定は、以下のSectionに書かれている。
Specifying Multiple Hosts
これをざっと読んだ感じでは、
'host=host1,host2 port=5432,5432 '
のようにカンマで区切って値を記述するという形式のようだ。
ただ、この複数記述の意味があるのは、host(hostaddr)とportのみ。
他のパラメータ、例えばdbname=test,test
と書いても、2つのtestという値にはならず、test,test
という値でdbnameが解釈される。
検証モデル
せっかくなので、PostgreSQL 10の他の機能と組合せてみる。
- 入力APからテーブルにデータを投入する。
- そのテーブルをPUBLISHERとして、2つの別サーバ上のテーブルをSUBSCRIBERとした構成で、ロジカルレプリケーションを行う。
- 分析APからレプリケーションされたテーブルへアクセスして検索を行う。このときに、1つのサーバがダウンしていたら、自動的にもう1つのサーバに接続するようにする。
今回は、この3.の部分をlibpqの新機能で試してみることにした。
といっても手元のマシンで実施するので、全て同じサーバ上で
- 入力サーバはport=10000
- レプリカ1はport=10001
- レプリカ2はport=10002
といい構成で試してみることにした。
libpqを使ったテストプログラムは、第1引数に接続文字列を、第2引数に任意のSELECT文を記述する。
実行すると、最初に接続したPostgreSQLサーバのポート番号を出力し、その後に指定したSELECT文を実行する。
(なお、元ネタはPostgreSQL 10.1ソースのsrc/test/examples/testlibpq.c
である)
実行結果
まず、入力サーバ(port=10000)、レプリカ1(port=10001)、レプリカ2(port=10002)が全て起動している状態にする。
[nuko@localhost ~]$ ps -ef | grep postgres | grep srv
nuko 2263 1 0 16:49 pts/8 00:00:00 /home/nuko/pgsql/pgsql-10.1/bin/postgres -D /tmp/srv10000
nuko 2521 1 0 16:50 pts/8 00:00:00 /home/nuko/pgsql/pgsql-10.1/bin/postgres -D /tmp/srv10002
nuko 56526 1 0 19:10 pts/9 00:00:00 /home/nuko/pgsql/pgsql-10.1/bin/postgres -D /tmp/srv10001
[nuko@localhost ~]$
この状態で、以下のようにテストプログラムを実行してみる。
[nuko@localhost libpq]$ ./testlibpq \
> 'host=localhost,localhost port=10001,10002 dbname=test user=postgres' \
> 'SELECT SUM(data) FROM table_x'
port=10001
sum
50105660
接続したサーバのport=10001を表示し、その後にSELECT文を実行している。
次にsrv10001をシャットダウンする。シャットダウン後のpsコマンドの結果はこうなったいる。
[nuko@localhost ~]$ ps -ef | grep postgres | grep srv
nuko 2263 1 0 16:49 pts/8 00:00:00 /home/nuko/pgsql/pgsql-10.1/bin/postgres -D /tmp/srv10000
nuko 2521 1 0 16:50 pts/8 00:00:00 /home/nuko/pgsql/pgsql-10.1/bin/postgres -D /tmp/srv10002
[nuko@localhost ~]$
この状態で、さっきと同じようにテストプログラムを実行する。
[nuko@localhost libpq]$ ./testlibpq \
> 'host=localhost,localhost port=10001,10002 dbname=test user=postgres' \
> 'SELECT SUM(data) FROM table_x'
port=10002
sum
50105660
port=10001への接続はせずに、次に書かれたport=10002のサーバへ接続を行い、その後にSELECT文を実行している(両方とも入力サーバからロジカルレプリケーションしているので、SUMの結果は同じになる)。
おわりに
今回はlibpqの複数接続先指定を試してみた。
ロジカルレプリケーションを用いて、同じパブリッシャから複数のサーバ上のサブスクライバにレプリケーションするような構成に対して、libpqライブラリの機能を使うだけで、簡易なHAっぽいことができることが確認できた。
参考:該当するリリースノート
本エントリに関連するPostgreSQL 10リリースノートの記載です。
E.2.3.1.6. Monitoring
- Properly update the statistics collector during REFRESH MATERIALIZED VIEW (Jim Mlodgenski)
参考:検証で使ったCソースコード
/*
* src/test/examples/testlibpq.c
*
*
* testlibpq.c
*
* Test the C version of libpq, the PostgreSQL frontend library.
*/
#include <stdio.h>
#include <stdlib.h>
#include "libpq-fe.h"
static void
exit_nicely(PGconn *conn)
{
PQfinish(conn);
exit(1);
}
int
main(int argc, char **argv)
{
const char *conninfo;
PGconn *conn;
PGresult *res;
int nFields;
int i,
j;
const char *sql;
/*
* If the user supplies a parameter on the command line, use it as the
* conninfo string; otherwise default to setting dbname=postgres and using
* environment variables or defaults for all other connection parameters.
*/
if (argc > 2) {
conninfo = argv[1];
sql = argv[2];
} else if (argc > 1) {
conninfo = argv[1];
sql= "SELECT 1";
} else {
conninfo = "dbname = postgres";
sql= "SELECT 1";
}
/* Make a connection to the database */
conn = PQconnectdb(conninfo);
/* Check to see that the backend connection was successfully made */
if (PQstatus(conn) != CONNECTION_OK)
{
fprintf(stderr, "Connection to database failed: conninfo=%s, message=%s",
conninfo,
PQerrorMessage(conn));
exit_nicely(conn);
}
/*
* print inet_server_port
*
*/
res = PQexec(conn, "SELECT inet_server_port()");
if (PQresultStatus(res) != PGRES_TUPLES_OK)
{
fprintf(stderr, "PQexec failed: sql=%s,message=%s\n", sql, PQerrorMessage(conn));
PQclear(res);
exit_nicely(conn);
}
printf("port=%s\n", PQgetvalue(res, 0, 0));
PQclear(res);
/*
* Our test case here involves using a cursor, for which we must be inside
* a transaction block. We could do the whole thing with a single
* PQexec() of "select * from pg_database", but that's too trivial to make
* a good example.
*/
res = PQexec(conn, sql);
if (PQresultStatus(res) != PGRES_TUPLES_OK)
{
fprintf(stderr, "PQexec failed: sql=%s,message=%s\n", sql, PQerrorMessage(conn));
PQclear(res);
exit_nicely(conn);
}
/* first, print out the attribute names */
nFields = PQnfields(res);
for (i = 0; i < nFields; i++)
printf("%-15s", PQfname(res, i));
printf("\n\n");
/* next, print out the rows */
for (i = 0; i < PQntuples(res); i++)
{
for (j = 0; j < nFields; j++)
printf("%-15s", PQgetvalue(res, i, j));
printf("\n");
}
PQclear(res);
/* close the connection to the database and cleanup */
PQfinish(conn);
return 0;
}