Библиотека сайта rus-linux.net
Архитектура приложений с открытым исходным кодом. Том 1. Глава 4. Berkeley DB
Оригинал: "Berkeley DB"Авторы: Margo Seltzer and Keith Bostic
Дата публикации: 2012 г.
Перевод: Н.Ромоданов
Дата перевода: октябрь 2012 г.
4.8. Менеджер журнала: Log
Менеджер журнала предоставляет абстрактную модель структурированного файла, позволяющего только добавлять записи. Как и в других модулях, мы намеревались разработать универсальные средства записи в журнал, впрочем, подсистема ведения журнала, вероятно, является, как минимум, тем модулем, где мы добились успеха.
Одиннадцатый урок конструирования
Когда вы обнаруживаете проблемы в архитектуре и не хотите исправлять их «прямо сейчас», а склонны просто их пропустить, помните, что закусанные утками, вы умрете точно также, как если бы вас затоптали слоны. Для улучшения структуру программы без колебаний целиком меняйте целые фреймворки, а когда делаете изменения, не делайте частичные изменения в надежде на то, что позже вы приведете все в порядок — делайте все, а затем двигайтесь дальше. Часто повторяют: "если у вас нет времени сделать это прямо сейчас, у вас не найдется времени сделать это позже". И когда вы меняете фреймворки, пишите схемы тестирования.
Концептуально журнал сравнительно прост: он получает байтовые строки произвольной структуры и записывает их последовательно в файл, присваивая каждому уникальный идентификатор, называемый порядковым номером в журнале (log sequence number - LSN). Кроме того, журнал должен обеспечивать эффективный обход в прямом и обратном направлении и поиск по LSN. Есть два сложных момента: во-первых, журнал должен гарантировать, что он не будет испорчен после любого возможного отказа (что означает, что он содержит непрерывную последовательность неповрежденный журнальных записей), во-вторых, поскольку при подтверждении транзакций журнальные записи должны записываться в постоянное хранилище, производительность журнала является ,как правило, является именно тем, что ограничивает производительность любого приложения, использующего транзакции.
Поскольку журнал является структурой, в которую можно только добавлять записи, он может расти неограниченно. Мы реализуем журнал как совокупность последовательно пронумерованных файлов, так что место для журнала могут восстановить простым удалением старых журнальных файлов. Учитывая, что для журнала используется многофайловое решение, мы формируем номера LSN в виде пары, указывающей номер файла и смещение в файле. Таким образом, для заданного номера LSN журнальный менеджер тривиальным образом находит запись: он обращается по указанному смещению в заданном файле и возвращает запись, находящуюся в этом месте. Но как журнальный менеджер знает, сколько байтов вернуть из этого места?
4.8.1. Формат журнальных записей
Для того, чтобы для заданного номера LSN журнальный менеджер мог определить размер записи, которую он возвращает, в журнале с каждой записью должны храниться метаданные. Как минимум, нужно знать длину записи. Мы предваряем каждую журнальную запись заголовком записи, в котором указывается длина записи, смещение от предыдущей записи (для облегчения обратного обхода) и контрольная сумма журнальной записи (для идентификации разрушения журнала и определения конца журнального файла). Этих метаданных журнальному менеджеру достаточно для поддержки последовательно записываемых журнальных записей, но недостаточно для того, чтобы выполнить действительное восстановление; эти функциональные возможности закодированы в содержимом журнальных записей и определены тем, как Berkeley DB использует эти журнальные записи.
Berkeley DB использует журнальный менеджер для того, чтобы записывать образы данных перед обновлением элементов данных в базе данных и после их обновления [HR83]. В этих журнальных записях достаточно информации для того, чтобы либо повторить (действие redo), либо отменить (действие undo) операцию в базе данных. Berkeley DB использует журнал для отмены транзакции (то есть, отменяя все результаты выполнения транзакции при отмене транзакции) и для восстановления после сбоя приложения или системы.
В добавок, к интерфейсам API, предназначенным для чтения и
сохранения журнальных записей, в журнальном менеджере предоставляется
интерфейс API, позволяющий принудительно выгружать журнальные записи на
диск (DB_ENV->log_flush
). Это позволяет в Berkeley DB реализовать упреждающую запись в журнал – прежде, чем некоторая страница будет удалена из Mpool, Berkeley DB проверит номер LSN на странице и попросит журнальный менеджер обеспечить, чтобы указанный номер LSN был в постоянном хранилище. Только после этого Mpool записывает страницу на диск.
Двенадцатый урок конструирования
В Mpool и Log для того, чтобы упростить упреждающую запись в журнал, используются внутренние методы-обработчики и в некоторых ситуациях объявление метода по размеру больше, чем исполняемый код, поскольку код чаще всего лишь сравнивает два целых значения и больше ничего не делает. Зачем утруждать себя такими незначительными методами, только для возможности выделения слоев? Потому, что если ваш код не настолько объектно-ориентированный, чтобы вызывать зубную боль, то он не является достаточно объектно-ориентированным. В каждом куске кода нужно делать небольшую часть работы и должен быть более высокий уровень, на котором программистам будет предложено создавать функции из меньших фрагментов, и так далее. Если есть что-нибудь, что мы узнали о разработке программ в последние несколько десятилетий, это то, что наши возможности создавать и поддерживать значительные фрагменты программ достаточно хрупки. Создание и поддержание значительной фрагментов программного обеспечения является сложным процессом, подверженным появлению ошибок, и, как архитектор программы, вы должны сделать все, что можно, так рано, настолько это можно, и делать настолько часто, насколько это возможно, чтобы максимизировать информацию, отображаемую в структуре вашей программы.
В Berkeley DB для того, чтобы облегчить восстановление, в журнальных записях задается определенная структура. Большинство журнальных записей Berkeley DB описывают транзакционные модификации. Таким образом, большинство журнальных записей отражают модификации страниц в базе данных, выполняемых в рамках транзакций. Исходя из этого можно это описание можно взять за основу для определения того, что должно указываться в метаданных Berkeley DB, добавляемых к каждой журнальной записи: база данных, транзакция и тип записи. Поля идентификатора транзакции и типа записи находятся в каждой записи на одном и том же месте. Это позволяет системе восстановления извлекать тип записи и перенаправлять запись в соответствующий обработчик, который может интерпретировать запись и выполнить соответствующие действия. Идентификатор транзакции позволяет процессу восстановления идентифицировать транзакцию, которой принадлежит журнальная запись, с тем чтобы на любой стадии восстановления было известно, можно ли эту запись проигнорировать или ее нужно обработать.
4.8.2. Разрушая абстракцию
Есть также несколько «специальных» журнальных записей. Среди таких специальных записей записи о контрольных точках являются, пожалуй, наиболее известными. Процесс создания контрольных точек является процессом запоминания на диске состояния базы данных в определенные моменты времени. Другими словами, Berkeley DB для улучшения производительности агрессивно кэширует в Mpool страницы баз данных. Но эти страницы должны быть, в конечном счете, записаны на диск, и чем скорее мы это сделаем, тем быстрее мы сможем сделать восстановление в случае отказа приложения или системы. При этом возможен компромисс между частотой создания контрольных точек и продолжительностью восстановления: чем чаще система создает контрольные точки, тем более быстро ее можно будет восстановить. Создание контрольных точек является транзакционной функцией, поэтому мы рассмотрим детали создания контрольных точек в следующем разделе. В данном разделе, в соответствие с его спецификой, мы рассмотрим записи о контрольных точках и о том, как журнальный менеджер выступает в двух ролях – как автономно работающий модуль и как компонент Berkeley DB специального назначения.
Обычно сам журнальный менеджер понятия не имеет о типах записей, так что теоретически он не должен отличать записи о контрольных точках от других записей – они просто являются строками байтов с произвольной структурой, которые журнальный менеджер записывает на диск. На практике, в журнале хранятся метаданные, а это указывает, что журнальный менеджер понимает содержание некоторых записей. Например, во время запуска журнала, журнальный менеджер проверяет все файлы, которые он может найти, и определяет, какой из них был записан последним. Он предполагает, что все журнальные файлы, записанные до этого, заполнены и он их не трогает, а начинает исследовать самый последний журнальный файл и пытается определить, сколько в нем содержится действительных журнальных записей. Он читает журнальный файл с начала и останавливается в случае, если / когда он обнаруживает заголовок журнальной записи, в котором находится неверная контрольная сумма, что указывает на конец журнала или начало испорченной части журнального файла. В любом случае это логический конец журнала.
В процессе этого чтения журнала, когда происходит поиск текущего конца журнала, журнальный менеджер извлекает тип записи Berkeley DB и ищет записи о контрольных точках. Он запоминает позицию последней найденной им записи о контрольной точке в метаданных журнального менеджера в качестве «предпочтительной» для системы транзакций. Т.е. последнюю контрольную точку должна искать система транзакций, но вместо того, чтобы позволить как журнальному менеджеру, так и менеджеру транзакций обоим прочитывать весь журнальный файл, менеджер транзакций делегирует эту задачу журнальному менеджеру. Это классический пример нарушения границ абстрагирования в обмен на повышение производительности.
Каковы последствия этого компромисса? Представьте, что система, отличная от Berkeley DB, использует журнальный менеджер. Если случится так, что в той же самой позиции, где Berkeley DB размещает свой тип записи, будет записано значение, соответствующее типу записи о контрольной точке, то журнальный менеджер идентифицирует эту запись, как запись о контрольной точке. Однако, если приложение запросит у журнального менеджера эту информацию (прямым доступом к полю cached_ckp_lsn
в метаданных журнала), эта информация никак ни на что не повлияет. Короче говоря, это либо вредное нарушение деления проекта на слои, либо хитроумная оптимизация производительности.
Управление файлами является еще одним местом, где разделение между журнальным менеджером и Berkeley DB является нечетким. Как уже упоминалось ранее, в большинстве журнальных записей Berkeley DB должна указываться база данных. В каждой журнальной записи должно быть полное имя файла базы данных, но это дорого с точки зрения расходования пространства журнала и громоздко, поскольку процесс восстановления должен использовать отображение этого имени в дескриптор определенного вида, который можно использовать для доступа к базе данных (дескриптор файла или дескриптор базы данных). Вместо этого, база данных в Berkeley DB идентифицируется в журнале по целочисленному идентификатору, называемому идентификатором файла и реализован набор функций, называемый dbreg (для «регистрации базы данных»), который поддерживает соответствие между именами файлов и идентификаторами журнальных файлов. Вариант такого отображения, хранящийся на диске (имеющий тип записи DBREG_REGISTER
), заносится в журнальные записи при открытии базы данных. Тем не менее, для того, чтобы облегчить отмену транзакций и выполнения восстановления, нам также нужны варианты представлениях этого отображения в оперативной памяти. На какую из подсистем возложить обязанность поддержки этого отображения?
Теоретически, отображение файла в идентификатор журнального файла является высокоуровневой функцией Berkeley DB; она не принадлежит ни к одной из подсистем, и она не вписывается в общую картину. В исходном варианте проекта информация об отображении оставалась в структурах данных журнальных подсистем, поскольку казалось, что журнальная система является лучшим вариантом. Тем не менее, после повторного поиска и исправления ошибок в реализации, поддержка отображения была изъята из кода журнальной подсистемы и была преобразована в свою собственную небольшую подсистему со своим собственным объектно-ориентированным интерфейсом и собственными структурами данных. (В ретроспективе, эта информация должна логически размещаться в самой информационной среде Berkeley DB вне любой подсистемы).
Тринадцатый урок конструирования
Редко встречается такая вещь, как несущественная ошибка. Конечно, то и дело встречаются опечатки, но ошибка обычно означает, кто-то не понял в полной мере, что он должен сделать и что-то реализовал неправильно. Когда вы исправили ошибку, не ищите симптом: ищите причину, если хотите – непонимание, т.к. это приведет к лучшему пониманию структуры проекта, а также к выявлению фундаментальных недостатков в самом проекте.
Назад | К оглавлению книги | Вперед |