Некоторые решения старых проблем

Dalton Calford делится своими практическими решениями ряда известных проблем
Оригинал – http://www.interbase2000.org/doc_calford_1.htm
Перевод – Кузьменко Дмитрий, http://www.ibase.ru
 
Примечания переводчика. Этот документ был написан более года назад. В нем был ряд упоминаний о примерах кода, графиках, комментариях и т. п., которые Далтон собирался предоставить впоследствии. Однако с тех пор ничего к этому документу не добавилось, поэтому я позволил себе исключить подобные упоминания.
Дополнительно, вы можете заметить, что ряд описанных здесь идей по поводу конфигурации дисковой подсистемы абсолютно аналогичны тем, которые изложены в документе. Если вы не собираетесь применять эти решения сразу, то по крайней мере, вам будет известен путь преодоления ряда проблем с производительностью, если таковые возникнут.
 

Введение

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

Если после чтения кто-нибудь обратится ко мне с лучшими решениями, чем мои – милости просим поделиться идеями. Мы все выиграем от этого, а вы, как минимум, улучшите свою карму...
 
  1. Обеспечивая непрерывную работу (24x7)
  2. Конфигурация "железа"
  3. Конфигурация сервера
  4. Способы сохранения резервных копий данных
  5. Roll-Forward Logging
  6. Репликация
  7. Отмена действий пользователей
  8. Advanced Techniques
 

1. Обеспечивая непрерывную работу (24x7)

1.1 Обзор

Многие встречают этот термин – '24x7' – подразумевающий непрерывную доступность базы данных, и считают что решение может быть найдено как в ПО сервера баз данных, так и в аппаратном обеспечении. Правда на самом деле в том, что никакой производительно программного или аппаратного обеспечения не может создать комплект, удовлетворяющий решению всех проблем. Производители всего лишь удовлетворяют потребности большей части рынка. Это означает, что разработчик должен самостоятельно проанализировать требования и архитектуру наилучшего решения для конкретной задачи.

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

В принципе, InterBase подходит для задач 24x7, если только разработчик понимает задачу в общем и обеспечен достаточным набором инструментария.

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

Что же касается аппаратных решений, то сбои происходят даже в самых дорогих конфигурациях, которые можно купить за деньги.

1.2 Наши предположения

Наши решения по базовой архитектуре и коду системы основывались на следующих предположениях:
  1. Аппаратура дешева и постоянно дешевеет.
  2. Время – деньги, в буквальном смысле. Вы должны иметь возможность решить проблему за минимально возможное время.
  3. Скупой платит дважды. Я считаю совершенно серьезно, что начальные затраты существенно экономят затраты и время впоследствии. Если вы с самого начала сэкономите на анализе проблемы и выберете неверное решение, то в дальнейшем вам придется потратить в три раза больше времени и усилий на решение возникающих проблем.
  4. Все, что может портиться, портится. Неважно, насколько хорош ваш план, у вас всегда будут проблемы. Если это не очевидно, поищите в интернете законы Мэрфи или фразу "энтропия".


2. Конфигурация "железа"

Это очень общий обзор конфигурации аппаратуры, который даст вам идеи по конфигурированию и настройке собственной системы.

2.1 Процессор(ы)

Если вы используете SuperServer, то текущие версии InterBase не дадут преимущества на нескольких процессорах. Причина не в операционной системе, как кажется некоторым. На самом деле причина в том, что InterBase архитектуры SuperServer расчитан на распараллеливание задач при помощи нитей (threads) на одном процессоре. Эта архитектура не масштабируется при увеличении количества процессоров.

(ДК – основной причиной подобного поведения SuperServer является нераспараллеливаемый ввод/вывод. Косвенно, можно утверждать, что дисковые подсистемы RAID5 не дадут повышения производительности при использовании IB SuperServer, т. к. они расчитаны на ускорение параллельных операций обмена с дисками).

Так что не тратьте деньги на дополнительные процессоры, если используете SuperServer. Если вы хотите использовать процессоры полностью, переходите на Classic. Лично я не нуждаюсь в возможностях, предоставляемых архитектурой SuperServer, и предпочитаю Classic. Я использую InterBase на многопроцессорных машинах и поэтому не перейду на SuperServer, пока он не начнет поддерживать подобную технику.

2.2 Память

