New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
pgxpool support for LISTEN #1121
Comments
In my own applications I handle listen by acquiring a dedicated connection from the pool and never returning it. Here is code similar to what I use for background email sending. // ListenAndSend listens for PostgreSQL notifications of new outbound emails and sends them. Errors are logged but do
// not terminate the method. The only way to terminate this method is via context cancellation.
func (es *EmailSender) ListenAndSend(ctx context.Context) {
for {
es.SendQueuedEmails(ctx) // send any emails queued prior to listening for notificiations
es.listenAndSendOneConn(ctx)
select {
case <-ctx.Done():
return
default:
// If listenAndSendOneConn returned and ctx has not been cancelled that means there was a fatal database error.
// Wait a while to avoid busy-looping while the database is unreachable.
time.Sleep(time.Minute)
}
}
}
func (es *EmailSender) listenAndSendOneConn(ctx context.Context) {
conn, err := es.db.Acquire(ctx)
if err != nil {
if !pgconn.Timeout(err) {
current.Logger(ctx).Err(err).Msg("failed to acquire database connection to listen for queued emails")
}
return
}
defer conn.Release()
_, err = conn.Exec(ctx, "listen outbound_email_queued")
if err != nil {
if !pgconn.Timeout(err) {
current.Logger(ctx).Err(err).Msg("failed to listen to outbound_email_queued")
}
return
}
for {
notification, err := conn.Conn().WaitForNotification(ctx)
if err != nil {
if !pgconn.Timeout(err) {
current.Logger(ctx).Err(err).Msg("failed while waiting for notification of outbound_email_queued")
}
return
}
// Parse notification and send email.
}
} IMO this style is simpler / more direct than dealing with an abstraction built on pool. |
(Your code snippet isn't unlistening before returning the connection to the pool, causing a memory leak -- not sure if an error can occur that doesn't cause the connection to be closed though) Definitely simpler, but I was hoping not to use up an extra connection for only listening. |
I don't think any non-fatal errors are possible, but I guess explicitly closing the connection wouldn't hurt.
I don't think the additional complexity is worth saving a single connection. As you mentioned in your original post there are a number of sticky edge cases -- some of which I don't think are actually solvable. |
Is it expected that the WaitForNotification receives an I would prefer a connection that stays open for the duration of the application, modulo any fatal errors of cause. |
pgxpool only will close connections that are in the pool. It will never close a connection that is acquired. And there is no connection lifetime for a standalone connection. My guess is that something in the network is closing idle or long lived connections. |
@jackc in that code is it safe if you get sent a bunch of messages at the same time, all will go through? |
Yes, it will buffer the messages until they are handled. |
@jackc Any chance you know how to flush notifications that were missed? Say my service crashes, I missed 10 notifications. If I restart it starts waiting for new notifications, but it looks like it leaves the missed ones in the channel. If I use a tool like |
I don't know how this would work. The PostgreSQL server sends the notice to connected clients. If the service has crashed it isn't connected. What I do is write jobs / messages to a table and send the notice. When a service starts it checks the table and handles anything previously recorded while it was disconnected. |
@jackc maybe I'm wrong and need to check again. But after sending |
@nkreiger If you can do it I'd like to know how, but AFAIK it doesn't work that way. |
@jackc do I need to specify some additional options to I'm trying to listen to notifications from postgres (basically N instances of a service write to a table and postgres noitfies instances about any changes), however after some time (sometimes 15 minutes, sometimes 90 minutes...) I get two errors from WaitForNotification():
(note that I have an endless loop there for testing right now, otherwise I'd returned after getting I've checked everthing I possbile can on postgress side and it seems like the client is dropping the connection:
PS: notify may be idle (as in no new messages) for any amount of time in theory |
@konart I don't think so. Perhaps there is something on the network closing the connection? |
Hello, I am having difficulties to understand the correct way to use your package for my case. ContextI need to listen to notification on a few channels, so I will need a Questions
Code
import (
"github.com/jackc/pgx/v5/pgconn"
"github.com/jackc/pgx/v5/pgxpool"
)
type NotificationPayload struct {
ID string `json:"id"`
Action string `json:"action"`
}
func main() {
config := &Config{
pgUrl: "postgres://root:secret@localhost:5432/postgres",
notifications: []string{"orders_notification", "contacts_notification"},
}
ctx := context.Background()
// Pool for queries
pool, _ := pgxpool.New(ctx, config.pgUrl)
defer pool.Close()
// mainConn for notifications
mainConn, _ := pgx.Connect(ctx, config.pgUrl)
defer mainConn.Close(ctx)
for _, notificationName := range config.notifications {
// 👇 listen to notifications (pgx)
mainConn.Exec(ctx, fmt.Sprintf("listen %s", notificationName))
}
for {
log.Debug("\nwaiting for notification")
// 👇 synch (pgx)
n, _ := mainConn.WaitForNotification(ctx)
go func(n *pgconn.Notification) {
// 👇 manage pool (pgxpool)
poolConn, _ := pool.Acquire(ctx)
handleNotification(ctx, poolConn, n)
poolConn.Release()
}(n)
}
}
func handleNotification(ctx context.Context, conn *pgxpool.Conn, pgNotif *pgconn.Notification) {
payload := &NotificationPayload{}
json.Unmarshal([]byte(pgNotif.Payload), payload)
// ✅ this works if I specify the column
var result string
_ := conn.QueryRow(ctx, "SELECT col_with_string_value FROM orders WHERE id=$1", payload.ID).Scan(&result)
log.Infof("row: %s", row) // row: <string_value>
// 🎯 this is what I would like to do
var result []byte
_ := conn.QueryRow(ctx, "SELECT * FROM orders WHERE id=$1", payload.ID).Scan(&result)
log.Infof("row: %+v", result) // row: ...values from all columns
} Thanks, |
@Kmelow Your overall idea is correct. But here are a few suggestions / other possibilities.
Yes, but
I don't understand exactly what you are trying to do with
I suppose you could use You might also take a look at https://github.com/jackc/pgxlisten for some ideas. I don't publicize it much because I don't want to imply there is any support or that the API won't change, but it is what I use in production for listen / notify. |
I want to use LISTEN together with pgxpool. pgx has WaitForNotification() and pgconn has Config.OnNotification.
I'd like to use one connection of a pool for having a LISTEN open, if that connection dies another one should run the LISTEN command. We probably need a callback to the application that it's possible notifications were missed in that gap.
A caveat we should document is that one has to be careful because notifications are not delivered during transactions, so if the application uses the pool for long transactions notifications might be delayed.
Alternatively I could build this myself using AfterConnect, but we'd need a BeforeDisconnect or AfterDisconnect to start the LISTEN on another connection.
Without either option, we'd have to set up the listening on every connection in the pool and do deduplication, but I guess the pool doesn't guarantee there's always at least one connection open.
The text was updated successfully, but these errors were encountered: