ACID, AID или AD

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

ACID, AID или AD

Самое главное преимущество транзакций - это упрощение программной модели приложения, осуществляющего доступ к данным. Транзакции позволяют представить последовательность операций чтения и записи как одну изолированную логическую операцию. Концептуально это значит, что все операции должны быть выполнены как одна по принципу "всё или ничего". Либо все действия выполняются успешно, либо производится отмена произведённых изменений. В первом случае речь про фиксацию изменений (commit), во втором - про откат (rollback, abort).

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

В чём же заключаются гарантии сохранности? Я думаю, что многие знают акроним "ACID", тем не менее предлагаю пройтись по этой теме еще раз и посмотреть на неё под другим углом.

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

  • Atomicity (атомарность). Все операции транзакции представляют собой одну логическую операцию, которая должна быть выполнена только успешно, а в случае любой ошибки все произведенные действия должны быть отменены (abort). Мартин Клеппманн в своей знаменитой книге Designing Data-Intensive Application сетует на перегруженность термина "атомарность" и говорит, что термин "отменяемость" (abortability) был бы более точным.
  • Consistency (согласованность). Если данные находились в согласованном состоянии до выполнения транзакции, они должны остаться согласованными и после неё. Однако что считать согласованностью? Да, база данных может предоставлять некоторые механизмы обеспечения согласованности в виде ограничений (constraints), например, контроль внешних ключей, уникальности, полноты данных, их формата, размера и т.п. Однако, только приложение может определить, какие данные корректны, а какие нет, а база данных отвечает лишь за их сохранение. Кто вас остановит от записи некорректных данных в базу?! Таким образом, здесь можно говорить лишь о согласованности файлов базы данных, поскольку применяются изменения только успешных транзакций. При таком подходе я бы предложил назвать это свойство целостностью. Клеппманн также указывает, что исторически буква "C" была добавлена лишь для создания акронима, и это не то свойство, которое может гарантировать база.
  • Isolation (изолированность). Параллельно выполняемые транзакции изолированы друг от друга таким образом, что результат их выполнения должен быть идентичен тому, который получился бы при их последовательном (serial) выполнении. Однако на практике такой уровень изоляции (serializable) используется крайне редко, поскольку может оказывать негативное влияние на производительность приложения. Более того, разные базы данных по-разному реализуют это свойство, ослабляя в некоторых случаях уровень изоляции в угоду производительности.
  • Durability (долговечность). Если транзакция завершилась успешно, все произведенные изменения не должны быть забыты. В монолитных базах данных долговечность реализуется записью данных на постоянный носитель информации (HDD/SSD); в распределенных - репликацией, то есть копированием данных на несколько узлов - реплик. Прежде, чем транзакция завершится успешно, база данных должна дождаться успешной записи всех изменений на диск или окончания репликации. Даже несмотря на это, нетрудно догадаться, что некоторые вещи не зависят от базы данных и идеальной долговечности не существует. Отключение питания, нарушение температурного режима, аппаратные сбои, деградация секторов накопителя, ошибки на уровне файловой системы - всё это не редкость. Наряду с этим, использование асинхронной репликации может стать причиной потери изменений. Таким образом, никаких гарантий нет, есть только техники снижения рисков: репликация и резервное копирование.

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

Многие так привыкли к абсолюту ACID, что подобные выводы могут показаться неожиданными. Причем они справедливы для любой реализации транзакций. Разница лишь в том, что для транзакционных (реляционных) баз данных это не совсем очевидно, а для распределенных систем - это норма. Например, процесс согласования данных на основе распределенных транзакций (2PC) или повествований (Saga) изначально подразумевает отсутствие изоляции и предполагает применение контрмер, которые нивелируют этот недостаток.

Поддержка ACID на уровне базы данных, безусловно, упрощает программную модель приложения, сдвигая акцент в сторону прикладной логики. За многие годы использования транзакционных (монолитных) баз они доказали свою надежность. Используя принцип KISS, мы должны следовать этим путём, если он для нас открыт, и только в противном случае искать более сложные решения. Однако мы должны помнить, что согласованность и изоляция - это то, о чём разработчик должен заботиться не меньше, чем база данных! К сожалению, иногда вижу, что про это забывают, прикрывая неудачные архитектурные решения транзакциями и ссылаясь на незыблемость реляционных баз. Из-за этого ошибки по причине нарушения согласованности и изоляции в монолитных системах встречаются не реже, чем в распределенных.

Подводя итоги, можно сказать, что ACID-транзакции - это лишь один из инструментов, используемый в архитектурном решении. Где-то он проще, поскольку поддерживается используемой базой данных, а где-то сложней, как например, в распределенных системах или при гетерогенном хранении данных. В последнем случае для упрощения прикладного кода мы используем специализированные фреймворки (для управления распределенными транзакциями или повествованиями).

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

P.s. Надеюсь, что было интересно и не скучно. ;-) А у вас есть интересные истории, когда даже транзакции не спасали от страшного? Или успешные истории, как и где можно жить без ACID?

P.p.s. Если вам интересна данная тематика, присоединяйтесь к моей новостной ленте в Telegram или здесь. Буду рад поделиться опытом. ;-)

1
2 комментария