Classic и Superserver используют память по разному:
  • У SuperServer есть общий кэш, что сильно экономит память. Нужные метаданные также загружаются в общий кэш.
  • В Classic каждое соединение стартует новый процесс сервера. Каждый процесс загружает метаданные самостоятельно. Это увеличивает потребности в памяти, и делает старт каждого процесса медленным.

Если у вас достаточно памяти и хорошее оборудование, то вы не заметите разницы в скорости. Here are the numbers I use for best performance:
  • 50 Mb отдано под RAM-Disk. TEMP сконфигурирован так, что начинается он на RAM-диске, и продолжается на винчестере.
  • 150 Mb занято под операционную систему и разные вещи (меньше на Unix, больше на NT)
  • 30 Mb в среднем занимает каждый процесс Classic.

Это означает, что мне нужно ~800 Mb для системы, оптимизированной для обслуживания 20 коннектов (150 на ОС, 50 на RAMDISK, 300 на SuperServer или 800Mb на Classic). For a system optimized for speed to service 20 connections (150 for OS, 50 for RAMDISK, 300 for Super Server) or 800 Mb for Classic.

Могут быть и другие случаи. Я наблюдал пользовательские коннекты в Classic, занимающие от 10 до 50 мегабайт, но даже при средних 30-ти, сервер может выдерживать высокую нагрузку без заметного влияния на остальные клиентские процессы.

(ДК – объем памяти клиентского процесса в Classic зависит от базового количества страниц кэша (75) и от количества метаданных в базе, в основном триггеров и процедур. В отличие от кэша данных кэш метаданных не регулируется, поэтому принудительно ограничить размер памяти, занимаемый клиентским процессом, нельзя.)

2.3 Диски

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

Люди обычно не обращают внимания на физические характеристики используемых дисков. Они покупают очень быстрые устройства и кладут все (OS, SWAP, TEMP и данные) на один диск. Подобная мусорная свалка будет замедлять любые операции с этим диском.

Лучше купить несколько более дешевых дисков и использовать их для разных задач отдельно.

Следующее, о чем стоит подумать, это каким образом диски подсоединены к компьютеру.

Простые IDE-диски могут пересылать большие объемы данных достаточно быстро. Они могут отнимать до 20% процессорного времени, поскольку интерфейс IDE полностью контролируется процессором.
 
Примечание переводчика. Это уже давно не так. почти все современные ide-диски поддерживают DMA-mode, при этом процессор нагружается не более чем на 5 %.
SCSI устройства очень хороши, но нужно помнить, что шина SCSI работает на скорости самого медленного диска, подключенного к шине. Если вы потратите 80% суммы на высокоскоростной контроллер и очень быстрые диски, а затем подключите их к SCSI-каналу, на котором находится ленточное устройство, то вы получите производительность, характерную для 80-х годов.

Наилучшей является смешанная система.

Я подключаю два диска IDE на первый канал IDE. Они подключены к hot-swap bays (около $16), и используются как диски для операционной системы.

Смысл всего этого в том, что операционной системе совершенно нет необходимости быть на SCSI, а также что я могу очень быстро заменять эти диски и перезагружать ОС. Это спасает в ситуациях когда операционная система вдруг начинает себя вести "не так" после установки сервиспака или любого другого ПО.

Под NT достаточно трудно скопировать диск ОС на второе устройство, однако существует отдельное ПО (я использую комплект Ghost), которое позволяет это делать. На Linux подобная операция проблем не вызывает.

Я подключаю CDROM ко второму каналу IDE, и оставляю IDE в таком состоянии.

(ДК – даже если этот CDROM является "пишущим", то современные устройства подобного рода выпускаются с размером кэша, достаточным для компенсации задержек данных по шине IDE. Т. е. и тут в SCSI нет необходимости).

Затем, я конфигурирую SCSI.

Вы можете купить материнскую плату с встроенным SCSI контроллером, обычно поддерживающим два канала. Один канал обеспечивает работу старых (50-pin) устройств, таких как CDROM и лента, в то время как ко второму каналу можно подключать диски ultra-wide fast.

Если материнская плата не содержит контроллера, то его можно приобрести отдельно, с кэшем или без. Максимальное количество кэш-памяти, которое я считаю разумным для подобных целей – 32 мегабайта. Этот контроллер может обслуживать SWAP- (виртуальную память) и TEMP-диски (желательно на разных каналах, но не обязательно).

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

Мне нравится использовать "умный" контроллер SCSI с нескольким каналами. Я ограничиваю по три диска на канал. Стараюсь использовать контроллеры с как минимум тремя каналами, и расширяемым до 256Мб кэшем.

