Как правильно организовать real-time обновление в огромном проекте?
Проблема:
В приложении Next.js используется общий сокет для трансляции спортивных событий. При обрыве интернета (или переключении устройства) логика восстановления комнат срабатывает нестабильно. Часто теряются поля в объектах, или TanStack Query не обновляет кеш, потому что ключи формируются неправильно
Что сделано:
Сейчас логика размазана по 10 хукам, которые отличаются только способом обновления кеша. Написал SocketManager, который отвечает за подключение и переподключение, но у разных событий с бека своя логика формирования ключа для комнаты и переподписки, из-за чего бывает потеря соединения
Вопрос:
Стоит ли делать отдельный провайдер, который обернет все приложение, будет следить за pathname, так как по нему формируется подписка на некоторые события, также будет подписываться на события, восстанавливать подключения и следить где какой кеш нужно обновить в зависимости от типа события?
Есть ли более масштабируемый паттерн для управления одним большим сокетом на проекте?
Если нужны еще детали, могу уточнить, хочу понять как строить полноценный реалтайм сервис, а не просто игрушку из пары хуков в компонентах
Провайдер нужен, но лучше не делать из него «бога». Разбей на слои: transport (только connect/reconnect/auth), subscription registry (хранит активные комнаты, при reconnect сам делает join), и event handlers где setQueryData если пришёл полный объект, invalidateQueries если partial. pathname — только сигнал «какие комнаты сейчас нужны», не основа архитектуры. И один queryKeys factory вместо строк в каждом хуке — это сразу уберёт половину несовпадений ключей.
p.s. после reconnect обязателен resync (snapshot по HTTP или sync_required событие) — socket.io recovery при долгом обрыве не гарантирован.
да, я тоже склоняюсь к тому, чтобы провайдер был лишь точкой, которая объединяет все эти слои: логику транспорта, с ее переподключениями, формированием ключей комнат по нужным хелперам и одним useEffectom, который подписывается на события и пробрасывает в подписки нужное обновление кеша
Ноды "WS фронтенд" — только связь с клиентами, тупо проброс сообщений между сокетом и очередью, плюс пинг клиентов и обновление TTL у своих сессий
Ноды "WS бэкенд" — бизнес-логика, коннект к БД, через связь "клиент <-> нода" в редиске отправляет сообщения в нужную фронт-ноду
Сессии в редиске — глобальный список связей "клиент <-> нода", ноды онлайн, ID их очередей, а вот у каждой фронт-ноды свой список сессий подключённых к ней клиентов и всякими дополнительными данными типа сессионных токенов, плюс у сессий ограничения по TTL — по истечении времени она автоматом уничтожается
Очереди — та же редиска, у каждой ноды своя пара очередей вх/исх, формат — сырые бинарные данные, дабы не лишний раз не заниматься конвертацией туда-сюда
Фичи WS протокола: аутентификация, авторизация, сообщения, подтверждение доставки сообщений, редирект на другой сервер — фича для балансировки и безопасного отключения клиентов от ноды и для её отключения, в случае потери связи клиенты реконнектятся по дефолтному урлу
Да, возможно не самое оптимальное решение, но задача была сделать "вчера и масштабируемо" (ну, как обычно) — поэтому максимально просто и тупо. Зато можно пачками плодить ноды, из узких мест — тут, конечно, в первую очередь БД, во вторую — дополнительная нагрузка и задержки на пересылку сообщений фронт-редис-бэк. В остальном вполне юзабельное решение. Чисто технически редиску можно вообще выкинуть из цепочки пересылки и оставить её только для сессий и других внутренних данных.
это немного не то, что я ожидал, мне нужна именно архитектура взаимодействия на фронте, как правильно следить за подписками и обновлением данных в большом приложении, вряд ли можно обойтись просто хуками в компонентах, где нужен real-time
p.s. очень нестабильная хрень
Денис Соловьев, архитектура взаимодействия чего с чем? Веб-сокеты — это просто протокол для взаимодействия между клиентом и сервером. В чём именно у вас сложности? Если у вас всё так нестабильно — значит вы что-то делаете не так. Имеет смысл либо сделать рефакторинг вашего текущего кода либо выкинуть его, переосмыслить всё и с учётом новых требований перепроектировать и переписать с нуля проблемную часть.
я хочу понять как правильно расширить текущий функционал, сокет работает нормально, но если нужно внести какие-то правки, добавить новые события, то приходится лазить по нескольким файлам, искать места где используются хуки и какие ключи передаются в хук, по этому и хотелось бы привести все к более удобному формату и знать, что нужно залезть в конкретное место, а не ловить потом баги
проблем при общении с бэком нет, этот этап уже пройден, нужно довести на фронте до какого-то правильного решения, а не просто работающего
но если нужно внести какие-то правки, добавить новые события, то приходится лазить по нескольким файлам, искать места где используются хуки и какие ключи передаются в хук, по этому и хотелось бы привести все к более удобному формату и знать, что нужно залезть в конкретное место, а не ловить потом баги
Воот, теперь уже понятнее: проблема в сопровождении, доработках и расширении текущего функционала. Причин может быть много разных: ошибки и недоработки проектирования, проблемы в реализации и т.д. и т.п. Причины — не принципиальны, но лучше их всё же найти и учитывать в будущем. Решение, как я уже говорил вполне очевидное: рефакторинг или полное переписывание. Для начала вам нужно полностью исследовать, расписать и визуализировать графически текущую логику вашей реализации. Затем проанализировать её и понять, что с ней не так. А уже по результатам этого анализа принимать решение: дорабатывать текущее решение или же перепроектировать его с нуля с учётом уже имеющегося опыта, а так же задач, требований и ограничений.