読者です 読者をやめる 読者になる 読者になる

もなかアイスの試食品

「とりあえずやってみたい」そんな気持ちが先走りすぎて挫折が多い私のメモ書きみたいなものです.

PythonでPostgreSQLの通知を受け取る[NOTIFY/LISTEN]

PostgreSQLには、接続しているクライアントに通知をおくることが出来るらしい。

今まであるテーブルの全レコードのフラグを監視して、見つけたら取り出し→処理→フラグ更新ってことばかりをやっていたけど

これを「通知」に置き換えることができそう

これは便利

というわけで普段あまり使わないPythonの練習がてら、通知処理を使ってみた

やってみた環境

  • サーバ側
    1. CentOS6.8(仮想OS。IP:192.168.192.130)
    2. PostgreSQL9.5.4
      • DB名:testdb


  • クライアント側
    1. Windows10(仮想のホスト。IP:192.168.192.1)
    2. Python2.7.12

サーバの設定

/var/lib/pgsql/9.5/data/postgresql.confで受け付けるIPアドレスの変更

#listen_addresses = 'localhost'          # what IP address(es) to listen on;
↑確かこうなっているのを
↓変更
listen_addresses = '*'          # what IP address(es) to listen on;

/var/lib/pgsql/9.5/data/pg_hba.confに、ホストからのアクセスを許可

host    all             all             127.0.0.1/32            trust
host    all             all             192.168.192.1/24        trust
↑追記(パスワードなんて認証なんて無い。イイネ?)

通知を受け取る

PostgreSQLから通知を受け取るアプリをPythonで作る。

以下のサイトを参考に作成した More advanced topics — Psycopg 2.6.2 documentation

※例外処理は適当

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import psycopg2
import time
import select
import traceback


def create_connection():
    try:
        dsn = "host=192.168.192.130 port=5432 dbname=testdb user=postgres password=postgres"
        connection = psycopg2.connect(dsn)
        connection.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
        return connection
    except:
        traceback.print_exc()
        return None


def start_listen(connection):
    try:
        cursor = connection.cursor()
        cursor.execute('LISTEN test;')
        cursor.close()
        return True
    except:
        print "unable to notify listen"
        return False


if __name__ == '__main__':

    connection = None

    while True:
        try:
            if (connection is None) or (connection is not None and connection.closed):
                print "create connection. wait 5 sec"
                time.sleep(5)
                connection = create_connection()
                start_listen(connection)
            elif connection is not None and not connection.closed:
                print "wait notify 5 sec."
                if select.select([connection], [], [], 5) == ([], [], []):
                    print 'listen timeout'
                else:
                    connection.poll()
                    while connection.notifies:
                        notify = connection.notifies.pop(0)
                    print "Got NOTIFY:", notify.pid, notify.channel, notify.payload
        except:
            traceback.print_exc()
            if connection is not None and not connection.closed:
                connection.close()
            connection = None

ポイントは、トランザクションの分離レベルを「ISOLATION_LEVEL_AUTOCOMMIT」に設定しているところ

これをしてないと通知が受け取れなかった

psycopgのドキュメントに書いてあった内容

Because of the way sessions interact with notifications (see NOTIFY documentation), you should keep the connection in autocommit mode if you wish to receive or send notifications in a timely manner.

イムリーに通知の送受信をするためには「オートコミットモード」にしろ。(詳しくはPostgreSQLのNOTIFY読んでね)

これだけだと「オートコミット」じゃないといけないのかよくわからん

PostgreSQLのドキュメントも見ると、以下ことが書いてあった。

NOTIFYとSQLトランザクションの間には、いくつかの重要な相互作用があります。 まず、NOTIFYがトランザクション内部で実行された場合、通知イベントはトランザクションがコミットされない限り配送されません。 トランザクションがアボートされた場合、NOTIFYだけでなく、そのトランザクション内で行われたコマンドが全て無効化されるので、これは妥当といえます。 しかし、通知イベントが即座に配送されることを期待していた場合、当惑するかもしれません。 次に、監視中のセッションがトランザクション処理中に通知信号を受け取った場合、そのトランザクションが(コミットもしくはアボートされて)完了するまで、通知イベントは接続しているクライアントに配送されません。 この理由も同じです。トランザクションに通知が配送された後にそのトランザクションがアボートされた場合、何とかして通知を取り消したくなりますが、サーバはいったんクライアントに送信した通知を「取り戻す」ことはできません。 したがって、通知イベントはトランザクショントランザクションの合間にのみ配送されます。 結論として、NOTIFYを使用してシグナルの実時間処理をするアプリケーションではトランザクションを短くしておかなければなりません。

「通知イベントはトランザクショントランザクションの合間にのみ配送されます。」

これに対応するために「ISOLATION_LEVEL_AUTOCOMMIT」にしないと動作しないのだろうか?

通知を送信する

通知を送るのは、「psql」で可能

# psql -U postgres testdb
psql (9.5.4)
"help" でヘルプを表示します.

testdb=# notify test, 'this is payload∠( ゚д゚)/';

通知の受信結果

こんな感じ

wait notify 5 sec.
listen timeout
wait notify 5 sec.
listen timeout
wait notify 5 sec.
Got NOTIFY: 4953 test this is payload∠( ゚д゚)/
wait notify 5 sec.
Got NOTIFY: 4953 test this is payload∠( ゚д゚)/

参考サイト PostgreSQL: Documentation: 9.5: NOTIFY More advanced topics — Psycopg 2.6.2 documentation