Таким образом, можно создать 3 RAID, каждый состоящий из трех дисков, и разделить диски по каналам. Можно пойти еще дальше, и использовать несколько контроллеров, каждый в похожей конфигурации.

Если база данных будет находиться на одном контроллере, а Shadow на другом, то даже если вы потеряете 60% дисков в случае сбоя, все будет продолжать работать, и клиенты будут иметь доступ к базе данных.

[I will post the CPU cases and hot swap bays we use in another posting.]

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

Если вы используете прямую репликацию, то я советую поставить по две сетевые карты в каждый [серверный] компьютер. Если репликация производится только между двумя машинами, то можно использовать null-кабель для их соединения..

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


3. Конфигурация сервера

Вы не поверите, но кроме установки размера страниц у баз данных в 8К, я ничего не меняю в конфигурации (ibconfig/isc_config) IB.

Вот что я делаю:
  • Размещаю операционную систему на одном диске.
  • Размещаю swap (виртуальную память) на другом диске (а не на отдельном разделе того же диска)
  • Размещаю пространство temp на другом диске
  • Размещаю данные на еще одном или нескольких дисках, которые больше не предназначены для других целей.
  • Создаю RAMDisk, устанавливая INTERBASE_TMP на этот диск. В случае Windows я указываю размещение временных файлов в ibconfig (TMP_DIRECTORY).
  • Если я использую Linux, я создаю еще один RAM-диск и размещаю там файлы InterBase.
Я использую скрипт Bash для копирования ПО InterBase на этот диск. Я привязываю этот диск так, что inetd запускает gds_inet_server именно с RAM-диска. Правда, это все в процессе тестирования, так что я не могу заявить о каких то изменениях производительности в результате этих действий.
  • Я наполняю базу данных, и делаю еще одно для увеличения производительности:
    • Содаю базу данных, скриптом или из backup.
    • Создаю временную таблицу, и наполняю ее случайными данными до тех пор пока файл базы данных не достигнет нужного объема.
    • Затем удаляю временную таблицу. В результате серверу больше не требуется просить операционную систему увеличить файл базы данных при вставке новых записей.

Все это дает существенное повышение производительности.
 

4. Способы сохранения резервных копий данных

Я использую следующие методы:
  • Обычный Backup – делает снимок базы данных на момент старта backup.
  • Shadow Backup – дает снимок базы данных на момент отключения сервера.
  • Roll-Forward Logs – Differential Backup – позволяет делать любой backup и применять изменения для достижения максимально актуального состояния данных. Вы можете расширить эту технику для удаленной репликации – далее я приведу способы реализации и упомяну возникающие проблемы.

4.1 Gbak

Моя база данных очень велика, но обычно мне нужно делать сохранение только некоторых таблиц, потому что большинство таблиц содержит статические данные. (Проблема: вы не можете сделать "частичный Backup" стандартными средствами InterBase)
ИЛИ
У меня есть базы данных, которые содержат мало изменяемх данных, и я хочу использовать "incremental backup" для фиксирования этих изменений. (Проблема: GBAK может делать только полный backup базы данных)
ИЛИ
Backup на моих базах данных работает так долго, что пока он закончится, проходит несколько часов/дней/недель. Такой backup практически бесполезен, поскольку данные в базе данных уже давно не те, что сохранились в backup...
ИЛИ
[ДОБАВЬТЕ ЕЩЕ ВАРИАНТ ПО ВКУСУ]

Gbak является одним из главных недостатков, на который жалуются пользователи баз данных. Я помню, что одна из моих первых дискуссий с Маркусом Кемплером (InterBase support) была как раз на тему backup/restore, длившихся сутками.

(ДК – на момент написания статьи основная база Dalton имела размер около 25 гигабайт)

4.2 Как работает Gbak?

Давайте рассмотрим GBAK, что это такое, зачем, и почему он так сделан?

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

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

Вы можете сделать то же самое (включая недокументированную функцинальность IB API, например отключение сборки мусора), и написать свой gbak, который будет иметь нужную вам функциональность. Я именно так и делал для своего предыдущего заказчика. Самое сложное, это понять, в каком порядке надо выгружать или загружать объекты и данные.

4.3 Альтернативы Gbak

4.3.1 'Быстрый Backup'

