Postgres 的跨 client 通知機制:LISTEN / NOTIFY

會想要使用 LISTENNOTIFY 的情況是想要偵測某個 table 的欄位變動時,想要做一些與 Query 本身無關的事情,例如:

  • 寄送通知信
  • 重算報表
  • 更新快取

雖然這些操作我們都可以在 CPP ( 或其他 DB Client ) 執行完 query 後手動處理,但是如果今天我們 DB 的結構比較複雜,有比較多的 trigger 或是 foreign key 的時候,就會比較難在 CPP 來處理所有需要通知的時機點,或是未來在調整程式時很容易漏掉

相反的如果我們在 postgres trigger 來做這些操作,不但會讓 query 執行變久,也要做額外的錯誤處理

這時候 LISTEN / NOTIFY 會是一個不錯的選項

語法

NOTIFY

NOTIFY channel [ , payload ]

channelpayload 都是字串,只有 channel 是一定要帶的參數

假設我們今天每次登入都會寫紀錄到 login_log 這張 table,當發生 login 失敗時我們要額外寄送通知信,那麼我們就可以直接在這張 table 綁上一個 trigger,判斷是登入失敗時,就直接呼叫 NOTIFY login_failed 'user@example.com'

如此一來我們就送出了一個登入失敗的通知,並且帶有失敗的 email (也就是 payload)

還有另一種方法是執行 SQL function pg_notify(text, text),一樣也可以送出 notify

NOTIFY 有以下的特性:

  • 只有在 transaction 被 commit 後才會送出
  • 一個 transaction 內重複的 channel, payload 組合只會送出一個 event
  • 一個 transaction 內的 event 會依 NOTFIY 執行的順序送出

LISTEN

LISTEN channel

我們只要將想要監聽的事件名稱放在 LISTEN 後面即可

舉例來說,我們想要寄送登入失敗的通知信的話,可以在負責寄信的 process 執行 LISTEN login_failed,如此一來就可以收到事件

LISTEN 有以下特性:

  • 不會收到執行 LISTEN 之前送出的 event
  • 如果在執行 transaction 時有人送出通知,要到 transaction 結束 (commit / abort) 後才收得到

如果你是用兩個 psql 來測試不同 connection 的行為的話,執行 NOTIFY 之後在 LISTEN 的 session 記得要輸入 ; 後按 ENTER 才會把收到的 event 印出來

在實作方面,如果是用 postgres 提供的 libpq,並沒有很好的通知機制,我們要在執行 Query 之後用 PQnotifies() 來檢查是否有收到 event

假設沒有 query 需要執行的話,也可以執行 PQconsumeInput() 後再執行 PQnotifies()

PGnotify *PQnotifies(PGconn *conn);

typedef struct pgNotify
{
    char *relname;              /* notification channel name */
    int  be_pid;                /* process ID of notifying server process */
    char *extra;                /* notification payload string */
} PGnotify;

回傳 nullptr 代表沒有 event 了,記得用完 PGnotify 的資訊後,要用 PQfreemem 來釋放記憶體空間,細節可以再看 官方文件

UNLISTEN

UNLISTEN { channel | * }

當我們不在需要監聽的時候,除了直接斷開 DB 連線之外,也可以手動取消 LISTEN。

如果要取消全部就執行 UNLISTEN *,反之就要寫上 channel 名稱

參考資料

覺得有用的話可以給我一個拍手

留言

這個網誌中的熱門文章

unit testing 的第一步:使用 gcov/lcov 統計 c++ project 的 testing coverage

在 python 中透過 ctypes 執行 C++ library 的 class

在 blogger 寫 markdown 最接近完美的辦法