В этой статье для обучения архитекторов, дата-инженеров и аналитиков данных рассмотрим, как поддерживаются транзакции в Apache HBase и почему к ACID-свойствам также добавляется характеристика видимости обновлений. Насколько атомарны и консистентны мутации данных внутри строки HBase, почему сканирование не полностью согласовано и как разрешить устаревшие чтения или путешествия во времени в этой NoSQL-СУБД.
Еще раз про требования к транзакциям: свойства ACID и видимость обновлений
Apache HBase – это NoSQL-СУБД с открытым исходным кодом на языке Java категории «семейство столбцов» (wide-column store) и представляет собой колоночно-ориентированное, мультиверсионное хранилище типа «ключ-значение» (key-value). Она работает поверх HDFS и обеспечивает возможности Google’вской BigTable для Hadoop, реализуя отказоустойчивый способ хранения больших объёмов разреженных данных
Apache HBase не поддерживает все свойства ACID-транзакций, однако гарантирует определенные специфические свойства группы логически объединённых последовательных операций по работе с данными, которая либо обрабатывается целиком либо отменяется полностью. Распределённые транзакции выполняются несколькими системами и требуют более сложной логики, например, двухфазный протокол фиксации (two-phase commit). Ключевые требования к транзакционной системе укладываются в акроним ACID, который означает следующее:
- Atomicity (Атомарность) — операция является атомарной, если она либо завершается полностью, либо не завершается вообще;
- Consistency (Согласованность) — все действия вызывают переход таблицы из одного допустимого состояния непосредственно в другое, гарантируя, что строка не исчезнет во время обновления;
- Isolation (Изолированность) — операция является изолированной, если она завершается независимо от любой другой параллельной транзакции;
- Durability (Надежность) — любое обновление, сообщающее клиенту об успешном выполнении, не будет потеряно.
Дополнительным требованием к транзакциям является Visibility (Видимость) — обновление считается видимым, если любое последующее чтение увидит его как зафиксированное.
Рассмотрим, каким образом Apache HBase реализует эти требования к транзакциям, в рамках API чтения (get, scan), записи (put, batch put, delete) и комбинированных операций (read-modify-write), таких как incrementColumnValue и checkAndPut. Напомним, в отличие от реляционных СУБД, HBase поддерживает только 4 основные действия по обработке данных, которые отличаются от классических SQL-запросов:
- Put – добавить новую или обновить существующую запись;
- Get– получить данные по определенному первичному ключу. Можно указать семейство столбцов, откуда будет считана информация и количество версий, которые требуется прочитать.
- Scan– поочередное чтение записей, начиная с указанной. Можно указать запись, до которой следует читать или количество записей, которые необходимо считать. Также в параметрах операции отмечается семейство столбцов, откуда будет производиться чтение и максимальное количество версий для каждой записи.
- Delete– пометить определенную версию к удалению. Физического удаления при этом не произойдет, оно будет отложено до следующего полного сжатия.
Не претендуя на полное соответствие стандарту ANSI SQL, Apache HBase по-своему реализует идею регулярных выражений с использованием фильтров в операциях Get и Scan, о чем мы рассказываем здесь.
ACID в Apache HBase
Все мутации данных в HBase атомарны внутри строки, даже если мутация пересекает несколько семейств столбцов в строке. Любой PUT-запрос либо полностью выполняется, либо полностью отменяется. При этом команды Put будут либо полностью успешными, либо полностью неудачными, если они отправлены на региональный сервер (RegionServer). Если используется буфер записи, сообщения, отправленные командой Put, не будут отправлены до тех пор, пока буфер записи не будет заполнен или явно не очищен. Если для какой-то операции истекло время ожидания, могла быть выполнена успешно, а могла и завершиться ошибкой. Тем не менее, это не будет частично успешным или неудачным.
API, изменяющие несколько строк, НЕ будут атомарными по всем строкам. Например, множественное преобразование может изменить некоторые, но не абсолютно все строки. В таких случаях эти API-интерфейсы будут возвращать список кодов успеха, каждый из которых может быть выполнен успешно, неудачно или по истечении времени ожидания.
API checkAndPut происходит атомарно, как типичная операция compareAndSet (CAS), встречающаяся во многих аппаратных архитектурах. Порядок мутаций происходит в четко определенном порядке для каждой строки без чередования. Это не соблюдается для многострочных пакетных мутаций.
Что касается согласованности и изоляции, все строки, возвращаемые через любой API доступа, будут состоять из полной строки, которая существовала в какой-то момент в истории таблицы. Это верно для семейств столбцов: состояние строки будет двигаться вперед только по истории ее изменений. Сканирование не является согласованным представлением таблицы и не демонстрирует изоляцию моментальных снимков. Скорее, сканы обладают следующими свойствами, которые напоминают уровень изоляции под названием «фиксированное чтение» (read committed) в реляционных СУБД:
- Любая строка, возвращенная сканированием, будет согласованным представлением, т.е. эта версия полной строки существовала в какой-то момент времени. Согласованное представление не гарантируется при сканировании внутри строки, т. е. при извлечении части строки в одном RPC-вызове с последующим возвратом к выборке другой части строки в последующем запросе. Сканирование внутри строки происходит, если установлено ограничение на количество возвращаемых значений для команды Scan#next.
- Сканирование всегда отражает представление данных, по крайней мере, такое же новое, как и начало сканирования. Это удовлетворяет гарантиям видимости: например, если клиент А записывает данные X, а затем связывается по стороннему каналу с клиентом Б, любые сканирования, запущенные клиентом Б, будут содержать данные, по крайней мере, такие же новые, как и Х.
- Сканирование должно отражать все мутации, совершенные до вызова команды SCAN, и может отражать некоторые мутации, совершенные после этого.
- Сканирование должно включать все данные, записанные до сканирования, кроме случаев, когда данные впоследствии мутируют, и в этом случае они могут отражать мутацию.
Все перечисленные гарантии согласованности операции SCAN относятся к времени фиксации транзакции (transaction commit time), а не к полю отметка времени (timestamp) каждой ячейки. Другими словами, сканер, запущенный в момент времени t, может видеть изменения со значением временной метки больше, чем t, если эти изменения были зафиксированы с будущей временной меткой (forward dated) до создания сканера.
В отношении видимости следует отметить, что она реализуется так: когда клиент получает успешный ответ для какой-либо мутации, она сразу видна как этому клиенту, так и любому другому, с которым он позже связывается через побочные каналы. Строка не может перемещаться во времени: если серия мутаций перемещает строку последовательно через ряд состояний, любая последовательность одновременных операций чтения вернет часть последовательности этих состояний. Например, если ячейки строки изменяются с помощью API incrementColumnValue, клиент не видит уменьшение значения какой-либо ячейки. Это не зависит от того, какой API чтения используется для обратного чтения мутации. Любая версия ячейки, которая была возвращена в операцию чтения, гарантированно будет надежно сохранена.
С точки зрения надежности в HBase все отлично: все видимые данные являются надежными и долговечными. Чтение никогда не вернет данные, которые не были сохранены на диске. В контексте долговременно на диске подразумевается вызов метода hflush() в логе транзакций. На самом деле это не подразумевает вызова fsync() на магнитных носителях, а предполагает то, что данные были записаны в кэш ОС на всех репликах лога. В случае полного отключения питания центра обработки данных возможно, что правки не будут действительно долговечными. При этом любая операция, возвращающая успешный результат без выбрасывания исключений, будет устойчивой. А операция, которая возвращает код сбоя, не будет устойчивой с учетом вышеприведенных гарантий атомарности.
В заключение отметим пару возможностей настройки отмеченных свойств транзакций в Apache HBase. Если возможно пожертвовать некоторыми гарантиями производительности, HBase позволяет настроить видимость для каждого чтения так, чтобы разрешить устаревшие чтения или путешествия во времени. А надежность может быть настроена только на периодический сброс данных на диск. Про компоненты и механизмы обеспечения отказоустойчивости этой NoSQL-СУБД читайте в нашей новой статье. Почему такие компромиссы насчет ACID-транзакций не устроили инженеров Pinterest и они реализовали проект миграции с HBase на TiDB, вы узнаете здесь.
Код курса
NOSQL
Ближайшая дата курса
Продолжительность
ак.часов
Стоимость обучения
0 руб.
Освойте администрирование и эксплуатацию Apache HBase и других NoSQL-решений для аналитики больших данных на специализированных курсах в нашем лицензированном учебном центре обучения и повышения квалификации для разработчиков, менеджеров, архитекторов, инженеров, администраторов, Data Scientist’ов и аналитиков Big Data в Москве:
Источники