Gbak написан так, что он может работать, когда к базе данных подключены другие пользователи (ДК – это просто еще одна транзакция SNAPSHOT). Если вам нужен более быстрый backup, то остановите Interbase, и сделайте backup на уровне ОС (пусть даже и простым копированием файла базы данных).

4.3.2 Использование Shadow

Вы можете создать shadow, затем остановить InterBase, переименовать первый файл shadow, рестартовать InterBase и подсоединиться к серверу. interbase.log будет содержать сообщение, что shadow недостуна но IB будет думать, что просто сломался диск с shadow, и продолжать работу.

Затем, удалите определение перименованной shadow в БД, и создайте еще одну shadow. После чего можно при помощи gfix (ключ -a) сделать из переименованной shadow нормальный файл базы данных.

Это позволит вам иметь очень короткий перерыв в работе (меньше минуты на остановку IB, переименование shadow и рестарт IB) и получить моментальный снимок базы данных, который уже можно подвергнуть обычному gbak или просто залить на ленту или другой диск.

4.3.3 Использование Roll-Forward Logs

Если даже прекращение работы на минуту невозможно, то вот метод Roll-Forward Logs.

Я помню дискуссию на interbase@mers.com около полугода назад, в которой одни пользователи жаловались на отсутствие "логов наката", в то время как другие утверждали что в этом нет никакой необходимости. Я докажу, что подобные логи не только очень полезны, но и спасали меня не один раз за последние несколько лет.

Что такое roll-forward log? Это полный лог изменений (insert/update/delete), происходящих в базе данных. Я опишу процесс размышлений, в результате которого мы достигли успеха (хотя и прошли через ряд ошибок), так что вы сможете понять почему и как мы применяли те или иные решения.

Наша первая ошибка – External Tables. Нашей первой идеей был комплект триггеров на каждой таблице (position 0, т. е. срабатывающих раньше любых других триггеров), которые вставляли данные во внешние таблицы. (Пожалуйста, имейте терпение. Я уже знаю что вы вовсю машете красными флагами, но как я сказал, я пытаюсь объяснить путь который мы прошли).

Внешние таблицы хранят текстовые значения очень хорошо, но когда дело доходит до дат или чисел, приведение типов (cast) не так уж и весело. Даже и не думайте о блобах или массивах. Мы некоторое время потратили на решениеэтих проблем, однако самым большим недостатком внешних таблиц является то, что они находятся ВНЕ КОНТЕКСТА ТРАНЗАКЦИЙ..

Для тех, кто не знает – транзакции, это лучший друг прогрограммиста баз данных во всем мире. Любое действие в IB выполняется в транзакции.

Это имеет ряд особенностей. Например, если вы вставляете данные в таблицу, у которой есть триггер на вставку, в котором запись вставляется еще в одну таблицу. Если вы выполните rollback, все вставки будут отменены.

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

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

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

Регистрация ВНУТРИ базы данных. Единственный нормальный способ – вести лог внутри базы данных. Как мы создавали его? Мы рассмотрели разные способы.
  • Первый способ – таблица с очень большим строковым полем, хранящая операторы insert/delete/update. Это позволяет обрабатывать все типы данных, кроме блобов и массивов.
  • Второй способ состоит в двух таблицах, master-detail:
поля таблицы MASTER
SURROGATE_KEY = уникальный идентификатор, автоматически генерируемый.
TABLE_NAME = Имя таблицы, над которой производится действие.
ACTION_TYPE = (I)nsert; (U)pdate;(D)elete
KEY_LINK = Текстовое поле, содержит условие запроса, например
'WHERE PK_FIELD1= VAL1 AND PK_FIELD2=VAL2'
TIME_STAMP = Время выполнения операции. Генерируется триггером при вставке записи.
Смысл KEY_LINK состоит в том, чтобы найти изменяемую запись, если ее первичный ключ состоит из нескольких полей.

поля таблицы CHILD
LINK_KEY = ключ связи с таблицей MASTER, ссылка на SURROGATE_KEY
FIELD_NAME = Название поля
VAL_VARCHAR
VAL_NUMBER
VAL_DATE
VAL_BLOB
и так далее.
Таким образом, любое изменение в базе данных отражается в виде записи в таблице MASTER, и одной или более записей в таблице CHILD, для каждого столбца исходной записи.

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

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

4.3.4 Использование 'Ботов' для извлечения зарегистрированных изменений

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

