LINUX.ORG.RU

Конкурентное обновление таблицы с ресурсами

 , ,


0

2

Вкратце опишу задачу. У компании есть клиенты, от имени которых она ведет переписку с третьими сервисами используя google workspace.

У каждого email эккаунта в google workspace можно завести алиас с помощью api, и использовать его в дальнейшем для конкретной переписки. После окончания переписки алиас можно просто удалить с помощью того же api. Это нужно потому, что есть лимит на количество алиасов для каждого email эккаунта в google workspace.

Бэк реализован на python с использованием sqlalchemy. БД - postgres.

Поэтому возникла идея создать две таблицы. Одну - со списком имеющихся email эккаунтов. И вторую, где будет список использующихся в данный момент алиасов для каждого email.

То есть во второй таблице (aliases) будут следующие поля: id, email, alias_email.

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

То есть делаем запрос вроде

select email, count(*)
from aliases
group by email

А затем выбираем из результата любой email, у которого не превышен лимит и вставляем новую запись со свежим алиасом для этого email в эту же таблицу. Ну и создаем алиас помощью google workspace api.

При окончании переписки просто удаляем запись с алиасом из таблицы и удаляем с помощью api сам алиас.

Возникает ситуация гонок, надо бы на момент выбора алиаса лочить таблицу aliases.

Можно ли это сделать с помощью алхимии? И какие есть альтернативы в подобной ситуации, когда есть конкурентные запросы к таблице в целом, а не к конкретным записям?


Ответ на: комментарий от anonymous

В том то и дело, что мы можем запретить с помощью for update именно update конкретных записей, а не вставку новых на момент выбора алиаса.

Может можно было бы использовать этот механизм (select for update) при какой-то другой схеме данных для данной задачи, но пока не понимаю, как.

C
() автор топика
Ответ на: комментарий от masa

Покажу пример таблицы, чтобы было наглядней. Предположим, что лимит у нас 3 алиаса на email.

-----------------------------------------------------------
|. email                   |           alias_email        |
|---------------------------------------------------------|
| corporateemail1@gmail.com|          vasya@gmail.com     |
| corporateemail1@gmail.com|          lesha@gmail.com     |
| corporateemail1@gmail.com|          misha@gmail.com     |
| corporateemail2@gmail.com|          sanya@gmail.com     |
| corporateemail2@gmail.com|          vova@gmail.com      |
-----------------------------------------------------------

Отсюда мы сделаем запрос с group by, узнаем, что у corporateemail2@gmail.com есть еще не занятый алиас и вставим новую запись для этого email

| corporateemail2@gmail.com|          kostya@gmail.com     |

То есть по идее надо бы запретить вставку и удаление записей на момент выбора алиаса.

C
() автор топика
Ответ на: комментарий от gruy

SERIALIZABLE - неплохое решение, но как я понимаю, оно повлияет и на все остальные запросы к другим таблицам, то есть сильно уменьшит производительность системы в целом. Неужели создавать два коннекшна с разным уровнем изоляции - одно для для работы с обычными таблицами и другое для таблицы с алиасами?

C
() автор топика

id, email, alias_email

Ты сейчас просто послал нафиг третью нормальную форму.

emails: id, email aliases: id, email_id, alias

Не хочешь городить контекст менеджер который бы через текущий коннект дергал сырой SQL с блокировкой - используй распределенный мьютекс в коде.

anonymous
()
Ответ на: комментарий от anonymous

Ты сейчас просто послал нафиг третью нормальную форму.

emails: id, email aliases: id, email_id, alias

Да, я просто не стал до конца нормализовать таблицы. Скорей всего так и сделаю, как ты сказал - с email_id в таблице aliases.

Не хочешь городить контекст менеджер который бы через текущий коннект дергал сырой SQL с блокировкой - используй распределенный мьютекс в коде.

У меня есть мысль создать хранимую процедуру в postgres - там вроде можно сделать lock таблице aliases. Гляну насчет сырого sql, раз там можно тоже lock указать.

C
() автор топика

