Ответ
NOTIFY и LISTEN в PostgreSQL — это асинхронный механизм межпроцессного взаимодействия, который позволяет реализовать паттерн "Издатель-подписчик" (Pub/Sub) на уровне базы данных.
Принцип работы:
- Подписчик (
LISTEN): Один или несколько клиентов (сессий) подписываются на канал, выполняя командуLISTEN channel_name;. - Издатель (
NOTIFY): Другой клиент отправляет уведомление в этот канал с помощью командыNOTIFY channel_name, 'payload';. Полезная нагрузка (payload) — это опциональная строка. - Получение уведомления: Все подписчики асинхронно получают уведомление. Это не прерывает их текущие запросы.
Основные сценарии использования в бэкенде:
- Real-time обновления: Отправка сигналов веб-серверу (например, через WebSocket) об изменении данных для немедленного обновления UI у клиентов.
- Инвалидация кэша: При изменении данных в таблице можно отправить
NOTIFY, чтобы сервисы, кэширующие эти данные, сбросили свой кэш. - Сигнализация для фоновых воркеров: Основное приложение может уведомить фоновый воркер о появлении новой задачи в очереди без необходимости постоянно опрашивать БД (
polling).
Пример использования в Go (с библиотекой pgx):
// Воркер, который слушает уведомления
go func() {
conn, _ := pgx.Connect(context.Background(), os.Getenv("DATABASE_URL"))
defer conn.Close(context.Background())
_, err := conn.Exec(context.Background(), "listen updates")
if err != nil { /* ... */ }
for {
notification, err := conn.WaitForNotification(context.Background())
if err != nil { /* ... */ }
fmt.Printf("PID: %d, Channel: %s, Payload: %sn",
notification.PID, notification.Channel, notification.Payload)
}
}()
// ... в другой части приложения, например, в HTTP-обработчике
// conn2 - другое соединение
_, err := conn2.Exec(context.Background(), "notify updates, 'user 123 updated' ")
Особенности и ограничения:
- Транзакционность: Уведомления отправляются только после успешного коммита транзакции, в которой был выполнен
NOTIFY. - Нет гарантии доставки: Если клиент отключается, он теряет все уведомления, отправленные во время его отсутствия.
- Ограниченный размер
payload: Полезная нагрузка не может превышать 8000 байт.
Ответ 18+ 🔞
Слушай, а вот эта штука с NOTIFY и LISTEN в Постгресе — это ж, блядь, просто песня, а не механизм! Представь себе: база данных умеет сама кричать на всех, кто её слушает, когда что-то происходит. Никакого тупого опроса таблиц каждые пять секунд!
Как это, сука, работает:
- Подписчик (
LISTEN): Какая-нибудь твоя го-рутина пристраивается к каналу, типа "Эй, база, я тут, слушаю каналuser_updates, как что — сразу в ухо!". И сидит, ждёт, как маньяк у почтового ящика. - Издатель (
NOTIFY): А потом, в другом месте, после того как ты успешно обновил запись и закоммитил транзакцию (важно, ёпта!), ты такой: "Внимание, всем подписчикам каналаuser_updates! Сообщение: 'юзер 123 накосячил!'". И база рассылает этот крик. - Получение: Все, кто слушал, это мгновенно ловят. Асинхронно, без всяких
SELECT * FROM .... Красота, пиздец!
Где это, блядь, применить, чтобы начальство офигело:
- Real-time обновления на фронте: Сделал апдейт в базе →
NOTIFY→ бэкенд ловит уведомление → по вебсокету пихает данные в браузер. Клиенты видят изменения сразу, без перезагрузки. Волшебство, ёпта! - Похерить кэш: Обновил главную таблицу? Отправь
NOTIFY 'cache_bust'. Все твои микросервисы, которые кэшировали старые данные, тут же их выкидывают к хуям собачьим. Чистота! - Будить фоновых работяг: Положил задачу в очередь в таблицу
jobs? Вместо того чтобы воркер каждую секунду дергал базу с вопросом "Чё по чем?", просто шлепниNOTIFY 'new_job'. Воркер проснётся и сразу возьмёт задачу. Экономия ресурсов — овердохуища!
Вот, смотри, как на Go с pgx это выглядит, чтоб ты понимал масштаб:
// Это у нас воркер, который слушает, как маньяк
go func() {
conn, _ := pgx.Connect(context.Background(), os.Getenv("DATABASE_URL"))
defer conn.Close(context.Background())
// Подписываемся, сука!
_, err := conn.Exec(context.Background(), "listen updates")
if err != nil { /* ... обработка ошибки, не будем идиотами */ }
// И вечный цикл ожидания
for {
// Тут он просто висит, не грузит ЦП, ждёт
notification, err := conn.WaitForNotification(context.Background())
if err != nil { /* ... */ }
// Получил — обрабатывай!
fmt.Printf("От кого (PID): %d, Канал: %s, Сообщение: %sn",
notification.PID, notification.Channel, notification.Payload)
}
}()
// ... А где-то в HTTP-обработчике, после апдейта пользователя
// conn2 — это уже другое соединение, главное
_, err := conn2.Exec(context.Background(), "notify updates, 'user 123 updated' ")
Но, блядь, есть нюансы, как в рекламе!
- Транзакционность — святое: Уведомление улетит только если транзакция с
NOTIFYуспешно закоммитилась. Иначе — хуй тебе, а не сообщение. Это хорошо. - Гарантий доставки — ноль ебать: Если твой подписчик в момент отправки был отключен, он это уведомление проебёт. Навсегда. Это как письмо, которое сгорело на почте. Нужна надёжность — пиши в таблицу-очередь.
- Размер payload'а ограничен: Не больше 8000 байт. Не пытайся запихнуть туда "Войну и мир", идиот. Для больших данных — только ключ или ID, а дальше сам дёргай.
Короче, инструмент — огонь. Но требует, блядь, аккуратности и понимания, где его впендюрить. Если впилишь правильно — архитектура запоёт, как сучка на выданье.