'Бот' это сокращение от 'робот' – автоматический процесс, который имеет очень ограниченную функциональность и запрограммирован на реакцию на некоторые события. Боты InterBase полностью управляются событиями и значениями, хранимыми в базе данных.

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

Мои БОТы (или IBOTS, т. е. боты InterBase) помогают обслуживать систему. Я вернусь к их описанию позже в этом документе. В настоящее время я разрабатываю базовый шаблон бота, чтобы любой из вас мог использовать его или настроить для своих собственных нужд.

4.3.5 Наша МногоСерверная, МногоФайловая база данных

Для объяснения ряда вещей, мне нужно показать архитектуру, в которой базы данных находятся на нескольких серверах и в нескольких файлах.

У меня есть типы файлов, которые считаются основными. Они содержат все пользовательские данные. Таких файлов может быть много, и они могут между собой реплицировать данные. Они могут находиться в одной или разных сетях, в зависимости от режима обновления, например упомянутого roll-forward log.

Один из основных файлов – главный. Его задача состоит в определении конфликтов с разными основными файлами. Разница между главным и основными файлами в настроке таблицы Variables.

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

Другие типы файлов – файлы истории. Они могут находиться на любой машине в сети. Если доступ к ним достаточно редок, то в качестве оборудования можно выбрать нечто дешевое...

Этот тип файла может быть полезен, а может и нет – если есть возможность разделить данные на части, то лучше разделить основной файл на несколько частей.

Третий тип файла – roll-forward log. Я настраиваю их так, чтобы хранились изменения только за неделю, или чтобы такой файл можно было без проблем записать на CD. В следующем разделе я остановлюсь на этом подробнее.
 

5. Roll-Forward Logging

5.1 Концепция и реализация Roll-Forward

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

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

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

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

Как только вы восстановили базу данных из резервной копии, то база данных примет то состояние, в котором она была на момент старта backup. Было бы здорово иметь все изменения с этого момента в виде скрипта SQL, чтобы применить их к восстановленной базе данных для получения самых актуальных данных на последний момент.

Roll-Forward – это накат изменений в виде действий пользователей, в той последовательности, в которой они происходили.

InterBase не имеет системных средств для реализации подобного механизма. Однако это не значит, что его нельзя реализовать самому. Можно использовать триггеры для регистрации действий, производимых в базе данных. Например, у вас могут быть триггеры After Insert, After Update и After Delete, которые будут записывать определенные значения в дополнительные таблицы.

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

Это делается достаточно просто при помощи компонентов IBObjects, обеспечивающих двухфазный коммит. Таким образом, удаление зарегистрированных изменений происходит только тогда, когда эти изменения уже перенесены в другую базу данных.

(ДК – 2PC следует использовать очень осторожно, и только в локальной сети. Если транзакция 2PC не будет завершена корректно, то застрявшие данные не смогут быть вычищены до тех пор, пока вы не обработаете сбой при помощи утилиты gfix (см. Operations Guide по gfix и командам фиксации двухфазных транзакций)).

При правильных отметках времени, вы можете сделать backup, затем запросить roll-forward базу данных и применить только те изменения, которые произошли после начала backup.

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

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

(ДК – известен и другой способ выборки изменений, применяемый в основном для обновления справочников на клиентских местах – это добавление к нужной справочной таблице столбца INTEGER, и увеличение записываемого в этот столбец генератора каждый раз, когда запись вставляется или обновляется. Таким образом, достаточно запомнить значение генератора, считать справочник, после чего для обновления справочника нужно считать только те записи, у которых значение дополнительного столбца больше запомненного значения генератора. Собственно, этот способ можно использовать для указанного метода roll-forward).

5.2 Реализация Roll-Forward при помощи демонов и ботов

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

У нас много компьютеров, каждый имеет свой IP-адрес, и каждый имеет свой уникальный путь к определенной базе данных.

В любой момент сервер может быть остановлен для обслуживания, или новый файл может вступить в действие после backup/restore. Очень трудно обработать такие ситуации при помощи алиасов BDE или чего то подобного.

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

5.2.1 Демоны

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

В нашей сети мы присвоили несколько IP-адресов нашему web-серверу при помощи DNS. Допустим, это хост GDBCONTROL.MYDOMAIN.COM, и он имеет ряд адресов (192.168.10.2 .....192.168.10.6) компьютеров, на которых будет работать наш расширенный DNS-сервер – демон GDBCONTROL.

