Pull to refresh

Comments 19

Мда... Хабр это не Пикабу. До сих пор про фамилию жены автора никто не спросил ))

P.S. За статью спасибо.

Query сильно ухудшает читаемость кода, лучше по-максимуму использовать Select, в котором и ошибиться нереально и занимает он всего 3-4 строчки. Писать-читать такой код очень легко и понятно.

Скажите, пожалуйста, а как так у Вас именованные параметры у функций работают? В вашем коде:

    psqlInfo := fmt.Sprintf(format: "host=%s port=%d user=%s "+
      "password=%s dbname=%s sslmode=disable",
      host, port, user, password, dbname)

Однако, даже передовая версия компилятора Go не допускает такого синтаксиса: https://go.dev/play/p/OgWVn9Tkc8i?v=gotip

опечатка затесалась, спасибо, исправил

Задам вопрос, раз тут специалисты собрались.
Как правильно организовывать хранение данных в базах для разных аккаунтов?

Вот у меня в системе для каждого аккаунта есть набор таблиц, где накапливаются данные приходящие от разных приборов. Все приборы однотипные, но у каждого аккаунта их разное число и параметры. Далее клиент через приложение может видеть что там накопилось.

Сейчас при создании аккаунта я для нового клиента создаю отдельную БД в PostgreSQL. Потом когда он логинится, получает JWT и далее на каждый REST API запрос, бэкенд по JWT понимает к какой БД подключаться.

Смущает отдельная БД для каждого аккаунта. Это норально или все это делается каким-то другим способом? По нагрузке планируется порядка сотен тысяч клиентов и запросы с периодом раз в 1 или 5 сек.

зависит от требований. если надо жёстко изолировать данные одних пользователей от других, то можно сделать под каждого пользователя базу, и даже, отдельные инстансы, только это будет очень очень неэффективно. Зато легко масштабируется.

Есть вариант чуть более эффективный - поиграться с row_security в PostgreSQL - но это тоже будет малоэффективно. по сравнению с предыдущим вариантом чуть более эффективно, потому что данные нескольких пользователей можно сгруппировать в одной базе, и чуть поднять "плотность" пользователей.

обычно разграничение прав делают на уровне кода, в каждой таблице есть колонка, типа userId и все запросы должны быть в стиле select * from table where user_id = SOME_USER_ID .

Чтобы исключить возможность разработчику случайно, или намеренно, получить доступ к чужим данным можно закрыть прямой доступ к таблицам, а работу с данными организовать через хранимые процедуры которые создаются и изменяются проверенными разработчиками, имеют доступ к таблицам благодаря опции security definer, принимают на вход jwt токен, и скрывают внутри себя всё операции по получению и изменению данных. Это было бы крутым решением, если бы не планируемые 100k запросов в секунду, для одной физической машины это выглядит многовато, данные придётся шардировать на несколько физических машин, это приведёт к тому что jwt токен придётся заменить на user_id (чтобы бэкэнд понимал к какому шарду коннектиться для выполнения запросов), а связку jwt -> user_id придётся хранить в отдельном сторадже.

вот как-то так :)

Спасибо за развернутый ответ. Теперь понятно в какую чторону смотреть

Колонку AccountId в каждую таблицу добавляешь, про в индексы тоже не забыть.

Спасибо за статью! Интересно было бы еще посмотреть на вашу обертку открытия соединений с чтением из vault, и разделяете ли вы соединения с мастером и слейвом, если да то как? 2 соединения и пакет для работы с конкретной базой куда передается 2 соединения и выбирается в зависимости от того что делает метод (читает или пишет). Или реплика только для фейловера (HA), а не для балансировки нагрузки?

По поводу контекста, чтобы копировать, но без cancel можно вытащить код detach метода, вроде так называется, из пакета context (почему он там приватный так и не понял, крайне полезная штука), позволяет копировать контекст (все что в нем, включая трейс, логгер и прочие переменные) не боясь что он отменится (приходилось с таким играться в errgroup).

Люблю разные новые тулзы находить и тут увидел pgeon - что это и где можно почитать? Гугл мне что-то не очень в этом помог :)

про vault в статье рассказано не совсем полно. на самом деле есть обёртка которая прокидывает секреты из vault в переменные окружения, и потом уже логины/пароли из переменных окружения используются как параметры для подключения внутри psql.Connect()

про detach, спасибо, посмотрю внимательнее. ещё я находил https://github.com/goinsane/xcontext/blob/master/xcontext.go#L14 по описанию может пригодиться, но использовать ещё не довелось

