Найти тему
OtezVikentiy.tech

ACID принципы - что это и зачем оно надо

Оглавление

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

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

Atomicy - атомарность

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

Давайте рассмотрим проблематику на примере из жизни. Представим, что у нас есть платежная система. Хуан из Гватемалы делает денежный перевод своему другу Пабло в Манаус. Что происходит на уровне платежной системы? Со счета Хуана списывается N песо, а на счет Пабло должно поступить N песо. Для этого выполняется 2 update запроса в БД.

Предположим, что по зелёному сценарию оба запроса проходят без ошибок и всё хорошо - в этом сценрии у нас совсем все хорошо и проблем нет.

А вот дальше начинаются приключения. Конкретно в этом случае есть 2 сценария.

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

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

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

Таким образом гарантируется Атомарность данных.

Consistency - согласованность

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

Предположим, что у нас в платежной системе есть клиенты, у них есть расчетные счета и карточки. У каждого клиента может быть N расчетных счетов, а у каждого расчетного счета может быть N карточек. Карточки не могут существовать без привязки к расчетному счету. А расчетные счета не могут существовать без клиента.

Предположим, что у нас был клиент у которого было 10 счетов и 15 карт. Он решил, что больше не будет пользоваться услугами нашей компании и решил закрыть все счета и карты. Пришёл, написал заявление и думал, что может спать спокойно. Но, так как у нас в бизнес-логике нет ни транзакций ни в БД нет foreign key - то наш менеджер удалил только запись в БД в таблице clients, а остальные записи в БД так и остались висеть - то есть 10 счетов и 15 карт.

Таким образом согласованность БД была нарушена. И в конце месяца бывший клиент получил счет за обслуживание счетов и карт от которых он юридически уже отписался. На лицо репутационные потери компании.

В данном случае необходимо было, либо использовать foreign key, чтобы БД на своем уровне знала, что все эти записи связаны между собой. Либо реализовывать эту логику в транзакциях на уровне приложения, в случае, если таблицы нагруженные.

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

Isolation - изолированность

До этого мы рассматривали примеры в вакууме на единичных последовательных запросах. Но в реальных условиях довольно редко происходит так, что выполняется всего 1-2 последовательных запроса в условный час. Обычно происходит так, что к одной и той же записи в БД идут сотни запросов одновременно. Например к расчетному счету юридического лица могут приходить до нескольких сотен запросов в секунду. В этом случае транзакции запускаются в параллель для ускорения работы системы в целом. А иначе было бы примерно так, что несколько бухгалтеров условной компании сидели бы и им бы постоянно высвечивалась надпись "Вы хотите оплатить счет? Ожидайте! Вы 5 в очереди на оплату счета среди других бухгалтеров. Но возможно еще директор вклинится без очереди...".

Но у параллельных запросов могут быть свои "приколы".

Потерянная запись

-2

Например у нас есть какой-то счет на котором изначально лежало 500 рублей. Приходит первая транзакция и начинает выполнять списание 300 рублей. В моменте получает остаток 200 рублей. В этот же момент, еще до завершения первой, приходит вторая транзакция на пополнение на 300 рублей и, так как первая транзакция еще на закрыта, то на выходе получает 500 + 300 = 800 рублей. Первая транзакция закрывается и следом за ней закрывается вторая транзакция. Итог - на счету мы получаем 800 рублей вместо 500. Так и выглядит эффект потерянной записи.

Грязное чтение

-3

Есть расчетный счет на котором лежит 500 рублей. Приходит операция на списание 300 рублей и в момент, пока транзакция еще не закрыта, приходит запрос на чтение. Так как транзакция еще не закрыта - то 500 - 300 = 200 возвращается в ответе на запрос на чтение. Но потом случается фатал запроса на запись и происходит откат транзакции. То есть фактически на расчетном счету остается 500 рублей. Но человек, который запрашивал остаток по расчетному счету - увидит другую сумму. Это и называется грязное чтение.

Повторимое чтение

-4

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

Фантомное чтение

-5

Разница между фантомным чтением и повторимым чтением лишь в том, что в повторимом чтении меняется только контент данных, но не их количество, а в фантомном чтении может меняться так же и их количество.

Как бороться

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

Изоляция транзакция бывает следующих основных видов например для mysql:

READ UNCOMMITTED - самая слабая изоляция. Данные будут доступны для чтения сразу после INSERT, еще до COMMIT.

READ COMMITTED - в данном случае данные можно будет прочитать только после COMMIT.

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

SERIALIZABLE - на данном уровне изоляции mysql полностью блокирует каждую строку над которой производится любое действие.

Durability - надёжность

Данный принцип гласит, что в случае, если транзакция уже завершена успешно и пользователь получил подтверждение об этом, то он может быть на 100% уверен в том, что ни сбой, ни другие фаталы, ни что бы то ни было еще уже не могут повлиять на проведенную транзакцию. Она проведена и точка.