◆ 送った数より多く表示されることがある
◆ 時刻はまとめられてる

NOTIFY/LISTEN が便利

PostgreSQL の NOTIFY/LISTEN 機能を使えばプロセス間で通信したいときに便利かなーと思っていろいろ試してます

例えば Node.js のあるプログラムが別の Node.js プログラムを起動して そのプログラムと通信したいとき ipc 通信を使うのが簡単です
しかし 起動後させたあとに親側のプログラムはすぐに終了して また後から起動した別のプロセスですでに起動してるプログラムと通信したいときに ipc 通信はできないはずです
これまでは 外部からの通知を受け取る側の起動させられる側のプログラム中で Web サーバを起動させて http サーバ経由で通信していました
Node.js だと http サーバはすごく簡単に作れますし 直接通知を送りたい相手にリクエスト送信でそのレスポンスを受取る仕組みなので結構いい感じでした

でも サーバを起動するとポートを決めないといけないです
Web サーバを使用して通信とかは内部の都合なので 使用する側はポート番号とか考えず 内部的に自動で行わせたいです
適当に 8000 とかにしても 場合によってはそれが別のプロセスで使用中かもしれません
0 を使って自動割当が良さそうですが そうなると後から通信したいときにポート番号を知るのが難しいです
ファイルに書くとサーバが終了したときに確実にファイルを消さないと整合性に問題が出たりします
また 常駐プロセスが複数になるとそれだけポートが増えます
さらに 複数ある起動中のプログラム全部に通知をしたいこともありえます
全部に http リクエストを送信するのは気が進みません

そういうときに PostgreSQL の NOTIFY/LISTEN 機能を使うといい感じに通知できます
チャンネルを文字列で指定して受け取るものを決めるので プロセスを指定して送る用のチャンネルと 全部が受け取る用のチャンネルを LISTEN しておいてメッセージが来たら処理すればよいです

DB が必要ないツールだとわざわざ DB 用意したりクライアントのライブラリ入れたりがあるので Web サーバの方法で十分なこともありますが DB はずっと起動してるとか JSON 保存しておきたいデータがあるという場合だとありだと思います

デメリットは通知でリクエストを送ってそれに対する結果のレスポンスをみたいという場合はちょっと複雑になるところです
Web サーバみたいにレスポンスを受け取るものじゃないので自分で工夫が必要です
例えば 通知を送る側が被らないような文字列を作ってチャンネル名にします
それを LISTEN してから通知を送信します
通知の payload にはそのチャンネル名も含めておきます
受け取った側はそのチャンネルに通知を送ればリクエストに対するレスポンスを受け取れます
この辺のやり方は色々ありますが なににしてもちょっと面倒なんですよね

通知という以上 レスポンスを期待せず Node.js でいう EventEmitter みたいなものであるべきなのかもしれませんけど

pgAdmin4 での通知受け取りの問題

長くなりましたがここからが本題です

通知機能を使ってると デバッグ用に通知を受信させて表示させておきたいです
psql でも

LISTEN channel;

を実行しておけば通知を受信して表示できます
ただ 受け取ってすぐ表示はしてくれず なにかの文を実行したあとにまとめて表示されます
実行したい内容がないなら「;」を入力するだけでも大丈夫です
ただ 流れるように表示したいのなら向いていません

pgAdmin4 は昔はちゃんと動かずダメな子だったのですが最近はいい感じに動いてくれてるので これが使えないかなと思って試してみました
SQL を実行するところで LISTEN を実行するとちゃんと通知を受信できます
結果は他の pgAdmin4 自体の通知と同じように右下に出てきてしばらくしたら消えますが ちゃんとログも確認できます
実行したタブの下の方の結果が見えるところで「通知」を選ぶと受信した通知の一覧が見れます
受信時刻も表示される便利さです

ただ 使ってるとなんかおかしいと思うことがありました

  • 時刻がまとまる
  • 通知が多い

時刻がまとまる

通知はリアルタイムでサーバから送られるので全部が個別の時間になりそうです
1 つの通知がいくつも表示されてるのかなと思いましたが それだと通知の数が足りなくなるので 受け取るごとに処理じゃなくて 1 秒に 1 回のような感じで適度にまとめてそうです
一応マイクロ秒まで出るのでこの仕組みはちょっと残念です

通知が多い

そしてもうひとつ こっちが致命的なのですが通知が多いです
短い頻度で連続で送ると送った数よりも表示されてる数のほうが多いです

NOTIFY channel, 'message';

と psql で実行してから 「⇧」「エンター」 の再実行を早めに数回行えば結構な頻度で再現します


実際にやってみたログです

まずは送信側です
psql で ch チャンネルに通知を送ってます
'a' を 5 回送ってその後で '12345' を 4 回送ってます

データベース "test1" にユーザ "postgres" として接続しました。
test1=# notify ch, 'a';
NOTIFY
test1=# notify ch, 'a';
NOTIFY
test1=# notify ch, 'a';
NOTIFY
test1=# notify ch, 'a';
NOTIFY
test1=# notify ch, 'a';
NOTIFY
test1=# notify ch, '12345';
NOTIFY
test1=# notify ch, '12345';
NOTIFY
test1=# notify ch, '12345';
NOTIFY
test1=# notify ch, '12345';
NOTIFY

別のプロセスでもうひとつ psql を起動して送信の前に LISTEN しておいた結果です


データベース "test1" にユーザ "postgres" として接続しました。
test1=# listen ch;
LISTEN
test1=#
test1=# ;
PID 22671 のサーバプロセスから、ペイロード "a" を持つ非同期通知 "ch" を受信しました。
PID 22671 のサーバプロセスから、ペイロード "a" を持つ非同期通知 "ch" を受信しました。
PID 22671 のサーバプロセスから、ペイロード "a" を持つ非同期通知 "ch" を受信しました。
PID 22671 のサーバプロセスから、ペイロード "a" を持つ非同期通知 "ch" を受信しました。
PID 22671 のサーバプロセスから、ペイロード "a" を持つ非同期通知 "ch" を受信しました。
test1=# ;
PID 22671 のサーバプロセスから、ペイロード "12345" を持つ非同期通知 "ch" を受信しました。
PID 22671 のサーバプロセスから、ペイロード "12345" を持つ非同期通知 "ch" を受信しました。
PID 22671 のサーバプロセスから、ペイロード "12345" を持つ非同期通知 "ch" を受信しました。
PID 22671 のサーバプロセスから、ペイロード "12345" を持つ非同期通知 "ch" を受信しました。
test1=#

こっちは 5 回と 4 回で正常に受け取れています

同じく pgAdmin4 でも LISTEN していたのですが結果がこれです

pga4-listen

'12345' も 5 回あるんですよね……

さすがにこういう信用ならないものじゃ使えないので psql で我慢するか LISTEN して標準出力に流すツールを作って置くのが良いと思います
たぶんこれだけで動くと思います

const { Client } = require("pg")
const client = new Client(config)
client.connect()

client.on("notification", msg => {
console.log({time: new Date(), channel: msg.channel, body: msg.payload})
})

client.query("LISTEN channel")

payload が JSON 前提なら JSON.parse も通しておくとログの出力が見やすくなります