12
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

libpqで複数の接続先を指定

Last updated at Posted at 2017-12-23

はじめに

にゃーん
この記事は、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の他の機能と組合せてみる。

LOGREP+libpq.png

  1. 入力APからテーブルにデータを投入する。
  2. そのテーブルをPUBLISHERとして、2つの別サーバ上のテーブルをSUBSCRIBERとした構成で、ロジカルレプリケーションを行う。
  3. 分析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;
}
12
8
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?