какая жесть в ответах…

  1. от дублей в БД адекватно защищает только уникальный индекс за счет своей специфичной структуры
  2. unique constraint от дублей тоже защищает, но здесь начинаются истории про реализацию конкретной СУБД, т.е. unique constraint можно строить как на обычных индексах, так и вообще без индексов, но цена вопроса здесь определяется уровнем эскалации блокировок, условно со стороны стандарта ничего не препятствует бахнуть на таблицу unique constraint, но результат будет такой, что каждая вставка или обновление будет захватывать эксклюзивную блокировку на таблицу, чего обычно не ожидается, поэтому СУБД обычно для неискушенных разработчиков «не различают» unique constraint и unique index, т.е. задание constraint будет создавать или требовать наличие индекса, а создание индекса само под собой подразумевает существование constraint, но разница есть
  3. в каких-то БД есть возможности создания «бизнесовых блокировок», например : https://www.postgresql.org/docs/current/explicit-locking.html#ADVISORY-LOCKS - но все реализации что существуют настолько всраты, что лучше их обходить стороной

т.е. да:

  • там где требуется уникальность нужно использовать уникальный индекс
  • с ошибками придется либо мириться, либо уметь обходить (например расставлять сейвпойнты, но там тоже какая-то разумность требуется, например в PostgreSQL там все очень плохо с этим)
borisych ★★★★★
()
Ответ на: комментарий от C

Пошли нафиг третью нормальную форму ©.

--------------------------------------------------------------------------------------
| email                    |  alias_email1      | alias_email2    | alias_email3     |
--------------------------------------------------------------------------------------
| corporateemail1@gmail.com|  vasya@gmail.com   | lesha@gmail.com | misha@gmail.com  |
| corporateemail2@gmail.com|  sanya@gmail.com   |                 | vova@gmail.com   |
--------------------------------------------------------------------------------------

Правда, если лимит на количество алиасов потом будет изменен, тебя ждут проблемы.

Aceler ★★★★★
()

При создании емейл создавай сразу максимальное количество строк алиасов для каждого емейл, и вместо добавления делай

update aliases set alias_email='ivanovalias@fake.email' where id in (select id FROM alias_email WHERE alias_email is null and email='ivanov@real.email' LIMIT 1)
returning id

И наоборот set alias_email=null когда удаляешь алиас

и все это в serializable транзакции. Конфликты от оптимистичной блокировки разрулишь на клиенте, они в твоем случае будут очень-очень редки.

Вариант с update оптимален когда есть граничение на количество строк, как в твоем случае.

Запросы не проверял, но идея должна быть понятна.

Проблема будет только с уникальным индексом чтобы алиасы + основные емейлы были уникальынми. Можно для алиасов завести отдельный домен, можно оба емейла хранить в одной колонке.

shimshimshim
()
Последнее исправление: shimshimshim (всего исправлений: 5)
Ответ на: комментарий от borisych

но все реализации что существуют настолько всраты

а что именно всрато в

IF pg_try_advisory_xact_lock(bla_bla_md5_hash)
THEN
/* делаю свои тёмные делишки будучи уверенным, что сюда никто другой не попадёт пока не закончу */
END IF;
?

Меня пока этому ребятишки поопытнее не научили - я велосипедил вручную семафоры в таблицах.

Плохому научили?

Вот конкретно прям сейчас использую это для применения «миграций», ибо не доверяю бэкенду с их асинхронщиной, многопотоковостью и прочими «пулами соединений». Мне нужны гарантии, что «миграции» будут применяться ровно в той последовательности, которую я хочу. Заблуждаюсь?

Toxo2 ★★★★
()
Последнее исправление: Toxo2 (всего исправлений: 1)
Ответ на: комментарий от shimshimshim

*И да, добавлю, лучше не использовать IFы, хранимки, локи и прочую императивную ВОНЬ там, где можно все ограничения сделать декларативно и выбирать все одним запросом, как в варианте с update.

shimshimshim
()
Ответ на: комментарий от Toxo2

а что именно всрато

все. Если-таки интересуют подробности:

  • я ожидаю, что в названии блокировки можно что-то осмысленное писать, а никак не число, если нам пришлось диагностировать в чем проблема, то как понять вообще откуда блокировка летит? тут все как в и 30 лет назад: 640Kb хватит всем
  • также я ожидаю, что блокировку можно снимать когда она не нужна, ну вот update и select for update до конца транзакции блокировку не снимают и это боль прямо боль, тут предлагается другое API, которое точно также творит дичь, вся разница здесь лишь в том, что эти advisory locks можно заменить табличкой с уникальным индексом, но из-за общей всратости PostregSQL в нее придется ходить через savepoint
  • оно все отдается на откуп приложения в итоге, т.е. если все приложения работают правильно, то вполне возможно, что БД пребывает в ожидаемом состоянии, а если нет, то и нет. Т.е. тут можно с уверенностью сказать, что базы, где активно используются advisory locks - все сплошь помойка