Когда клиентская машина хочет подсоединиться к системе, она ищет хост 'GDBCONTROL.MYDOMAIN.COM', после чего посылает UDP-пакет специального формата на полученный адрес (и на предопределенный порт). Если за указанный интервал времени ответа не получено, поиск хоста 'GDBCONTROL.MYDOMAIN.COM' повторяется. Если вы настроили ваш DNS правильно, то он будет по очереди выдавать IP-адреса из указанного пула, и в конце-концов клиентский компьютер сможет соединиться с демоном GDBCONTROL на одной из этих машин.

Это позволяет добавлять демонов GDBCONTROL в систему по мере необходимости, или наоборот, исключать их из списка без необходимости перенастройки клиентских систем.

Демон GDBCONTROL, подсоединяясь к одной из основных баз данных, запрашивает имя и пароль пользователя, и обращается к базе данных на предмет выяснения к каким базам данных может быть перенаправлен клиент.

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

(ДК – в настоящее время существует несколько менеджеров алиасов для InterBase/Firebird, работающих точно таким же способом. Вы можете использовать те из них, которые идут с исходным текстом, для подобных нужд).

Демон GDBCONTROL не просто подключен к основной базе данных. У него нет интерфейса как такового, однако он может регистрировать действия пользователей по броадкастам через UDP.

Демон является аналогом сервиса NT. Вы стартуете его. У него нет пользовательского интерфейса. Он работает в фоне, вы можете конфигурировать его через Registry или через собственное API.

(ДК – напомню, что если нужно подсоединиться к БД из сервиса, то требуется указывать имя сервера, как в удаленном подключении – например, localhost:c:\dir\data.gdb – если сервис и IB находятся на одной и той же машине. Локальные соединения в таком режиме не работают).

При помощи GDBCONTROL, все управляющие команды и ответы идут через броадкасты UDP.

Сейчас я помещаю возможность обмена по UDP для конкретного IP-адреса. таким способом удаленный пользователь может просигнализировать боту о всем, что он делает. Правда, у меня нет необходимости в столь подробном логе. Описываемая система и так самодостаточна, что я обнаружил, когда оказалось что одна из вторичных баз данных и ее бот были остановлены. Я проверил лог бота, обнаружил проблему, перезагрузил NT, и... все опять заработало как нужно...

Демон также общается с одним из ботов под названием 'ISALIVE', и в курсе, когда конкретный сервер работает или остановлен.

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

5.2.2 БОТы/IBOTы

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

Причина, по которой я называю GDBCONTROL "демоном", а не "ботом" в том, что GDBCONTROL общается с клиентом по UDP. Боты не делают этого. Только так я различаю приложения типа "бот" от многозвенных приложений типа "демон".

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

Если бот не получает информации о рабочем файле или о команде завершения работы, он прекращает работу сам.

Бот должен быть сконфигурирован так, чтобы при запуске он знал что он такое, и соединился с GDBCONTROL. После того как он получит имя сервера, базу данных, имя пользователя и пароль, он регистрируется на указанном сервере и регистрируется на уведомления о двух событиях. Одно событие идентично имени бота, а второе называется 'BOTSTYLE||SETUP'.

Например, бот ROLLFWD регистрируется на одноименное событие и на событие ROLLFWD||SETUP.

Мы пришли к идее, что определенный тип бота может быть только в единственном числе на конкретном IP-адресе, и каждый пользователь будет идентифицироваться однозначно. Это означает, что если на компьютере будет запущено несколько ботов, все они будут разными.

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

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

ROLLFWD. Бот ROLLFWD подсоединен к основной базе данных и базе данных логов. Соединение не постоянное – оно осуществляется только по мере необходимости. Когда таймер бота сработает, бот выполняет процедуру на основном сервере, сохраняя получаемые данные в базе данных логов. Это делается при помощи двухфазного коммита, поскольку после того как изменения перенесены в лог, они удаляются в источнике. Если произойдет ошибка, то все изменения в обоих база данных будут отменены.

(ДК – на самом деле не все так просто. См. описание транзакций 2PC и их состояний. Еще раз замечу, что для подобных целей совсем не обязательно использовать транзакции 2PC. Определить, удалены ли скопированные в лог данные в случае сбоя, можно путем дополнительной проверки (запроса)).

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

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

Мы даже пошли на размещение программ в полях блоб, при этом бот при запуске сверяет собственную версию и версию в блоб, после чего, если он "устарел", бот самостоятельно вытаскивает свою новую версию из базы данных.

