1. Репликация
  2. Репликация с несколькими ведущими узлами
  3. Конкурентные операции
  4. Секционирование
  5. Транзакции
  6. Слабые уровни изоляции

В суровой реальности информационных систем очень многое может пойти не так:

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

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

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

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

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

ACID

Обеспечиваемые транзакциями гарантии функциональной безопасности часто описываются известной аббревиатурой ACID (atomicity, consistency, isolation, durability — атомарность, согласованность, изоляция и сохраняемость).

Она был придумана в 1983 году Тео Хэрдером и Андреасом Ройтером в попытке создать четкую терминологию для механизмов обеспечения отказоустойчивости в базах данных.

Однако на практике реализации ACID в разных базах отличаются друг от друга. Например, как мы увидим, существуют серьезные различия в понимании термина «изоляция» (isolation).

Системы, не соответствующие критериям ACID, иногда называются BASE, что расшифровывается следующим образом: «как правило, доступна» (Basically Available), «гибкое состояние» (Soft state) и «конечная согласованность» (Eventual consistency). Это понятие еще более расплывчатое, чем ACID. Похоже, единственное разумное определение BASE — «не ACID», то есть оно может значить практически все что угодно.

Атомарность (atomicity)

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

В контексте ACID атомарность не связана с конкурентным доступом. Этот термин не описывает, что происходит, когда несколько процессов пытаются обратиться к одним и тем же данным одновременно, поскольку относится к понятию изоляции.

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

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

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

Согласованность (consistency)

Слово «согласованность» ужасно перегружено.

  • согласованность реплик и вопрос конечной согласованности, возникающий в асинхронно реплицируемых системах;
  • Согласованное хеширование — метод секционирования, используемый в некоторых системах для перебалансировки;
  • В теореме CAP слово «согласованность» используется для обозначения линеаризуемости (linearizability);
  • В контексте ACID под согласованностью понимается то, что база данных находится, с точки зрения приложения, в «хорошем состоянии».

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

Идея согласованности в смысле ACID состоит в том, что определенные утверждения относительно данных (инварианты) должны всегда оставаться справедливыми — например, в системе бухгалтерского учета кредит всегда должен сходиться с дебетом по всем счетам. Если транзакция начинается при допустимом состоянии базы данных и любые производимые во время транзакции операции записи сохраняют это свойство, то можно быть уверенными, что система всегда будет удовлетворять инвариантам.

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

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

Атомарность, изоляция и сохраняемость — свойства базы данных, в то время как согласованность (в смысле ACID) — свойство приложения.

Изоляция (isolation)

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

Изоляция в смысле ACID означает, что конкурентно выполняемые транзакции изолированы друг от друга — они не могут помешать друг другу. Классические учебники по базам данных понимают под изоляцией сериализуемость (serializability). То есть каждая транзакция выполняется так, будто она единственная во всей базе. БД гарантирует, что результат фиксации транзакций такой же, как если бы они выполнялись последовательно (serially, одна за другой), хотя в реальности они могут выполняться конкурентно.

Однако на практике сериализуемые транзакции используются редко, поскольку влекут за собой проблемы с производительностью.

Сохраняемость (durability)

Задача СУБД — предоставить надежное место для хранения данных. Сохраняемость (durability) — обязательство базы не терять записанных (успешно зафиксированных) транзакций данных, даже в случае сбоя аппаратного обеспечения или фатального сбоя самой БД.

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

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

Однообъектные и многообъектные операции

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

  • Атомарность. Если посередине последовательности операций записи происходит ошибка, то транзакцию необходимо прервать, а выполненные до того момента операции аннулировать. Другими словами, база данных ограждает вас от забот о частичных отказах, предоставляя гарантию типа «все или ничего».
  • Изоляция. Конкурентно выполняемые транзакции не должны мешать друг другу. Например, если одна транзакция выполняет несколько операций записи, то другая должна видеть или все их результаты, или никакие, но не какое-то подмножество.

Эти определения предполагают, что необходимо модифицировать несколько объектов (строк, документов или записей) одновременно. Подобные многообъектные транзакции часто оказываются нужны для обеспечения синхронизации нескольких элементов данных.

Для многообъектных транзакций необходимо знать, какие операции записи и чтения относятся к одной транзакции. В реляционных базах данных это можно узнать из TCP-соединения клиента с сервером БД: считается, что все находящееся между операторами BEGIN TRANSACTION и COMMIT конкретного соединения относится к одной транзакции.

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

Однообъектные операции записи

Атомарность и изоляция также применимы при обновлении одного объекта. Допустим, что вы записываете в базу данных JSON-документ размером 20 Кбайт.

  • При разрыве сетевого соединения после отправки первых 10 Кбайт будут ли сохранены в базе эти 10 Кбайт, синтаксический разбор которых невозможен?
  • При сбое питания посередине перезаписи базой данных предыдущего значения на диске не перемешаются ли старое и новое значения
  • Увидит ли частично обновленное значение другой клиент, читающий данный документ во время операции записи?

Эти вопросы могут оказаться весьма сложными, так что подсистемы хранения практически всегда стараются обеспечивать атомарность и изоляцию на уровне отдельных объектов (например, пар «ключ — значение») в каждом узле.

Атомарность можно обеспечить с помощью журнала для восстановления после сбоев, а изоляцию — путем блокировок отдельных объектов (благодаря которым обращаться к объекту одновременно может только один поток выполнения).

Востребованность многообъектных транзакций

Существуют сценарии использования, при которых однообъектных вставок, обновлений и удалений вполне достаточно. Однако во многих сценариях необходимо согласование операций записи в несколько различных объектов.

  • В реляционной модели данных строки в таблицах часто ссылаются на строки в других таблицах с помощью внешних ключей. Многообъектные транзакции гарантируют, что эти ссылки останутся действующими: при вставке нескольких записей, ссылающихся друг на друга, внешние ключи должны быть правильными и актуальными, иначе данные потеряют смысл.
  • В документной модели данных поля, которые должны обновляться совместно, часто находятся в одном документе, рассматриваемом как единый объект, так что при обновлении отдельного документа не нужны никакие многообъектные транзакции. Однако документоориентированные БД, в которых нет функциональности соединений, создают условия для денормализации. А обновление денормализованной информации требует обновления нескольких документов за один проход. В этом случае транзакции оказываются очень удобны для предотвращения рассогласования денормализованных данных.
  • В БД со вторичными индексами при каждом изменении значения необходимо обновлять индексы. Последние представляют собой различные объекты БД с точки зрения транзакций: например, без изоляции транзакций запись может встречаться в одном индексе и не встречаться в другом, поскольку второй индекс еще не был обновлен.

Обработка ошибок и прерывание транзакций

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

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

Источники


Комментарии в Telegram-группе!