borisych ★★★★★
()
Ответ на: комментарий от borisych

Спасибо, интересно.

Насколько я понимаю - если в мои таблицы никто не может попасть прямым доступом, ни на чтение, ни тем более на запись - всё взаимодействие с БД только на процедурах - то advisory locks не так уж и ужасен.

Toxo2 ★★★★
()
Ответ на: комментарий от anonymous

Я далеко не Гуру. Тут есть ребятишки более глубоко погруженные в вопрос.

В 16й версии, при использовании родной клиентской библиотеки libpq, появилась возможность указывать несколько хостов для подключения.

Игрался. В чём кайф именно «балансировки» не понял . Или подряд перебором, кто первый отзовётся, или случайным образом из списка. Так себе балансировка - «а я вот сейчас угадаю куда мне подключиться».

Точнее кайф вроде один - если у тебя Мастер-Мастер (который теперь можно делать просто логической репликацией) и один из них стал недоступен клиенту - клиент сам подключится ко второму. Т.е. не нужны внешние проверялки-переключалки. Только это же прям сильно за уши надо тянуть до слова «балансировка». КМК.

Вот логическую репликацию в обе стороны я запихал в Прод. Красота. Я счастлив и доволен. Только клиенты - всякие Питоны и прочие всё равно же ничего не знают о новых возможностях libpq - так и переключаются по старинке, полувручную.

------

https://postgrespro.ru/docs/postgresql/16/release-16
https://www.opennet.ru/opennews/art.shtml?num=59758

Toxo2 ★★★★
()
Последнее исправление: Toxo2 (всего исправлений: 1)
Ответ на: комментарий от Toxo2

Не, ты не понял (и я плохо описал). Речь не про балансировку на разные хосты.

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

Вот вроде в пг что-то с этим решили (или это был мой пьяный сон).

anonymous
()
Ответ на: комментарий от anonymous

Ну например если я возьму у себя сейчас рабочий сервер с пг14 и сделаю туда даже 10 тысяч коннектов то все будет плохо.

Через pgbouncer будет ок (очевидно, он для этого и нужен).

anonymous
()
Ответ на: комментарий от anonymous

если я возьму у себя сейчас рабочий сервер с пг14 и сделаю туда даже 10 тысяч коннектов

То, мне кажется, тебя может найти DBA и побить.

Не представляю себе нужды в таком количестве реальных подключений к БД одновременно. На таких количествах уже само ядро Линукса начинает помирать (и надо крутить somaxconn), какой уж там ПГ.

Toxo2 ★★★★
()
Ответ на: комментарий от Toxo2

Ну ты не представляешь, а pgbouncer придумали не с пустого места - берём стопицот коннектов и работаем с ними.

Может у нас в терминологии расхождения, но ситуация вполне обычная.

Твердо уверен, что в ченжлоге новой мажорной версии пг было про это.

anonymous
()
Ответ на: комментарий от anonymous

Или я действительно что-то не понимаю, или ты мешаешь у одну кучу сервер и клиентов.

pgbouncer это же по сути просто клиент. Ну, пусть будут Питон+Редис делать вид про 100500 коннектов к БД. Вот они у нас в Проде и помирали на сотнях тысяч понабежавших одновременно. Сама БД-то тут ни при чём. И едва ли вообще когда-нибудь это дело будет касаться БД.

Toxo2 ★★★★
()
Последнее исправление: Toxo2 (всего исправлений: 1)
Ответ на: комментарий от Toxo2

едва ли это вообще когда-нибудь это дело будет касаться БД.

Конечно будет. Если напрямую 100500 клиентов пойдут к тебе в бд, то это будет тяжелее, чем эти клиенты шли бы через условный прокси пг, который держит пул коннектов лучше, чем сам пг.

И именно это пытались решить в новой мажорной версии и частично решили, как найду - скину коммиты или строки из ченжлога.

