Что такое SWEEP в InterBase и Firebird?

KDV, hvlad, 28.10.2004, обновление – 21.04.2016.

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

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

Начнем с того, что по умолчанию в созданной базе данных есть параметр Sweep Interval, равный 20000. Как только разница между транзакциями Oldest Snapshot и Oldest (см. gstat -h db.gdb) (в InterBase 7.x и выше – якобы разница между Oldest Active и Oldest) достигнет этого числа, стартует "sweep thread" (на самом деле если посмотреть в tra.c на функцию start_sweeper, то ясно видно что это отдельный коннект (!) который занимается "каким то" sweep. В InterBase 7.x он виден как отдельный коннект с именем "collector" в tmp$attachments).

Собственно, данная разница между номерами транзакций может возникнуть при совершенно различных условиях, поэтому как правило автоматический sweep и стартует неожиданно. Для избавления от таких неожиданностей рекомендуется устанавливать авто-sweep в 0 (gfix db.gdb -housekeeping 0), и периодически запускать sweep вручную (gfix db.gdb -sweep), в момент наименьшего количества активных коннектов, или вообще в "монопольном" режиме.

Теперь немного отвлечемся в сторону сборки мусора. При версионности сборка мусора является кооперативной. Мусор – это те версии записей, которые не нужны больше ни одной транзакции. Кооперативность сборки мусора проявляется в том, что "мусорные" версии детектируются только при чтении этих самых версий. Т. е. до тех пор, пока запись с версиями не прочитана (select в SuperServer, select и update в Classic) ни в одной транзакции, ее версии остаются на диске и как мусор не собираются (если, конечно, в данный момент они неактуальны, и действительно являются мусором).

Наихудшие случаи:
  1. запись обновляется в триггере, запись никто не читает. Версии в этом случае накапливаются бесконечно, до тех пор, пока данную запись кто-либо не прочитает.
  2. в таблице производится пакетная вставка, выборка и удаление с проверкой на условие. Допустим, минимальный и максимальный диапазон "читаемых" записей помечается генераторами. То есть, вставка, чтение, удаление при where id > min_gen_id and id <= max_gen_id. Значит, после удаления данные версии никогда не будут прочитаны, а следовательно не будут собраны как мусор, в итоге на диске останутся оригинальные вставленные записи и их версии удаления (delete stub).
Более подробно ситуации с накоплением и несборкой мусора описаны в статье.

Сборка мусора бывает двух типов:
  1. В архитектурах Classic (до InterBase 6.0, в Yaffil и Firebird) и SuperClassic (Firebird 2.5, 3.0) – мусорные версии убираются самим потоком (thread), который при чтении наткнулся на версии.
  2. SuperServer (в InterBase 6.0 и выше, Firebird 2.5, 3.0) – при обнаружении мусорных версий на странице (при чтении и обновлении) об этом сигнализируется потоку (thread) сборщика мусора. Сборщик мусора периодически собирает мусор на помеченных страницах.
Первая стратегия приводит к замедлению select (чтения записей) при сборке мусора. Вторая стратегия приводит к замедлению сборки мусора как таковой – в частности, при модификации страницы сборщик мусора не знает, был он на этой странице уже или нет. Кроме того, поток (thread) сборщика мусора при высокой нагрузке сервера редко получает кванты времени для работы, в результате чего сборка мусора выполняется много дольше, чем при первой стратегии (в InterBase 7.x в конфигурации введен параметр SWEEP_YIELD_TIME для задержки сборки мусора при его обнаружении на n миллисекунд. А также SWEEP_QUANTUM, определяющий количество "тиков" процессора, занимаемых сборщиком мусора).
Также, препятствовать фоновой сборке мусора может постоянное и частое обновление одних и тех же изменяемых страниц данных - как только сборщик мусора к ним добирается, страницы опять меняются, и сборщик мусора должен подождать.
Ну и конечно, при второй стратегии сборка мусора может происходить неопределенное время даже после отключения клиентских соединений, что делает небезопасной перезагрузку сервера в этот момент.
 
