PythonでPostgreSQLの通知を受け取る[NOTIFY/LISTEN]
PostgreSQLには、接続しているクライアントに通知をおくることが出来るらしい。
今まであるテーブルの全レコードのフラグを監視して、見つけたら取り出し→処理→フラグ更新ってことばかりをやっていたけど
これを「通知」に置き換えることができそう
これは便利
というわけで普段あまり使わないPythonの練習がてら、通知処理を使ってみた
やってみた環境
- サーバ側
- CentOS6.8(仮想OS。IP:192.168.192.130)
- PostgreSQL9.5.4
- DB名:testdb
- クライアント側
- Windows10(仮想のホスト。IP:192.168.192.1)
- Python2.7.12
- PostgreSQLのライブラリ:psycopg2
サーバの設定
/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