Олсо пгбаунсер у пг в репах лежит, мужики то видать и не знают что пулбалансир нужен.

anonymous
()
Ответ на: комментарий от Toxo2

Раз уж мы на русском, предлагаю посмотреть (думаю ты и так это знаешь) https://postgrespro.ru/docs/postgrespro/10/pgbouncer

Там есть три режима работы. Найду в ченжлоге о чем говорил - скину.

Печально кстати что о постгресе тут говорят немногие.

anonymous
()
Ответ на: комментарий от Toxo2

Да чой-то? У меня клиентский софт открывает соединение к бд и висит, может даже не закрыть его, и таких тысячи одновременно.

Пгбаунсер все решил.

anonymous
()
Ответ на: комментарий от anonymous

Ну ты не представляешь, а pgbouncer придумали не с пустого места - берём стопицот коннектов и работаем с ними.

pgbouncer может быть полезен весьма в специфичных ситуациях, а именно когда у приложения нет собственного пула для БД, т.е. расклад в реальности примерно такой:

  • да, каждый раз подключаться к БД для выполнения запроса выходит довольно дорого, тут спора нет
  • держать в приложении соединение открытым тоже ничем хорошим не сулит совершенно, в большинстве случаев получается так, что в приложение что-то из базы выбрало и пошло фигней страдать, в духе формировать json/xml/html для клиента и потом ждать, когда клиент этот трафик получит уже, а соединение все висит и висит открытым, т.е. у открытого соединения должно быть какое-то время жизни/скоуп - это вызывает потребность иметь пул соединений
  • ну хорошо, случилось так, что у приклада пула собственного нет, чем тут поможет pgbouncer? мы думаем, что подключаться к БД - это дорого, а чего мы решили что подключаться к pgbouncer дешево? В первом случае у нас tcp handshake + fork (особо упоротые еще TLS впихивают), а во втором случае только tcp handshake - вот оно тоже нифига не дешево выходит, удешевлять можно только тем, что pgbouncer нужно устанавливать не на БД, не где-то как отдельный сервис, а, сюрприз, прямо на локалхосте, т.е. там же где крутится приклад, при таких раскладах, где мы под экземпляр приложения устанавливаем pgbouncer, возможности хоть как-то регулировать количество подключений к БД просто напросто нет - у нас клиентом БД становится не приклад, а pgbouncer, т.е. с точки зрения вводимых ограничений на количество одновременных соединений с БД разницы нет никакой (куда лучше в приложении использовать собственный пул)
  • нужно понимать, что сам по себе pgbouncer был написан дидами еще черт знает когда, и в многопоточность/производительность он не умеет ну вообще никак, условно с трафиком в гигабит он точно не справляется, на счет полгигабита - уже на грани фола. Есть odyssey от Yandex (https://github.com/yandex/odyssey), но судя по тому что пишут в issues ситуация с ним примерно такая: он в яндексе скорее всего работает, потому что там потребности хоть и довольно специфичные, но при этом ограниченные (т.е. все делают по определенным шаблонам), но это никак не продукт для массовой аудитории. Есть еще решения от отечественного производителя: https://postgrespro.ru/docs/enterprise/16/connection-pooling - сделать более всратее нужно еще постараться
borisych ★★★★★
()
Последнее исправление: borisych (всего исправлений: 1)
Ответ на: комментарий от borisych

Ты прав во всем, но ветвь комментариев началась с того что ВРОДЕ КАК в пг хотят и начали пилить свой пул коннектов.

Я вообще не хочу спорить, надо это или нет; переложить это надо на серверную часть (в нашем случае пг) или клиентскую, черт с ним, у меня и так все работает.

Хехе, а вот щас подумал что может это и не в пг пилили а в руфорке пг про. Весело тогда.

anonymous
()
Ответ на: комментарий от borisych

pgbouncer нужно устанавливать не на БД, не где-то как отдельный сервис, а, сюрприз, прямо на локалхосте, т.е. там же где крутится приклад, при таких раскладах, где мы под экземпляр приложения устанавливаем pgbouncer, возможности хоть как-то регулировать количество подключений к БД просто напросто нет - у нас клиентом БД становится не приклад, а pgbouncer, т.е. с точки зрения вводимых ограничений на количество одновременных соединений с БД разницы нет никакой (куда лучше в приложении использовать собственный пул)

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

anonymous
()
Ответ на: комментарий от anonymous

Как по мне - такой «клиентский софт» даже подпускать к БД близко нельзя.

Ну, и собственно, про локи с которых тут вся тема стартовала - фигу ты их правильно в БД положишь, когда не можешь на уровне БД отличить коннекты - ты-то думаешь, что это настоящий клиент. А это какой-то клиентский пулл не пойми каких настоящих клиентов.

В таком раскладе других вариантов, кроме advisory locks и не придумывается.

Toxo2 ★★★★
()
Ответ на: комментарий от borisych

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

Как интересно получается, вот тут был соседний тред (если не видел, напиши, найду ссылку), где спрашивали зачем закрывать приложение сверху nginx-ом.

Очевидно, все комментаторы в том треде довольно однообразно ответили.

Твой комментарий про базу и зависший коннект мне почему-то напомнил это :)