Примечание. Под "перезагрузкой сервера" имеется в виду штатная перезагрузка операционной системы с принудительным завершением работы служб. Точно так же сюда можно отнести нажатие кнопки reset, отключение питания и т. п., что само по себе может привести к повреждению базы данных, если в этот момент с ней интенсивно работают. Обычная остановка сервиса или приложения Firebird или InterBase работает иначе, при этом сборка мусора любого вида (фоновая, кооперативная или sweep) прекращается корректно.
В Firebird 2.0 и выше можно управлять стратегией сборки мусора. Для этого в конфигурации (firebird.conf) есть параметр GCPolicy, который имеет 3 варианта:
  1. background – сборщик мусора работает как фоновый, собирая мусор отдельным thread.
  2. cooperative – сборщик мусора работает в кооперативном режиме, собирая мусор немедленно при чтении "мусорных" версий
  3. combined – сборщик мусора работает в кооперативном режиме, но если мусор собрать не удается, то о "замусоренных" страницах сигнализируется фоновому сборщику мусора.
Как сервер определяет, считывается мусорная версия записи, или она еще кому то нужна: версии нужны в основном транзакциям snapshot. Как только стартует такая транзакция, она запоминает самый младший номер активной на данный момент транзакции. Соответственно, все версии созданные от такой "младшей" транзакции до номера данного snapshot могут порождать версии, в которых заинтересован данный snapshot. И эти версии "мусором" не являются. Поэтому ниже Oldest snapshot (той самой минимальной активной на момент старта ныне активного snapshot) версии записей могут быть убраны как мусор, а выше – нет.

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

Отсюда уже понятно, почему sweep приводит к существенным "тормозам" системы, будучи запущен по базе данных сколь-нибудь значительного размера. Например, по базе данных 2 гигабайта на средней технике вручную запущенный sweep (gfix -sweep db.gdb) работает в течение 2.5 минут, в монопольном режиме (это по базе, где нет версий записей вообще. а сборка мусора, разумеется, приводит к модификациям страниц, которые должны быть записаны на диск). Причем, поскольку читаются все страницы данных и индексов, вытесняется кэш БД, именно поэтому при многопользовательской работе sweep сильно замедляет выполнение всех запросов.

Для чего sweep пытается собрать мусор во всей базе данных? Для того, чтобы подвинуть "вверх" номер Oldest Interesting Transaction (Oldest transaction в gstat -h). Дело в том, что единственные транзакции, которые приводят к "залипанию" Oldest на месте, это транзакции, которые завершились Rollback, и для которых сервер не смог отменить все изменения по сохраненным в памяти savepoints. То есть, пока все версии, созданные этой транзакцией не будут отменены, ее состояние нельзя перевернуть в committed. А транзакции snapshot оценивают состояния конкурирующих транзакций и возможность модификации записей именно от Oldest transaction до Next transaction. Собственно, при чтении версий, номер транзакций которых меньше Oldest transaction, сервер даже не проверяет наличие версий, потому что таковых нет и быть не может, а кроме того, все транзакции меньше Oldest обязательно находятся в состоянии committed (т. е. их версии можно читать).

Таким образом, если у нас в БД возникает транзакция, отмененная по rollback (опять же, для которой сервер не смог отменить изменения по savepoints. Для разных версий сервера это разное значение, например не менее 60 тысяч измененных записей), то это означает, что каждая следующая транзакция будет проверять версии на состояние транзакций от Next до Oldest. И чем больше этот интервал, тем больше страниц Transaction Inventory Page (хранящих состояния транзакций) будет считываться в кэш, и тем больше этих страниц будут запоминать в "локальной памяти" транзакции snapshot.

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

Именно для того, чтобы периодически уменьшать расстояние от Next до Oldest и был введен автоматический sweep.
 

Дополнительно:

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

Подписаться