Очистка метаданных в репликации слиянием

ПУБЛИКАЦИИ  

По материалам статьи: Michael R. Hotek " Purging Merge Metadata"

Таблица MSmerge_contents может стать проблемой для любого администратора баз данных, который обслуживает репликацию слиянием (Merge) достаточно много времени. Проблема состоит в том, что MSmerge_contents будет постоянно расти, если этим не управлять. Merge репликация очень интенсивно использует MSmerge_contents, что и вызывает проблемы, поскольку её размер неуклонно растёт. Это обычно проявляется в виде взаимоблокировок и существенного замедления обмена изменений в репликации. Происходит это потому, что время поиска искомых строк в таблице метаданных увеличивается вместе с увеличением её размера.
Перед тем, как непосредственно приступить к разбору сценария разрешения описанной выше проблемы, стоит понять, что происходит и почему необходимо чистить метаданные.
Главными таблицами для репликации слиянием являются: MSmerge_contents, MSmerge_tombstone и MSmerge_genhistory. В дополнение к ним, есть набор таблиц на дистрибуторе, которые не существенны для репликации слиянием, т.к. просто содержат сообщения о состоянии агентов (запуск, остановка, повтор и т.д.). Эти таблицы на дистрибуторе можно не резервировать (если у Вас только репликация слиянием), т.к. при потере их достаточно просто обновить. MSmerge_tombstone содержит по строке для каждой записи, которая была удалена в издаваемой таблице. Каждая публикация имеет в настройках период задержки. Задание на очистку удаляет всё, что старше этого периода задержки. Это относится только к сообщениям в базе дистрибутора и к строкам в таблице MSmerge_tombstone, но больше ни к каким таблицам. Когда происходит транзакция на издаваемой таблице, срабатывает триггер, который создаёт записи в таблицах метаданных слияния. Вставки и изменения отражаются в MSmerge_contents. Все новые строки получают поколение 0 (нуль). Таблица MSmerge_genhistory - краеугольный камень к системе репликации слиянием и от неё зависит производительность и гибкость репликации слиянием. Она содержит по записи для каждой строки, изменяемой в каждой статье и в каждой публикации. Когда запущен агент слияния, он считывает все строки с поколением ноль. Потом он просматривает таблицу MSmerge_genhistory и получает самое большое значение для поколения. После этого, он вычисляет следующее значение поколения на основании значений, которые он получил из MSmerge_genhistory, и пересылает строки между издателем и подписчиком. Как только они приняты, запись для каждой строки в разрезе статей, посланной в этом поколении, вставляется в таблицу MSmerge_genhistory на сервере получателе. Если запись по этой строке уже присутствует в MSmerge_genhistory, поколение модифицируется (на самом деле, это упрощение реальной схемы, которая немного сложнее, но это упрощение достаточно близко к реальности, чтобы можно было понять то, что происходит и почему).
Последний столбец в таблице MSmerge_genhistory называют coldate. Именно эта дата используется для управления метаданными репликации слиянием. Колонка coldate говорит о том, когда в последний раз была сделана модификация каждой строки, затронутой в каждой статье. Если зафиксировано 500 модификаций одной строки в MSmerge_genhistory, в MSmerge_contents будет содержаться только одна строка для всех 500 транзакций. Строка в таблице MSmerge_genhistory будет содержать поколение и время последнего изменения. MSmerge_contents всегда будет содержать только одну строку для всех 500 изменений.
На основании представленного выше упрощённого описания уже можно сделать предположение относительно того, почему замедляется репликация при увеличении размера таблиц MSmerge_contents и MSmerge_genhistory. Каждый раз после запуска агенту слияния необходимо выполнить большую работу:

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

Это огромный объем работы. Выполняя чистку старых метаданных, Вы ускоряете этот процесс, сокращая количество данных, которые агент слияния должен обработать. Это также снижает вероятность появления тупиковых блокировок в течение сеанса репликации.
С другой стороны, метаданные очень важны, если Вам когда-либо потребуется восстановить из резервной копии базу данных. Это означает, что Вы должны быть чрезвычайно осторожны при их очистке.
Автор статьи пробовал уменьшить размер MSmerge_contents до нескольких сотен тысяч строк с одновременным сокращением MSmerge_genhistory. Этого он добивался, сохраняя метаданные о поколениях изменений за последние 2 недели. Скрипт, который приводится в конце статьи, содержит таблицы и хранимые процедуры, которые использовались им для обслуживания таблиц MSmerge_contents и MSmerge_genhistory, и позволяющий оптимизировать продолжительность сеансов репликации слиянием.
В системах, администрируемых автором статьи, существует база данных, называемая admin. Единственная цель этой базы данных состоит в том, чтобы содержать все администраторские процедуры, представления и таблицы, которые используются для обслуживания сервера баз данных. Иногда, для подобных целей, используют базу данных master. Автор статьи является убеждённым противником такого подхода. Вы создаете пользователя, который привязан к пользовательской, а не к системной базе данных. Мало того, что указанный подход делает восстановление очень трудным, он вводит элемент неустойчивости в вашу систему. Аргумент использования sp_ в качестве системных процедур не проходит, потому что Вы всегда можете оформить это по-другому, просто затратив на это немного больше усилий.
Предлагаемый Вам скрипт начинается с создания базы данных admin. После этого создаются две таблицы:

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