(ДК – нужно помнить, что, например, NT позволяет перименовать exe во время его работы. Именно таким образом можно заменять боты на ходу. Старый бот должен переименовать себя определенным образом и завершить работу, а новый бот при запуске должен сделать поиск определенного "старого" файла, и удалить его. Таким образом, боты будут автоматически обновлять себя сами.)

5.2.3 БОТы и DDL

До сих пор рассказ описывал работу с данными, и никак не затрагивал изменения метаданных, т. е. объектов базы данных.

Вначале я строил триггеры на системных таблицах, которые срабатывали при изменениях. Эти триггеры должны были строить DDL целиком. После ряда проблем я сделал по другому – таблица Varables фиксирует изменения, а уже бот ROLLFWD в соответствии с изменениями строит правильный DDL, и сохраняет его в базе данных логов.

5.2.4 Защита клиентских соединений

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

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

5.2.5 Применение логов

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

В нашем случае, если база данных портится, то у нас есть backup, и есть лог изменений в базе данных.

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

5.2.6 Внимание!!!

Если вы формируете лог в триггерах, то можно это делать в триггерах Before или After. Учитывайте следующее: Если вы не меняете данные в триггерах, то неважно, в каком месте вы регистрируете изменения. Однако если кроме регистрирующих есть и другие триггеры, то нужно учитывать некоторые вещи:

Если вы перехватываете данные в триггере insert, вы должны подумать о том, как сервер будет обрабатывать вставку в логи. Это может быть проблемой, если триггеры на таблицах вставляют данные в другие таблицы. Эти изменения могут также отказаться в логах, что приведет к дублированию данных.
Если вы перехватываете данные после выполнения DML, то нужно убедиться, что в результате этого обычные триггеры не сработают опять. В противном случае вы испортите ваши данные.Простой блок IF...THEN в триггере, с проверкой должен триггер выполняться для данного пользователя или нет, предохранит вас от случайных изменений данных в БД.Это так, даже если вы не работаете с репликацией. Вы можете заставить триггеры не срабатывать для определенных пользователей.

5.3 Мои IBOTы

ISALIVE – этот бот просто проверяет доступность различных серверов или баз данных, и одновременно может анализировать скорость доступа к данным. Это более информативно чем пинг, хотя бы в том случае, когда сервер работает, а база данных недоступна.

SECURIT – этот бот реагирует на команды управления пользователями, выдачу прав (grants), проверяет зависимости прав, и прочее. Это исключает необходимость знать пароль SYSDBA для вызова IB API управления пользователями, а также совместимо с IB 4.

ROLLFWD – о нем уже подробно рассказано.

REPLICAT – этот бот осуществляет репликацию много-ко-многим.

METABUILD – этот бот производит изменения метаданных. У меня на системных таблицах стоит триггер, который запрещает кому либо кроме этого бота выполнять операторы DDL.

DOCUMENT – этот бот обеспечивает идентификацию счетов и т. п. (выдает номера). Может быть один бот на диапазон номеров.

У меня есть и другие, но эти самые главные.

5.4 Some Conclusions

There we have a simple roll-forward system. My big concern is being able to provide a solution for those who need it but are locked into a non-maintained IB environment (4.x) or for those who want a system that is highly modular and easy to modify and maintain without needing to understand C or BLR or even the InterBase API. If they do go this route, they simply print out the final version of the documentation as a development or maintenance guide and give it any new developers on staff to read. I am trying to target as wide a audience as possible with my writing because, for many, this is a very new and intimidating subject.

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

6. Репликация

Репликация осуществляет копирование данных из одной базы данных в другую. Существуют разные виды репликации.

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

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

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

Shadow похожа на одностороннюю репликацию, происходящую под управлением InterBase. Это хорошая защита от случаев сбоя дисков..

Однако тень не относится к решениям, когда сервер не способен обработать все поступающие запросы.

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

Защита от сбоев

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

Частичная репликация

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

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

Приложения в этом случае при необходимости могут работать с обоими серверами одновременно.

Если такие серверы содержат одинаковые данные, то вы получили балансировку нагрузки.

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

Полная репликация

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

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

При полной репликации это может быть проблемой.

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

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

6.1 Уникальные Идентификаторы уровня предприятия (UIDs)

Много систем используют обычные числа, например возвращаемые генераторами, как первичные (суррогатные) ключи. Генератор не имеет способа определить, какие зачения были присвоены какой записи на другом сервере.

