Пользователь обращается к API, с него списывается 1 единица с его счета. Если средств не хватает - запрос отклоняется. Запросов может быть невероятно много. Считать баланс пользователя, просчитывая все его "транзакции" с момента создания пользователя в системе крайне накладная операция, которая распухает как снежный ком с каждым новым запросом пользователя. Но такая операция необходима в каждом запросе чтобы определять имеет ли пользователь достаточно средств на балансе для осуществления запроса. Существуют ли какие-то понятные методики калькуляции баланса пользователя, чтобы не проходиться по всем транзакциям, но при этом быть уверенным что "баланс" сходится?
Используйте для баланса в реальном времени in memory db
Спасибо за совет, да, я рассматривал этот вариант, пока не понял что количество транзакций будет по-настоящему невероятно велико и расти чуть ли не экспоненциально и впихнуть кластер БД с данными о транзакиях в in memory db будет крайне затратно. Ищу более элегантное решение.
Так вы не храните там транзакции. Храните текущий балланс. А транзакции сохраняйте через очередь в архивную БД.
Выходит нам все же перед списанием с баланса пользователя необходимо получить все транзакции, посчитать, положить баланс в in memory db? Ведь перед выполнением следующего запроса от пользователя нам снова надо все получать, считать и сохранять баланс чтобы с ним снова сравниться? Не понимаю пока что в чем преимущество такого подхода.
Вы совершенно не правы. В imdb вы храните только текущий счет и транзакционно с него списываете. Как списали - кладете транзакцию в базу. Вот если счета пользователя нету в памяти, тогда надо идти в базу доставать оттуда.
Нет, зачем. В redis хранится текущий балланс или счетчик запросов. В PG хранится история. - в момент запроса по API проверяйте балланс в redis, если все ок, то делаейте -1 в redis, и процесьсте запрос. Параллельно отправляйте в очередь информацию о транзакции. Воркер разгребает очередь и сохраняет транзакцию в PG. Раз в час/10 минут как угодно пересчитывайте балланс на PG. Чтобы было откуда взять если redis перезапустите
А в чём проблема просто создать таблицу для текущего баланса и обновлять её (хоть с каждой транзакцией, хоть более "умно", время от времени "сворачивая" баланс)?
В том что в первую секунду мы "выводим" баланс из 100 000 транзакций. А через 3 секунды из 300 000 транзакций. А через сутки представляете сколько транзакций нужно обсчитать, чтобы получить баланс для следующего 1 запроса?) Вот поэтому.
Одну. Взяв баланс из таблицы баланса. В чём проблема-то? ;)
Что значит"баланс сходится"? В смысле, надо при каждом запросе вычесть единичку и проверить на больше нуля? Но это один атомарный запрос.
Вот тоже хотел по Редис сказать
Под сходится баланс я имею ввиду то что в системе из ниоткуда деньги не появлись и никуда в "никуда" не ушли. То есть дебет=кредету. По всем счетам всех пользователей. Или что по конкретному пользователю, совершающему запрос, мы знаем точный его текущий баланс в системе включая все пополнения\списания от времен создания пользователя. И вот когда мы знаем что у него баланс положительный на текущий момент времени и его достаточно для выполнения запроса, мы запрос и выполняем и пишем новую транзакцию с тратой пользователем 1 у.е.
Так просто храните текущий баланс в виде числа. И из него вычитайте. А историю отдельно.
При этом иногда проверяя сходится ли это число с реальным балансом из всех транзакций? А это идея. Но, боюсь, может где-то что-то само списаться, пополниться без транзакционного учета.
Всякие доп проверки - это уже другой вопрос. Например, можно в какой то период пересчитывать независимо нарастающий итог и по нему сверяться
Зачем что-то проверять?! Я уверен, что [очень] много систем используют подобное, это одно из "стандартных" решений для складских / финансовых транзакций — почему Вы не хотите его использовать?
По опыту - как раз время от времени проверять такие штуки независимой функцией довольно полезно.
Если они "криво" написаны — то да. Иначе — это пустая трата времени.
Хочу в будущем дать возможность пользоавтелям выводить из системы деньги назад. Таким образом нужна строгая проверка что ничто ниоткуда вдруг не взялось и не исчезло. Думаю если принципы не заложить при разработке, то в продакшене потом все будет очень туго меняться.
Давайте / выводите. Ещё раз, это стандартное решение — в чём проблема его просто использовать?
мне кажется он не понял еще как это работает
Да, это как раз про кривизну, которая где-нибудь да вылезет. И вот тогда такая проверочка сбережёт массу волос на залнмце :)
Да, спасибо что прям так настойчивы. Только сейчас до меня дошло то что вы утверждаете.
Вы случайно не видели как гугл реклама работает? Раз в неделю они сверку делают по всем кликам и корректируют баланс. Вы тоже можете это делать, и возвращать клиенту на счет средства, например за неуспешные запросы, которые вы списали и так далее Но это не значит, что вам надо апдейтить транзакцию спиания суммы по запросу - вам над просто добавить транзакцию зачисления корректировки на счет
Такое я тоже видел, спору нет — некоторым удаётся накосить и в таком, казалось бы, тривиальном месте... но иногда оно далеко не тривиальное (когда хранится много балансов, в разных единицах измерения и т.д. и т.п.). ;(
Ну тут вот видишь какие реалии - мы втроём пытаемся вежливо намекнуть про то, как написать update records set balance=balance-X where userid=... and balance > X; И у нас не получается с первого раза :)
Я вам ровно то же самое предложил, но текущий балланс хранить в отдельно БД, лучше всего in memory. Для MVP можете хранить и в PG, просто в отдельной схеме / таблице. И потом переход на in memory будет не сложным
можно использовать redis, там быстрые счетчики можно сделать. можно сделать in-memory шардированные счетчики тоже с синхронизацией через какую-то бд
Отлично! Я в PG еще и Anchor Modeling использую для проекта. Так что у меня абсолютно все сущности хранятся в отдельных табличках. Я еще все советы внимательно перечитаю и что-то хорошее точно реализую)
А что такое "невероятно велико" в конкретных числах, кстати? Потому что в реализации всех этих решений кэширования отдельно от базы как раз запросто можно накосить, и заниматься этим без реальной необходимости — это преждевременная оптимизация как она есть.
Согласен. Занимаюсь именно преждевременной оптимизацией и хорошо это осознаю. Ну на старте у меня для пользоваля 1 млн запросов это норма. Уже сейчас вручную отгружаю столько запросов. В случае с MVP это будет 1 запрос = 1 бизнес транзакция списания денежных средств. Т.е. накопительно мы получим свыше нескольких (думаю около 5) миллионов транзакций только на самом старте. И дальше эти цифры будет только увеличиваться. Поэтому у меня и возник такой большой страх о подсчете балансов пользователей из всех совершенных им запросов, для каждого из которых мы формируем для него транзакции и храним без удаления\апдейта. Более того, количество транзакций которые мы сохраняем, может варьироваться порядками. Реально порядками. Это зависит лишь от того что мы отдаем пользователю и в каких объемах ему требуется предоставляемая нами информация. Как правило это очень хорошие объемы. Суточные бывают от 500 млн запросов на пользователя из бизнес-среды.
я бы еще посмотрел на подход с батчевой обработкой. допустим у пользователя много единиц на балансе, ему дается бакет, который он исчерпывает и за него платит. он идет транзакцией и ее в бд пищите. остальное в редисе
Видел пару такие реализаций... но перед тем как ставить нужно продумать как шардировать.
> Занимаюсь именно преждевременной оптимизацией и хорошо это осознаю. Так прекратите! Premature optimization is the root of all evil. -- D.E. Knuth > мы получим свыше нескольких (думаю около 5) миллионов транзакций только на самом старте. Что для современной СУБД на современном же "железе" — просто ничто. Ну а дальше? > Как правило это очень хорошие объемы. Какие конкретно? Опираться стоит на числа, не на оценочные суждения. ;) > Суточные бывают от 500 млн запросов на пользователя из бизнес-среды. Каких именно запросов? Получения баланса? Или его изменения?
Смотрите. Объясню. Мы для пользователя ежедневно выгружаем много информации. Она разная. Но вся информация выгружается посредством обращения к API. 1 запрос - 1 ответ. Таких запрос пользователь формирует много. 100 000 это самый минимальный запрос как правило. И таких запросов он делает часто не 1 раз в день. У некоторых вообще суточные запросы по 500 млн. И на каждый такой запрос мы должны обсчитывать баланс текущий чтобы списать с этого баланса единицу стоимости для того чтобы отдать информацию по новому запросу. Вот и получается что мы распухаем в математике балансов минимум уже на 500 млн транзакций в день. Сегодня 500 обсчитали, завтра 1лярд. через 3 дня полтора лярда. И это только для одного пользователя. А мелкие? А другие еще более крупные? Я вручную эти запросы не могу выполнить. Не хватает рук. Поэтому пилю MVP который это осилит. Но руками я принимаю оплату сразу "за всё" и не веду учет, а отдаю результат, а вот в случае с API придется как-то рисовать баланс пользователя. И это можно делать только по историческим данным (пользователь пополнил баланс, совершил 100500 операций, теперь у него Х денег). Через 30 дней для 5-10 пользователей обсчитывать 300 миллиардов строк для выведения баланса это норма разве? А что тогда будет через год? Если и это для бд ок, то я где-то видимо правда совсем заблудился. Запросы к базе (те 500 млн) это запросы получения некоторых данных с большим количеством JOIN. Но этот запрос не пойдет на выполнение пока мы не поймем что у пользователя баланс положительный и больше стоимости запроса. Я, конечно, же сейчас говорю об эфимерных объемах. Эти объемы у меня уже запросили, но я их еще ни разу не выполнил, т.к. вручную это выполнить нереально. Поэтому стоит простая задача. Дать API к которому можно обращаться много раз, получать нужные данные. Каждое обращение будет записано в бд и будет история, а так же по каждой можно будет свести баланс типа дебет\кредет. Запросов много. Поверьте. Очень много. Телекоммуникационная компания просто отдыхает в сторонке.
а зачем вам копейку в копейку, вы же не банк. сделайте допущения и архитектура системы на порядок упростится
Подождите. > 500 млн транзакций в день. Т.е. каждый пользователь 500 млн раз в день меняет свой баланс, в самом деле? Или только запрашивает, всё же (что тоже как-то странно, если это не какие-то биржевые торги и т.п.)? > И это только для одного пользователя. Так сколько пользователей? > Через 30 дней для 5-10 пользователей обсчитывать 300 миллиардов строк для выведения баланса это норма разве? Нет. И делать этого не нужно, как я уже неоднократно говорил! Текущие балансы можно хранить. А Вы уверены, что это реалистичная оценка количества транзакций? > Запросы к базе (те 500 млн) это запросы получения некоторых данных с большим количеством JOIN. > Каждое обращение будет записано в бд и будет история, а так же по каждой можно будет свести баланс типа дебет\кредет. > Запросов много. Поверьте. Очень много. Нет, я запутался. :( Всё-таки считать нужно, исходя из количества пишущих и читающих транзакций (отдельно). И да, если Вам нужно надёжное хранение всего этого — "железо" нужно закладывать соответствующее, ни в какой СУБД "серебряной пули" нет, это не зависит от технологии и т.п. Если не нужно, т.е. Вы готовы терять данные (возможно, систематически) — это другое дело.
Не могу. Планирую предоставить пользователям возможность вывода средств помимо пополнения баланса и его расходования. Будет пропадать копейка - пользователь\сотрудник будут махинации проводить и я буду терять деньги. Не могу себе этого позволить. Не буду сводить балансы - буду терять деньги.
вы терять будете только в случае ошибки\падения. а так просто иногда давать в долг
Имхо это от лукавого. ORM и подобные вещи - злейший враг высоких нагрузок. Если нужна nosql база - не насилуйте postgres, возьмите тот же mongo. Ну или тот же json отлично сейчас поддерживается PG, если вам нужна sql но местами с гибкой структурой данных. А подобные надстройки над бд как вы привели, имеют пользу в очень специфичных случаях. Во всех остальных они добавят вам невероятное количество головняка и непредсказуемости в поведении.
Да, да. 500 млн раз в день пользователь меняет баланс. Именно меняет. По реалистичности оценки количества транзакций могу абсолютно точно утвержать что это еще очень маленькая оценка. Чтобы вы понимали, стоимость запроса бывает гораздо меньше цента. А вот самих запросов при этом бывает много. Сейчас руками я отгружаю реально по 100 000 запросов+. Бывает по 500к. И это очень малая часть того что я предоставляю. Пишущих транзакций в бд много. Работает куча микросервисов и через тот же апи укладывают информацию в бд. По железу понимаю. Быстренько на хадуп или сеф ускочу с получением первых серьезных денег и наймом настоящих спецов, а пока занимаюсь проектированием MVP и разглагольствованиями, как видите😊
Как только пользователи пронюхают про эти копейки - такое начнётся... Тем более, у автора куча мелких транзакций.
Да уже выбрал) Но для MVP покатит. Мне главное определиться с архитектурными решениями основных процессов и с возможностью быстрого переноса\масштабирования. Вот последний шаг и остался на этих самых учетах, балансах и т.д. Головняк выходит ужасный. Вообще думаю с 6NF нормализацией мы и на PG проживет очень очень долго в хорошем кластере сефа например.
Боже... Да сделайте уже ручное шардирование пользователей и храните их счет в памяти, если совсем сложно. Вы описываете слона в вакууме. Вам надо думать или за батчинг запросов или за переделку всей архитектуры. Или вы там опять криптобиржу строите? Так там нету таких объемов. Таких объемов нету даже у NASDAQ.
Не, не. Поставка "открытых" данных. не котировки, не крипта. Просто данные напарсенные откуда попало)
> Да, да. 500 млн раз в день пользователь меняет баланс. А пользователи как-то связаны друг с другом (если нет — стоит думать про sharding)? > Запросы к базе (те 500 млн) это запросы получения некоторых данных с большим количеством JOIN. А, всё же, к чему тогда вот это? > Вообще думаю с 6NF нормализацией мы и на PG проживет очень очень долго А мне кажется, что если речь про только одну базу — не проживёт "слабый" сервер при такой нагрузке (но это только впечатление... лучше бы Вы посчитали нагрузки, объёмы и т.п.). ;( Это если нужна надёжность, опять-таки.
Обсуждают сегодня