Comments 29
В ограничения еще стоит добавить, что коннект к PostgreSQL будет держать один из бэкендов БД. Соотвественно каждый экземпляр сервиса будет держать по коннекту. Чем больше инстансов, тем больше таких коннектов. На большом количестве балансирующих (микро)сервисов (речь о дестяках) это может вызвать слишком большую нагрузку на БД просто тем, что вы отобрали потоки (бэкенды), которые висят и ничего не делают. Поэтому на нагруженных сервисах стоит делать по другому.
Добрый день, знаю об этой в ситуации. Это не сыграет значимой роли в рамках нашего проекта без колоссальных нагрузок.
В ситуации, если нагрузка будет большой, описанную Вами проблему можно решить добавлением бóльшего числа коннектов, которое зависит от количества реплик sso.
Если есть предложения по улучшению решения, с удовольствием выслушаю.
Как можно улучшить? Ну это зависит от контекста (с) :)
1. Первое что приходит в голову - вернуть все же Redis :)
2. Можно изменения в словарях собственно отправлять в брокер какой-то вроде Kafka, RabbitMQ etc и массив сервисов будет уже его слушать
3. Ну и если хочестся ручной привод: можно выделить роль листенера ограниченному подмножеству инстансов с последующим броадкастом на обновления всем остальным (NATS тот же завести под капот, он очень легковесный).
На большом количестве балансирующих (микро)сервисов (речь о дестяках) это может вызвать слишком большую нагрузку на БД просто тем
Как так вышло, что на большом количестве микросервисов мы получили подключение к одной БД?
добрый день! а стоило ли реализовывать свое решение, когда на просторах гитхаба можно найти patrickmn/go-cache ?
Лезть в БД диском на каждый чих
в pg (да и в любой более-менее развитой СУБД) есть буферный кеш в котором лежат часто используемые страницы. Так что вы не ходите на каждый чих на диск.
Гарантированную консистентность (кэш обновляется сразу после коммита транзакции в БД).
гарантированная консистентность у вас будет, если у вас notify "вдруг" станет синхронным, будет в нём механизм ретраев чтобы обрабатывать ошибки нотификации и т.д.
1. Про «диск»:
Вы правы, это упрощение. Речь не о физическом диске, а о накладных расходах сетевого взаимодействия и парсинга SQL. Даже из кеша буферов Postgres получение данных требует сетевого обмена и десериализации (десятые доли мс). Чтение же из локальной map в памяти Go — это наносекунды. Экономия именно на устранении межпроцессного взаимодействия.
2. Про «гарантированную консистентность»:
Абсолютно верно, здесь я переоценил формулировку. NOTIFY — асинхронный механизм, он не даёт гарантий доставки, как очередь. Это очень быстрая eventual consistency. В нашем случае (стабильное соединение, маленькая таблица) потеря сигнала маловероятна и быстро компенсируется, но строгой гарантии нет.
Видится мне что это просто чтобы поставить пункт в резюме….
В этом решение есть одна очень не приятная проблема, а именно, если кто-то подпишется на NOTIFY и не будет вычитывать данные, то данные не отправленные клиенты-подписчику будут накапливаться на сервере и когда объем данных в очереди превысит 8 Гб, то сервер остановится.
Немного смущает пара моментов:
Если NOTIFY зовется FOR EACH ROW, то почему бы не передавать в нем payload с контентом записи, аккуратно порезав по 8KB, если надо.
Если все равно хочется перечитывать таблицу целиком, что бы не сделать FOR EACH STATEMENT?
А мне нравится :) 🌟
Вот бы еще навайбкодить простое демо приложение для всех описанных тестов и положить в тот же докер для студентов очень наглядно.
Непонятен смысл делать путем тригера из БД ивентов. В чем смысл? Админ руками в БД лазит? Я бы по рукам дал. Интерфейс или АПИ и пущай себе конфигурит. А уже админка пусть информирует сервисы что подтянули конфиги путем пуша в брокер.
Спасибо за статью.
Таблица маленькая: Вряд ли там будет больше 100 строк (сервисов в экосистеме не тысячи).
Читается постоянно: Почти каждый запрос на аутентификацию требует проверки: "А есть ли такое приложение? А какие у него права?".
А чем не устроило собственное решение на sync.Map?
Из преимуществ вижу только отсутствие затрат на первичный прогрев кэша ввиду безопасного хранения данных (а насколько он нужен). Но в тоже время, дополнительный поднятый бекенд для psql, если не через пул работаем. А если через пул, то как будто бы все равно вечно поднятый Коннект (к тому же и постоянно занимающийся с базой, как понял из статьи), не то что бы способствует улучшению пропускной способности psql в целом.
Возможно, криво статью прочитал.
Как раз для таких заголовков есть противопоставление:
В итоге один и тот же кеш лежит в нескольких подах, т.е. расход памяти = X * Y. А потом еще рассинхрон, и так далее... Мы тоже сначала делали локальный кеш. И в итоге всё равно перешли на использование Redis, чтобы кеш был общий.
Когда у вас из 6ms времени выполнения запроса в 99.9 перцентиле у вас 5 ms кушает поход в Redis вы задумаетесь о том "а надо ли оно мне" :)
Конечно можно сказать - эй, тут 6 ms, не надо ничего оптимизировать. Но если это один из самых нагруженных сервисов... Ну вы понимаете :)
А проблемы когеренции кэшей решали задолго до микросервисной архитектуры - еще в мультипроцессорных средах.
А память - ну тут классический трейд-офф.
Хранить в RAM можно, иногда даже очень полезно (снижение нагрузки на сеть, ускорение работы критических частей), но с этим надо уметь работать. Принимать ли на себя эти сложности или делегировать их готовому решению - зависит от контекста (с) :)
Не совсем понял что вы имели ввиду в первом пункте таблицы
Мгновенно (ns). Данные уже в куче Go в нужном формате.
Быстро (~0.5 - 1 ms). Требует сериализации/десериализации (JSON/Protobuf) и похода в сеть.
Т.е у вас бэкенд стоит на одном хосте с Постгресс а Редис на отдельном, и вы сравниваете два этих кейса?
Вы абсолютно правы, в статье сравнение идёт для случая, когда бэкенд и база данных развернуты в одном сегменте сети (например, в одном дата-центре). В такой конфигурации разница действительно измеряется микросекундами против миллисекунд.
Однако ключевой момент в том, что при использовании облачной инфраструктуры или микросервисной архитектуры, где Redis вынесен в отдельный сервис, разница становится ещё ощутимее. Добавляются дополнительные сетевые хопы, что увеличивает задержку (latency). И да, важный фактор — однопоточная модель Redis, которая при высокой конкурентной нагрузке может стать узким местом, в то время как локальная мапа в Go масштабируется на количество ядер.
Ну тогда надо и Редис поставить на тот же хост. И сравнивать. А то сова какая-то получается с глобусом:)
То что Редис однопоточный - это не значит что он не производительный. Как раз наоборот, это его фишка т.к ненужна синхронизация потоков со всеми вытекающими. Он умеет запускать по потоку на ядро и очень бодро обрабатывать большое количество запросов. Если же его вдруг стало мало то есть KeyDB - распределенный Редис
Спорить на основе предположений — неблагодарное дело. Конкретные цифры под конкретную нагрузку всегда важнее теоретических выкладок.
Я обязательно проведу честные тесты в ближайшее время, сравнив:
In-memory кэш (по схеме из статьи).
Redis на том же хосте (loopback).
Redis в отдельном сетевом сервисе.
Замерю не только чистую latency при разном количестве потоков, но и влияние на общую пропускную способность (RPS) системы.
Привет! Вместо rw лока можно свапать atomic.Pointer на data, если данные обновляются целиком
Не зная про цифры и конкретные проблемы с производительностью, складывается впечатление, что задачу сильно переусложнили.
Меняется редко: Новые сервисы добавляются не каждый день.
Если сайты добавляются редко, что мешает, сделать простое кеширование на уровне приложения, и вместо того, чтобы слушать события от посгри и пинговать его, просто раз в минуту актуализировать кеш?
Учитывая специфику данных — что в таблице сайты, которые связаны с sso, то вряд ли будут кейсы, где добавили новый сайт, и тут же прилетают запросы с аутентификацией для этого сайта, которого может не оказаться в кеше.
Redis больше не нужен?! Реализуем реактивный кэш на чистом PostgreSQL и Go