pgeon - это часть механизма CDC (change data capture) и доставки данных в Data Warehouse. если в двух словах - это триггер на таблице который логгирует изменения в pgq-очередь и обвязка над этим механизмом которая позволяет разработчику не задумываться о внутреннем устройстве. В общих чертах я рассказывал об этом на pgConf 2022, но, кажется, организаторы не публиковали видео, и вообще не уверен что опубликуют. Если аудитория проявит интерес к этой теме, то можно подумать о том чтобы рассказать более подробно в одной из статей.

Спасибо за ответ :) про vault понял. За delay спасибо, не натыкался, посмотрю. Ну и по pgeon надеюсь все таки выложат. Вообще с CDC в пг все сложно))) это не тарантул ?

В pgq-очередь это случайно не на listen/notify ли строится?

Нет, listen/notify для такого не подходят, там нет гарантий доставки, там потеря событий при отсоединении подписчика, и, кажется, есть какие-то ограничения на размер payload. Я, честно говоря, даже и не знаю где можно применять listen/notify, мы в своё время использовали как способ доставки метрик изнутри хранимых процедур, но это было ещё во времена когда много логики было скрыто в них. Вот, кстати, статья, на эту тему https://habr.com/ru/company/avito/blog/323900/.

А что про pgq, то мы используем вот это https://github.com/pgq . Ранее это было частью пакета утилит skytools которым skype в своё время реализовывал логическую репликацию londiste. Мы ранее тоже использовали, но отказались вообще от использования логической репликации в компани.

А отказ от логической по причине скорости? Видимо используется физическая, но каким образом делается обновление серверов, ведь там нужно соблюдать полное совпадение версий РСУБД? Я понимаю когда проект небольшой и серверов несколько штук всего, можно побыстрому стопнуть. Но когда их очень много? Какой путь был выбран? Предположу, что какую-то логику пришлось заложить в PgBouncer.

логическая репликация londiste отличная штука, мы научились ей пользоваться там где она действительно нужна, пришлось правда создать достаточно много инструментов вокруг неё чтобы она удовлетворяла нашим требованиям. есть хорошее видео 2017-го года с воркшопа на хайлоаде где Миша, Костя, и Сергей подробно рассказывают как что устроено и что пришлось доработать
https://www.youtube.com/watch?v=vCYGOVa3w1g . Было круто, но... со временем компания выросла, скорость внедрения новых "фич" тоже, и все эти "кастомные" штуки, стали очень дороги в поддержке и скорее мешали, чем помогали. Поэтому целенаправленно пошли в сторону упрощения. И теперь нет никаких логических репликаций средствами DBA, никакого чтения с физических реплик. Реплики только для поддержания High Availability.

Не очень понял вопрос про обновление. Если имеется в виду обновление минорных версий, то схема примерно такая, поочерёдно обновляются физические реплики, потом одна из них становится мастером и обновляется оставшаяся реплика. Переключение мастера для приложения выглядит как обрыв коннекта с базой и небольшой даунтайм. С обновлением мажорных версий конечно всё чуточку сложнее, но схема примерно такая же. pgbouncer во всём этом не участвует и работает как простой прокси.

Читаю код и приёмы которые используются для того чтобы реализовать тот или иной достаточно низкоуровневый функционал (подключения, пулинг, транзакции) - в чем бенефит Go в результате перед языками где есть достаточное количество устоявшихся библиотек и фреймворков?

простой вопрос - сложный ответ. сильно зависит от задачи и что понимается под `языками где есть достаточное количество устоявшихся библиотек`. Когда принимается решение использовать тот или иной язык или фрэймворк это всегда многокритериальный выбор. Стоит учесть и свой опыт, и возможности языка и его runtime, и наличие нужных библиотек, наличие поддержки этих библиотек, наличие разработчиков на рынке и возможность их нанять. А так если сравнить го и любой другой развитый ЯП, просто как язык программирования, то вроде и нет никаких профитов.

Именно так, и ровно все что вы привели в качестве критериев: "свой опыт, и возможности языка и его runtime, и наличие нужных библиотек, наличие поддержки этих библиотек, наличие разработчиков на рынке и возможность их нанять" - го не является здесь очевидным выбором. Поэтому и спрашивал - в чем его бенефит-то в результате?

для Авито это, скорость, "микросервисность" (если это можно так назвать), и наличие нужного количества разработчиков на рынке труда. с php разработчиков вроде бы больше, но не удобно работать в микросервисной архитектуре. есть ещё питон, и он тоже в Авито используется достаточно широко.
со всеми остальными языками будут проблемы, в основном из-за найма.

Sign up to leave a comment.