For a data warehouse, many servers may be serving multiple users and producing calculated results that encompass the work being performed by other servers.

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

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

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

Теперь мы находимся в месте, которое решается разработчиками одинаково, т.е. автоинкрементными числами, обычно это генератор, начинающий свой отсчет с 1. Разница только в том, что мы должны привести целочисленное значение к строке, и строка должна быть достаточной для этого длины – 11 символов включая знак. Для облегчения операций сортировки по таким уникальным идентификаторам мы будем использовать дополнение нулями, котоое выглядит следующим образом:
001
002
003
вместо
1
2
3

Это исправляет обычный порядок сортировки для строк, и мы не получаем их в приблизительно таком виде
19
2
20
21
22
...
29
3
30
31
и так далее.

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

В итоге мы пришли к UID из 19 символов, который выглядит как
2 символа-идентификатора сервера. Чтобы не создавать проблем при сортировке, используются всегда два символа
- AA или AB и так далее.

2 символа (3-й и 4-й) идентифицирующих базу данных
- то же самое – AA или AB и так далее.

4 символа (с 5 по 8), идентифицирующих таблицу
- Разумеется, одинаковые таблицы в разных базах данных имеют одинаковый идентификатор.

10 символов – идентификатор записи в диапазоне с -2147483647 до 2147483648, дополненный слева нулями,
например,
AAAAAAAA-2147483647 (минимум)
AAAAAAAA00000000001 (середина)
AAAAAAAA02147483648 (максимум)
и так далее.

(ДК – как известно, в базе данных нет переменных уровня базы данных кроме генераторов. Но генераторы содержат только числа, а не строки. Нет проблем – двухсимвольную строку можно выразить числом, если представить ее как smallint (т. е. word). А преобразовать это число в два символа – написать простую UDF.

В InterBase 6/Firebird логичнее было бы для тех же целей использовать NUMERIC(18, 0), однако в Delphi и C++Builder до сих пор существуют проблемы с использованием таких чисел для справочных таблиц в качестве lookup.

CHARACTER SET OCTETS тоже хорошо подошел бы для этих целей, но Delphi и C++Builder в TStringField обрезают строку при первом же значении 0. Поэтому с числами, хранимыми в таких полях, можно было бы работать только при помощи компонент, работающих с XSQLVAR – например, IBSQL в IBX, FIBQuery в FIBC и т. п.)

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

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

Подобное планирование требуется, когда данные реплицируются между серверами. В простой схеме репликации, для пометки записи может быть спользован специальный столбец, например,
select * from mytable where changed='YES'

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

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

Другая ситуация, которая должна обрабатываться, это когда разные пользователи на разных серверах вводят одни и те же данные.

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

Через некоторое время клиент звонит еще раз, чтобы подтвердить изменения, и попадает на сотрудника Б. Тот обнаруживает, что данные не введены, и начинает вводить их.

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

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

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

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

To ensure that the data are correct and not duplicated, each table has a series of 'KEY' fields that enable duplicate checking. Also, one server out of the cluster is responsible for maintaining the data integrity. All the servers must have the code in place to do the maintenance, but only one may be active at a time. This is managed by storing a value in the Variables table that the database checks.

You can see that data design for handling load balancing can be very convoluted and detailed. Don't worry, it is actually very simple if you stay in touch with the overall picture and don't get lost in the details.

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

7. Отмена действий пользователей

В предыдущих разделах я упоминал о удобствах регистрации изменений и возможности отмены отдельных действий пользователей.

Когда пользователь регистрируется в Interbase для того чтобы изменить данные, изменения не вступают в силу и не влияют на другие данные пока не будет сделан commit. Если данные сохранены, то выделить и отменить изменения становится намного сложнее.

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

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

8. Advanced Techniques

8.1 Управление конфликтами

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

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

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

8.2 Вопросы сети и скорости передачи данных

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

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

8.3 Выбор архитектуры InterBase

Чаще всего вставка записей оказывается самым медленным процессом. Именно здесь архитектура Classic может спасти ситуацию, поскольку каждый клиент – это отдельный процесс.

Замечание

Репликация это такая область дизайна баз данных, когда конкретное решение абсолютно неподходит для остальных случаев

(ДК – тем не менее, существует ряд механизмов репликации, или общих архитектурных особенностей, которые применимы для любого случая.)

Подпишитесь на новости Firebird в России

Подписаться