Комментарии 59
Вы правда честно и искренне предлагаете блокировать запись в БД на время обращения ко внешнему API?
Скажите, пожалуйста, а что вы будете делать в сценарии «вы дернули внешний API, все успешно, но коммит в БД не прошел»?
Скажите, пожалуйста, а что вы будете делать в сценарии «вы дернули внешний API, все успешно, но коммит в БД не прошел»?
в ситуации выше хотелось лишь понять, знает ли кандидат как пользоваться одной из фич innodb а именно построчным локом.
для звонка во внешний апи вариантов тут несколько:
-до звонка уменьшить баланс и закоммититься
-без построчного лока попытаться захватить запись способом вроде " update requests set status=%pid where status=0 and id=123" и продолжить
для звонка во внешний апи вариантов тут несколько:
-до звонка уменьшить баланс и закоммититься
-без построчного лока попытаться захватить запись способом вроде " update requests set status=%pid where status=0 and id=123" и продолжить
в ситуации выше хотелось лишь понять, знает ли кандидат как пользоваться одной из фич innodb а именно построчным локом.
Так у вас задача для этого не предназначена. Возможно, люди не знают про построчный лок — а возможно, считают, что внешний API в транзакцию включать нельзя.
без построчного лока попытаться захватить запись способом вроде " update requests set status=%pid where status=0 and id=123" и продолжить
А это — не построчный лок?
коммит не пройдет из-за deadlock-a? если нужные строки залочить, то всё должно пройти.
Плакать и смеяться, пряники жевать, очевидно же :)
По мне так логичней сначала апдейтить баланс, а потом уже дёргать внешнее АПИ. Если внешнее АПИ зафейлится, никто не мешает нам заново проапдейтить, вернув баланс обратно.
По заголовку статьи рассчитывал не большее. «Транзакции в mysql», а тут только один маленький частный случай: select for update.
Я бы вообще с GET_LOCK/RELEASE_LOCK делал, и работало бы независимо от движка таблицы, и таким образом логику блокирования можно вынести в приложение.
Я бы вообще с GET_LOCK/RELEASE_LOCK делал, и работало бы независимо от движка таблицы, и таким образом логику блокирования можно вынести в приложение.
Вы дополнили название статьи.
Хорошим тоном было бы написать небольшой upd. в конце статьи, если что-то было изменено (не считая опечаток), а то некоторые комментарии, как мой выше, становятся идиотскими.
Хорошим тоном было бы написать небольшой upd. в конце статьи, если что-то было изменено (не считая опечаток), а то некоторые комментарии, как мой выше, становятся идиотскими.
Я думаю, что если внешний апи платный, то должно точно вестись логирование использования такого апи, а следовательно можно применить следующую схему:
таблицы
-user поле баланс
-queue со статусом (1-запланировано, деньги не сняты, 2 деньги сняты, 0 задача выполнена), и результатом запроса к внешнему апи
при нажатии кнопки
-INSERT INTO queue SET status = 1,… (autocommit)
-BEGIN; UPDATE queue SET status = 2 WHERE id = xx; UPDATE user SET balance = balance — yyy WHERE id = zzz;COMMIT;
-Запрос к внешнему API,
если все хорошо то
UPDATE queue SET status = 0, result = 'aaaa' WHERE id = xx;
если нет то
BEGIN; UPDATE queue SET status = 1 WHERE id = xx; UPDATE user SET balance = balance + yyy WHERE id = zzz;COMMIT;
При таком раскладе вы будете знать на каком этапе запрос. Если что то упадёт то:
1.Будет стоять в очереди невыполненная задача, баланс не тронут
2.Баланс списан, задача на выполнении.
3.Баланс списан, задача исполнена.
Если присутствует ситуация баланс списан, задача не исполнена в течении нужного времени, админ должен это увидеть.
таблицы
-user поле баланс
-queue со статусом (1-запланировано, деньги не сняты, 2 деньги сняты, 0 задача выполнена), и результатом запроса к внешнему апи
при нажатии кнопки
-INSERT INTO queue SET status = 1,… (autocommit)
-BEGIN; UPDATE queue SET status = 2 WHERE id = xx; UPDATE user SET balance = balance — yyy WHERE id = zzz;COMMIT;
-Запрос к внешнему API,
если все хорошо то
UPDATE queue SET status = 0, result = 'aaaa' WHERE id = xx;
если нет то
BEGIN; UPDATE queue SET status = 1 WHERE id = xx; UPDATE user SET balance = balance + yyy WHERE id = zzz;COMMIT;
При таком раскладе вы будете знать на каком этапе запрос. Если что то упадёт то:
1.Будет стоять в очереди невыполненная задача, баланс не тронут
2.Баланс списан, задача на выполнении.
3.Баланс списан, задача исполнена.
Если присутствует ситуация баланс списан, задача не исполнена в течении нужного времени, админ должен это увидеть.
Если в задаче появляется внешние api — то это задача про two phase commit
Применительно к django это можно реализовать через отдельную таблицу с операциями и их статусами. Ну а в конце, да, обновление баланса и пометку операции как пройденной можно обернуть в короткую транзакцию с блокировкой баланса.
Применительно к django это можно реализовать через отдельную таблицу с операциями и их статусами. Ну а в конце, да, обновление баланса и пометку операции как пройденной можно обернуть в короткую транзакцию с блокировкой баланса.
— достаем из базы баланс пользователя;
— если баланса достаточно, то дергаем API;
— если всё хорошо, то списываем с баланса сумму за услугу, делаем UPDATE, коммитим, иначе откатываемся;
— отвечаем пользователю
Если это обернуть в транзакцию, всё должно отработать правильно.
— если баланса достаточно, то дергаем API;
— если всё хорошо, то списываем с баланса сумму за услугу, делаем UPDATE, коммитим, иначе откатываемся;
— отвечаем пользователю
Если это обернуть в транзакцию, всё должно отработать правильно.
только если включен dev.mysql.com/doc/refman/5.0/en/set-transaction.html#isolevel_serializable
«Правильно» — это как? Представьте, что у вас баланса достаточно на одну операцию, а за время обращения к API пришел еще один запрос. Баланс не уменьшен? Не уменьшен, запустится вторая операция. На коммите все плохо? Плохо, откатываем транзакцию, но API-то уже был вызван дважды.
юзер жмет на кнопку «activate» — мы лочим баланс, холдим деньги и переведим запрос в статус TO_PROCESS
далее 2 таска:
-лок и смена статуса на что-то вроде PROCESSING + коммит + api call
-получение колбека либо опрос сервиса и перевод запроса в конечное состояние COMPLETE
но всё равноу нас должен быть либо способ узнать статус на удаленном сервисе либо получить обратный звонок от него. Иначе на любом этапе можно потерять данные, слепо дернуть api без обратной связи безопасно не получится
далее 2 таска:
-лок и смена статуса на что-то вроде PROCESSING + коммит + api call
-получение колбека либо опрос сервиса и перевод запроса в конечное состояние COMPLETE
но всё равноу нас должен быть либо способ узнать статус на удаленном сервисе либо получить обратный звонок от него. Иначе на любом этапе можно потерять данные, слепо дернуть api без обратной связи безопасно не получится
[Кличко моде он]
Не только лишь все понимают FOR UPDATE, мало кто может это делать.
[Кличко моде офф]
Не только лишь все понимают FOR UPDATE, мало кто может это делать.
[Кличко моде офф]
Очень опасная команда. Я за такую (ну хорошо, за подобные) бил по руками и прочим частям тела до понимания. Почему? Потому что в любом развитом сайте это мгновенно приводит к огромному количеству локов, роняющих производительность в ноль.
Например.
Скажем, у нас есть веб-сайт, где у пользователей есть личный кабинет, в котором показывается баланс. Любой показ баланса вызывает лок. Поднимите руку те, кто считает, что лок обходится бескровно? Делать две функции показа баланса?
Есть сайты, где функционал завязан на баланс. Что будет с сайтом, если вдруг юзер откроет два окна, в одном будет за что-то платить, а в другом просто шариться по сайту? Кешировать «баланс»?
Есть сайты с развитым апи, к которым цепляются мобильные клиенты. Лазит себе юзер по сайту, а тот внезапно начинает тормозить из-за того, что мобильный клиент начал какую-то операцию, связанную с балансом?
Так что ТС выбрал очень плохой вариант и соискатели оказались просто опытней :)
Весь мой опыт говорит о том, что если в базе появились транзакции, локи или (упаси меня) хранимые процедуры — значит огромный геморрой не просто близок, а уже пришел.
Например.
Скажем, у нас есть веб-сайт, где у пользователей есть личный кабинет, в котором показывается баланс. Любой показ баланса вызывает лок. Поднимите руку те, кто считает, что лок обходится бескровно? Делать две функции показа баланса?
Есть сайты, где функционал завязан на баланс. Что будет с сайтом, если вдруг юзер откроет два окна, в одном будет за что-то платить, а в другом просто шариться по сайту? Кешировать «баланс»?
Есть сайты с развитым апи, к которым цепляются мобильные клиенты. Лазит себе юзер по сайту, а тот внезапно начинает тормозить из-за того, что мобильный клиент начал какую-то операцию, связанную с балансом?
Так что ТС выбрал очень плохой вариант и соискатели оказались просто опытней :)
Весь мой опыт говорит о том, что если в базе появились транзакции, локи или (упаси меня) хранимые процедуры — значит огромный геморрой не просто близок, а уже пришел.
Во-первых создавать блокировку только ради чтения баланса нет необходимости.
Во-вторых, вы путаете сайт и standalone клиент. Если даже мы сделали select for update для чтения баланса, мы отдали страницу пользователю, запрос завершен, транзакция завершена, блокировка снята. Вне зависимости от того сколько окон у пользователя открыто.
Во-вторых, вы путаете сайт и standalone клиент. Если даже мы сделали select for update для чтения баланса, мы отдали страницу пользователю, запрос завершен, транзакция завершена, блокировка снята. Вне зависимости от того сколько окон у пользователя открыто.
Пока делается select for update, второй такой же select будет висеть, нет?
второй select for update будет висеть да, но это миллисекунды пока страница не уйдет пользователю.
По определению внешний сервис работает дольше нескольких миллисекунд. И если он первый успел заблочить, то запрос легко может провисеть и секунды.
Плюс такие запросы в случае многосерверной конфигурации типа «мастер-мастер» или «мастер-куча слейвов» очень сильно дают по производительности. Пока они локи засинхронизируют, а потом локи снимут…
В общем, надо искать другие задачи/примеры, когда реально без таких запросов не обойтись. Но если честно, так сразу в голову ничего не приходит.
Плюс такие запросы в случае многосерверной конфигурации типа «мастер-мастер» или «мастер-куча слейвов» очень сильно дают по производительности. Пока они локи засинхронизируют, а потом локи снимут…
В общем, надо искать другие задачи/примеры, когда реально без таких запросов не обойтись. Но если честно, так сразу в голову ничего не приходит.
Я про случай со страницой просто показывающей баланс говорю.
По определению внешний сервис работает дольше нескольких миллисекунд.
что это за определение такое? брокеры чикагской биржи тоже внешние, но там на 3 порядка меньше время ответа.
Плюс такие запросы в случае многосерверной конфигурации типа «мастер-мастер» или «мастер-куча слейвов» очень сильно дают по производительности. Пока они локи засинхронизируют, а потом локи снимут…
а такую репликацию вряд ли кто то будет делать, если только не псих
что это за определение такое? брокеры чикагской биржи тоже внешние, но там на 3 порядка меньше время ответа.
Плюс такие запросы в случае многосерверной конфигурации типа «мастер-мастер» или «мастер-куча слейвов» очень сильно дают по производительности. Пока они локи засинхронизируют, а потом локи снимут…
а такую репликацию вряд ли кто то будет делать, если только не псих
Я с чикагскими биржами не работал, я работаю с обычными, широко распространенными сервисами :) Скажем, вызов api от apple для проверки покупки легко может занять пару-тройку секунд (а может и сотней миллисекунд обойтись, да и то большая часть на сетевые издержки уходит).
И такие реплики делают гораздо чаще, чем Вам кажется. Например так часто делают для случаев, когда пишут в базу редко, зато читают очень и очень часто и помногу.
И такие реплики делают гораздо чаще, чем Вам кажется. Например так часто делают для случаев, когда пишут в базу редко, зато читают очень и очень часто и помногу.
Сама база данных при этом работает очень короткое время. http сервер принял запрос — отдал его приложению. Приложение стартануло транзацию. Полочила необходимое, выполнило действие. commit. После это результат отдается http серверу. А сколько оно уже отправляется клиенту, какие там сетевые издержки базе данных все равно. Она идет дальше и выполняет запросы. При этом без лочек никто финансовые инструменты не делает, это я вам как разработчик биллинговых систем заявляю. Так что вам бы немного поразбираться в устройстве серверных приложений работающих с деньгами, а не делать голословные заявления.
Вообще-то изначально была речь про внешний апи, который вместе с фронтэндом обращается к одним и тем же данным. И тогда «полочила необходимое» легко может превратиться в паузу для отдачи страницы.
про «без лочек никто не делает» — бывает, если вы и есть никто. У меня есть совершенно противоположный опыт во вполне успешных проектах, которые гоняют миллионы транзакций без локов, хранимых процедур и прочего.
про «без лочек никто не делает» — бывает, если вы и есть никто. У меня есть совершенно противоположный опыт во вполне успешных проектах, которые гоняют миллионы транзакций без локов, хранимых процедур и прочего.
да как же можно сравнивать задачи по неблокирующей раздаче статики и stale кешей с процессингом? обычно юзер бд от фронтенда читает реплики процессинговых данных и уж точно не имеет прав на запись в таблицы (базы) процессинга. это разные объекты и по сущности и по ответсвенности.
Мы почему-то уперлись в процессинг. Думаю, стоит свернуть тред. Ибо по основным тезисам ни у кого нет возражений (ну или мне так кажется). А как оно применяется и где — тут можно долго меряться
По вашему посту видно, что работа с сервисами где учитывают деньги вас обошла стороной. Может, оно и к лучшему. Не поверите, но не просто так сделали построчную блокировку и транзакции, не только для того, чтобы были deadlock-и и второе окно тормозило. По дефолту на session file тоже ставится лок и вторая страница не открывается. Это так, на всякий случай.
Может в oracle напишем, что второе окно тормозит?
По-моему, блокировка не лучший способ решения проблемы. Тут скорее всего надо делать просто транзакцию, которую потом можно откатить в случае чего. Но в этом случае АПИ тоже должен поддерживать некий откат, т.к. при конфликтующих транзакциях коммит не пройдёт, надо сделать откат в АПИ, и далее повторить попытку. Поправьте плз, если это решение плохо.
Помимо предложенного метода в статье предложу более общий вариант — оптимистическая блокировка:
Данный запрос вернет 0, если ожидаемый баланс не совпадет с текущим — т.е. кто-то нас опередил и наши данные о балансе больше не актуальны.
UPDATE users
SET balance = ${newValue}
WHERE balance=${expectedOldValue};
Данный запрос вернет 0, если ожидаемый баланс не совпадет с текущим — т.е. кто-то нас опередил и наши данные о балансе больше не актуальны.
вообще учет денег так нигде не делается (из бух учета по-моему). всегда на каждое изменение баланса — одна запись в бд. баланс — это сальдо.
Я лишь хотел напомнить про подход как таковой.
Если нужна оптимистическая блокировка в ситуации INSERT-а строки вместо UPDATE существующей, то можно использовать внешнюю таблицу для лока:
«Добавить новую строку баланса, проставив новую ревизию в глобальную таблицу ревизий, если ожидаемая старая ревизия такая-то»
Надеюсь идея понятна.
Если нужна оптимистическая блокировка в ситуации INSERT-а строки вместо UPDATE существующей, то можно использовать внешнюю таблицу для лока:
«Добавить новую строку баланса, проставив новую ревизию в глобальную таблицу ревизий, если ожидаемая старая ревизия такая-то»
Надеюсь идея понятна.
вот в статье я о том и пытался написать, что как бы там ни было, все пробуют придумать свой вариант блокировки (в комментах уже штук 5 наберется). я не против — варианты могут быть вполне рабочими, но есть же встроенные инструменты.
бывают неоткатываемые сервисы) совсем неоткатываемые.
На собеседованиях обычно спрашивают то, о чем сами узнали недавно :)
Или то, с чем недавно столкнулись и решили.
Вы правда честно и искренне считаете, что если на собеседовании python программиста спрашивают " что такое generator comprehension?" то это означает, что либо сами недавно узнали, либо недавно столкнулись и решили?)
также можно спросить «как сделать гугл» или «почему по индексу быстро ищется» или «чем плох питон».
или тут есть существенная разница?)
также можно спросить «как сделать гугл» или «почему по индексу быстро ищется» или «чем плох питон».
или тут есть существенная разница?)
Нет, не означает. Но тем не менее, когда я на собеседовании перехожу от тривиальных вопросов к нетривиальным, я неизбежно спрашиваю что-то, с чем моя команда недавно сталкивалась (потому что это означает, что задача (а) достаточно сложная (б) решаемая (ц) сравнительно актуальная по технологиям).
или же всё таки интересует опыт человека?
заметьте, что это не вопрос из разряда «как сделать чтоб в javascript plus(4)(5) вернула 9?»
заметьте, что это не вопрос из разряда «как сделать чтоб в javascript plus(4)(5) вернула 9?»
Что интересует и что спрашивают — это две разные вещи.
лучше всегда в лоб — что интересно, то и спрашиваешь, а то начинают сопли жевать, то хихи то хаха, а по делу ноль по вдоль.
НЛО прилетело и опубликовало эту надпись здесь
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Как оказалось, знают все, а понимают не все. Транзакции в mysql и SELECT FOR UPDATE