И в десятый раз - постгрес не умеет сейчас в дешёвые коннекты, но их хотят запилить скоро.

anonymous
()
Ответ на: комментарий от Toxo2

Так ты рассуждаешь как дба, а я как админ и пользователь, поэтому вопросы возникают у меня глупые.

Приятно иногда поговорить с тобой в тредах, где пускают анонимуса.

anonymous
()
Ответ на: комментарий от Toxo2

Я нашел, вроде оно. Мда :)


В большинстве инсталляций Postgres Pro для ограничения числа запускаемых обслуживающих процессов применяются внешние средства, например, J2EE, odyssey или pgbouncer, один из наиболее популярных пулов соединений для Postgres Pro. Однако применение внешних пулов соединений подразумевает дополнительные издержки, связанные с их установкой, настройкой и сопровождением. Кроме того, если пул работает в одном потоке, как pgbouncer, вам также придётся запускать несколько экземпляров пула, чтобы он не оказался узким местом в сильно нагруженных системах.

Решить эти проблемы призван встроенный в Postgres Pro Enterprise экспериментальный пул соединений. В отличие от внешних решений он не требует дополнительного обслуживания и не налагает никаких ограничений на клиентов. Клиенты, взаимодействующие с сервером через встроенный пул соединений, могут использовать параметры конфигурации сеансов, подготовленные операторы и временные таблицы так же, как и напрямую.```
anonymous
()
Ответ на: комментарий от anonymous

Твой комментарий про базу и зависший коннект мне почему-то напомнил это :)

мне не напомнило, просто потому что я заранее описал две разных ситуации: есть очевидная, когда трафик идет во вне и там нужно обрубать концы, тут upstream proxy помогает (вопрос лишь в том как его готовить правильно), а есть ситуации, когда условно дорогое соединение висит, когда у приклад его не использует (чуть позже напишу расклад в ответе для @Toxo2)

borisych ★★★★★
()
Ответ на: комментарий от Toxo2

Насколько я понимаю - если в мои таблицы никто не может попасть прямым доступом, ни на чтение, ни тем более на запись - всё взаимодействие с БД только на процедурах - то advisory locks не так уж и ужасен

ну если часы открутить на машине времени на 20 лет назад, то может и покатит, хотя очень сильно сомневаюсь, вот что было 20 лет назад (условный state of art):

т.е. то что есть в разрезе advisory locks у «прямых конкурентов» (специально в кавычки поставил, потому что PostgreSQL никак не конкурент ни Oracle, ни MSSQL, и никогда им не был):

  • имена бизнес-блокировкам давать можно (в Oracle оно сделано плохо, но возможность есть, у MSSQL лимит в 255 байт - в принципе терпимо)
  • у блокировок есть определенные правила совместимости, т.е. если нужно, то можно сделать так, что читатели блокируют писателей и наоборот

вот ничего из этого в PostgreSQL сделать нельзя (специально думал, можно ли lock mode эмулировать наборами блокировок - нет, так не получится).

Если оставаться ближе к тебе топика, то мое мнение, основанное на наступании на кучу грабель, примерно такое, что нужно при любой возможности нужно создавать уникальный индекс, в противном случае последствия будут непредсказуемые (при это я лично считаю, что FK - это полная ссанина)

borisych ★★★★★
()