Далее, создаётся процедура, sp__mergepurgemetadata. Эта процедура произведет очистку старых метаданных, ориентируясь на период задержки, который ей передаётся.
Эта процедура выполняется с теми же самыми ограничениями, что и у стандартной процедуры sp_mergecleanupmetadata. Поскольку метаданные чрезвычайно полезны на стадии восстановления, необходимо гарантировать, что удаляемые метаданные не будут востребованы. Автор рекомендует удалять строки старше двух недель. Это позволит гарантировать, что строки были переданы всем подписчикам. Если имеются мобильные пользователи - подписчики, необходимо учесть их режим синхронизации данных. Если Вы производите чистку метаданных, которые реплицированы не всем подписчикам, эти транзакции будут потеряны. Если Вы должны восстановить базу из резервной копии которая старше чем метаданные, имеющиеся в базе данных, Вы не сможете выполнить синхронизацию после восстановления.
Эти рассуждения относятся к таблице mergepurgeflag, в которой Вы явно устанавливаете флаг в 1 только тогда, когда уверены, что все транзакции находятся в пределах указанного периода, и когда Вы осуществляете очистку, все изменения отреплицированы. Мало гарантировать, что все подписчики и издатели синхронны на это время, Вы также должны проверить ваши резервные копии. Если Вы собираетесь производить очистку метаданных, которые старше двух недель, убедитесь, что Вы имеете резервную копию, которая не старее двух недель. Перед очисткой восстановите резервную копию на другую машину, и проверьте целостность её носителя и данных. Только после того, как Вы всё это сделали, можно установить флаг purgeflag и разрешить очистку метаданных.
Представленный ниже скрипт может помочь Вам обслуживать метаданные в вашей системе и оптимизирует продолжительность сеансов репликации слиянием, повышая эффективность и снижая трафик в сети. Есть два очень важных предостережения при использовании этой процедуры.

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

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


use master
go

create database admin
on
(name = admin_dat,
filename = 'c:\mssql7\data\admindat.mdf',
size = 10)
log on
(name = 'admin_log',
filename = 'c:\mssql7\data\adminlog.ldf',
size = 5MB)
go

use admin
go

create table mergepurgeflag
(PurgeFlag    bit not null,
DatabaseName  varchar(128)not null)
go

create table mergepurgehistory
(PurgeDate    datetime not null,
DatabaseName  varchar(128) not null,
NumRows       int not null)
go

create procedure sp__mergepurgemetadata @numdays  int = 14
as
/****************************************************************************/
/*  Stored Procedure: sp__mergepurgemetadata                                */
/*  Creation Date: 1/12/00                                                  */
/*  Copyright:  Michael Hotek                                               */
/*  Written by: Michael Hotek					            */
/*                                                                          */
/*  Purpose: This procedure will clean out generations from MSmerge_contents*/
/*           that are older than the specified number of days               */
/*                                                                          */
/*  Input Paramters: 	                                                    */
/*  @numdays  Number of days to retain history in MSmerge_contents          */
/*                                                                          */
/*  Output Parameters: None                                                 */
/*                                                                          */
/*  Return Status: None                                                     */
/*                                                                          */
/*  Usage: exec sp__mergepurgemetadata 21                                   */
/*                                                                          */
/*  Local Variables:                                                        */
/*       @purgedate   Cutoff date for the purge                             */
/*       @command     The SQL statement that is dynamically executing       */
/*       @database    Database to purge                                     */
/*       @numrows     Number of rows purged                                 */
/*                                                                          */
/*  Called By:                                                              */
/*    SQL Server Agent                                                      */
/*                                                                          */
/*  Calls: None                                                             */
/*                                                                          */
/*  Data Modifications:                                                     */
/*        Deletes from MSmerge_contents                                     */
/*        Deletes from MSmerge_genhistory                                   */
/*        Updates mergepurgeflag                                            */
/*        Inserts into mergepurgehistory                                    */
/*                                                                          */
/*  Updates:                                                                */
/*    Date      Author      Purpose                                         */
/*    1/12/00   Mike Hotek  Created					    */
/*                                                                          */
/****************************************************************************/
declare  @purgedate   datetime,
         @command     varchar(1000),
         @database    varchar(128),
         @numrows     int

create table #temp
(NumRows   int)

select @purgedate = dateadd(dd, -1 * @numdays + 1, getdate())

declare curdb cursor for select DatabaseName 
              from mergepurgeflag where PurgeFlag = 1
open curdb
fetch curdb into @database

while @@fetch_status = 0
begin
     --Get the number of rows to remove
     select @command = 'select count(*) from ' + @database + 
	                   '..MSmerge_contents a, ' + 
                       @database + 
					   '..MSmerge_genhistory b 
					   where a.generation = b.generation and ' + 
                       "b.coldate < '" + convert(char(10),@purgedate,101) + "'"

     insert into #temp
     exec(@command)

     select @numrows = NumRows from #temp

     truncate table #temp

     --Remove the specified generations from MSmerge_contents
     select @command = 'delete ' + @database + '..MSmerge_contents from ' + 
	                   @database + '..MSmerge_contents a, ' + @database + 
					   '..MSmerge_genhistory b 
					   where a.generation = b.generation and ' + 
                       "b.coldate < '" + convert(char(10),@purgedate,101) + 
					   "'" + ' and a.generation > 0'

     begin transaction
     exec(@command)
     commit transaction

     --Remove the specified generations from MSmerge_genhistory 
     select @command = 'delete ' + @database + 
	                   '..MSmerge_genhistory where coldate < "' + 
                       convert(char(10),@purgedate,101) + 
					   '" and pubid is not null'
  
     begin transaction
     exec(@command)
     commit transaction

     insert into admin..mergepurgehistory
     select getdate(), @database, @numrows

     update admin..mergepurgeflag
     set PurgeFlag = 0
     where DatabaseName = @database

     fetch curdb into @database
end

close curdb
deallocate curdb
drop table #temp
go

[В начало]


Перевод: Александра Гладченко  2003г.

ПУБЛИКАЦИИ

Скачать электронную карту Ангарска бесплатно
Сайт управляется системой uCoz