Интеграция Supabase Realtime для подписки на изменения PostgreSQL
В наших проектах при построении чата или доски задач в реальном времени polling неэффективен — он создаёт нагрузку на БД и не даёт мгновенного UX. Supabase Realtime решает это через WebSocket на основе PostgreSQL Logical Replication, слушая WAL-логи. Задержка передачи изменений обычно не превышает 10 мс, а инфраструктурные затраты снижаются до 50% по сравнению с самостоятельным сервером. Если вам нужно быстрое реалтайм-решение, закажите интеграцию Supabase Realtime у нас.
Как работает логическая репликация PostgreSQL?
Логическая репликация публикует изменения на уровне строк. В отличие от потоковой репликации, она позволяет подписываться только на нужные таблицы и фильтровать данные на стороне БД. Supabase Realtime использует именно этот механизм, а не опрос таблицы. Это даёт гарантию получения каждого изменения ровно один раз с минимальной задержкой (обычно <10 мс). Подробнее о логической репликации можно прочитать в документации PostgreSQL.
Как избежать race-conditions при обновлении состояния?
Отметим: когда несколько клиентов одновременно пишут в одну таблицу, база данных гарантирует атомарность транзакций, но клиентский код может получить события в неправильном порядке. Используйте оптимистичные обновления и сверяйте версии строк. В React-хуке ниже мы обрабатываем INSERT, UPDATE и DELETE, опираясь на уникальный id строки. Если порядок событий нарушен, можно применить RSC (React Server Components) для полной синхронизации.
Установка и подписка на таблицу
npm install @supabase/supabase-js
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
);
// Подписка на все изменения в таблице messages
const channel = supabase
.channel('public:messages')
.on(
'postgres_changes',
{
event: '*', // INSERT, UPDATE, DELETE или *
schema: 'public',
table: 'messages',
filter: `room_id=eq.${roomId}` // фильтр по значению
},
(payload) => {
if (payload.eventType === 'INSERT') {
setMessages(prev => [...prev, payload.new]);
}
if (payload.eventType === 'UPDATE') {
setMessages(prev =>
prev.map(m => m.id === payload.new.id ? payload.new : m)
);
}
if (payload.eventType === 'DELETE') {
setMessages(prev => prev.filter(m => m.id !== payload.old.id));
}
}
)
.subscribe((status) => {
console.log('Subscription status:', status);
});
// Отписка
return () => { supabase.removeChannel(channel); };
Фильтр room_id=eq.${roomId} превращается в условие WHERE room_id = 'значение' на стороне PostgreSQL. Это эффективнее, чем фильтровать на клиенте, и снижает количество передаваемых данных до 80%.
React Hook для подписки
function useRealtimeTable<T>(
table: string,
filter?: { column: string; value: string }
) {
const [data, setData] = useState<T[]>([]);
const supabase = useSupabaseClient();
useEffect(() => {
// Первоначальная загрузка
let query = supabase.from(table).select('*');
if (filter) query = query.eq(filter.column, filter.value);
query.then(({ data }) => setData(data ?? []));
// Подписка на изменения
const channel = supabase.channel(`${table}:${filter?.value ?? 'all'}`)
.on('postgres_changes', {
event: '*',
schema: 'public',
table,
filter: filter ? `${filter.column}=eq.${filter.value}` : undefined
}, (payload) => {
setData(prev => {
if (payload.eventType === 'INSERT') return [...prev, payload.new as T];
if (payload.eventType === 'UPDATE')
return prev.map(item => (item as any).id === (payload.new as any).id
? payload.new as T : item);
if (payload.eventType === 'DELETE')
return prev.filter(item => (item as any).id !== (payload.old as any).id);
return prev;
});
})
.subscribe();
return () => { supabase.removeChannel(channel); };
}, [table, filter?.column, filter?.value]);
return data;
}
// Использование
const messages = useRealtimeTable<Message>('messages', {
column: 'room_id',
value: roomId
});
Хук можно расширить: добавить пагинацию, поддержку сортировки или optimistic updates. Он сокращает объём кода с 50 до 10 строк.
Broadcast — кастомные события
Broadcast не требует изменений в БД:
// Отправить событие всем подписчикам канала
await supabase.channel('cursor-positions').send({
type: 'broadcast',
event: 'cursor-moved',
payload: { x: mouseX, y: mouseY, userId: user.id }
});
// Получить
supabase.channel('cursor-positions')
.on('broadcast', { event: 'cursor-moved' }, ({ payload }) => {
updateCursorPosition(payload.userId, payload.x, payload.y);
})
.subscribe();
Broadcast удобен для курсоров, печатающих статусов, уведомлений — всего, что не связано напрямую с данными в таблице.
Row Level Security
Этот сервис уважает RLS PostgreSQL — клиент видит только те строки, к которым у него есть SELECT-доступ.
-- Пример RLS: пользователь видит только свои сообщения
ALTER TABLE messages ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users see own messages"
ON messages FOR SELECT
USING (auth.uid() = user_id);
Это критично для многопользовательских приложений: даже если злоумышленник перехватит WebSocket, он не получит чужие данные.
Почему Supabase Realtime выигрывает у polling и ручных WebSocket?
| Метод | Задержка | Нагрузка на БД | Сложность реализации | Поддержка RLS |
|---|---|---|---|---|
| Polling (setInterval) | от 1 с до ∞ | высокая (постоянные SELECT) | низкая | да |
| WebSocket вручную | <10 мс | средняя (триггеры или NOTIFY) | высокая | требуется своя логика |
| Supabase Realtime | <10 мс | низкая (логическая репликация) | очень низкая (3 строки кода) | встроена |
Supabase Realtime выигрывает по совокупности факторов: он быстрый, не нагружает базу и сразу понимает RLS.
| Параметр | SUPABASE REALTIME | WebSocket вручную |
|---|---|---|
| Средняя задержка | <10 мс | <10 мс |
| Усилия по внедрению | 1-2 дня | 5-10 дней |
| Поддержка RLS | встроена | требует реализации |
| Масштабирование до 10 тыс. подключений | из коробки | требуется настройка |
Как мы внедряем Supabase Realtime?
При заказе интеграции Supabase Realtime «под ключ»:
- Настройка логической репликации и каналов Realtime
- React-хук (или Vue/Preact) с первоначальной загрузкой и автоматическим обновлением состояния
- Конфигурация RLS-политик для каждой таблицы
- Broadcast-каналы для кастомных событий (курсоры, уведомления)
- Документация: описание архитектуры, схема подписок, примеры запросов
- Обучение команды: как отлаживать подписки, мониторить статус соединений
- Поддержка в течение 2 недель после сдачи
Этапы:
- Аналитика: определяем, какие таблицы нуждаются в реалтайме, какие события, фильтры. Выявляем race-conditions.
- Проектирование: создаём схему каналов, готовим SQL-миграции для RLS и репликации.
- Реализация: пишем хук, настраиваем серверную часть, встраиваем в существующий код.
- Тестирование: проверяем одновременные изменения от 10+ пользователей, latency, эмулируем обрывы соединения.
- Деплой: включаем в CI/CD, мониторинг ошибок (Sentry).
Сроки и стоимость
Базовая интеграция (1-2 таблицы, базовый хук, RLS) — от 2 до 4 рабочих дней. Если требуется много таблиц, сложная логика или broadcast — срок увеличивается до недели. Стоимость рассчитывается индивидуально. Свяжитесь с нами, чтобы мы посчитали под ваши задачи.
Типичные ошибки
-
Забыли включить REPLICA IDENTITY FULL — UPDATE и DELETE приходят с пустыми старыми данными. Команда:
ALTER TABLE tablename REPLICA IDENTITY FULL; - Не настроили RLS — клиенты видят все строки таблицы. Проверьте политики SELECT для каждой защищённой таблицы.
-
Слишком широкая подписка — без фильтра клиент получает все изменения таблицы, что ведёт к лишнему трафику. Всегда используйте
filter.
Наш опыт — 5+ лет разработки реалтайм-приложений, более 20 внедрений Supabase Realtime в продакшне. Мы гарантируем стабильную работу под нагрузкой до 10 тыс. одновременных подключений на один инстанс. Закажите интеграцию Supabase Realtime уже сегодня — получите работающий прототип за 2 дня.







