WSUS — Reset Server Node — SqlException Execution Timeout Expired

Ох уж этот WSUS, столько времени съел за совместную с ним 15 летнюю жизнь. В очередной раз БД SUSDB разрослась до 52GB, очистка сервера через MMC консоль не работает до конца и прерывается с сообщением System.Data.SqlClient.SqlException — Execution Timeout Expired. Invoke-WsusServerCleanup -CleanupObsoleteUpdates обрывается с абсолютно таким же сообщением и временем выполнения. Сама БД WSUS расположена на отдельно стоящем MS SQL Server 2014, БД жива, во время операции по очистке грузит процессор и диск на полную. Я так понимаю, приложение WSUS сервера не дожидается окончания выполнения одного из запросов и сваливается в Timeout.

Расследования, поиск информации, пробы всего и вся были крайне долгими. Таймауты, лимиты и прочее были либо убраны, либо поставлены в максимально возможные значения, но это не помогало. Скрипт перестройки индексов выполнялся. Начал смотреть, откуда такой большой размер у БД. Выяснилось, что таблица tbEventInstance занимала больше половины занятого пространства, через SQL Server Management Studio открыл свойства таблицы:

Index Space 1 916,078 MB
Row Count 7 720 991
Data Space 39 325,508 MB

Недолгий поиск в интернете привёл к статье https://www.betaarchive.com/wiki/index.php/Microsoft_KB_Archive/909131 в которой достаточно доходчиво разъяснено: одна из причин ошибки связана с тем, что содержимое таблицы tbEventInstance превысило 1 миллион записей:

This problem occurs if the number of reporting events in the tbEventInstance table exceeds 1 million rows.

A WSUS server that is using the recommended hardware can support a maximum number of 15,000 clients by using a default detection cycle of 22 hours. The number of reporting events that is added to the tbEventInstance table depends on the number of clients and on the frequency that is set for each detection cycle. Automatic deletion of rows from the tbEventInstance table starts when a client tries to send a report. The automatic deletion process is initiated only if the reporting events in the tbEventInstance table exceed 1 million rows.

The automatic deletion process is very slow and blocks the client computers from reporting back to the WSUS server. By default, WSUS is configured to delete events that are older than 15 days on workstations and that are older than 90 days on servers. WSUS deletes old events at the rate of 1,000 events every 12 hours.

Видимо в механизме очистки что-то застряло, либо некорректно настроено.

Остановил сервер WSUS и веб сервер: WSUS Service и World Wide Web Publishing Service.

Запустил очистку таблицы:

USE SUSDB;
TRUNCATE TABLE tbEventInstance;

Запустил «уменьшение» БД, чтобы освободить, хотя бы временно, место на диске:

DBCC SHRINKDATABASE ('SUSDB');  

БД стала занимать 5.5 ГБ вместо 52 ГБ.

Запустил перестроение и реорганизацию индексов (скрипт от MS):

USE SUSDB; 
GO 
SET NOCOUNT ON; 
 
-- Rebuild or reorganize indexes based on their fragmentation levels 
DECLARE @work_to_do TABLE ( 
    objectid int 
    , indexid int 
    , pagedensity float 
    , fragmentation float 
    , numrows int 
) 
 
DECLARE @objectid int; 
DECLARE @indexid int; 
DECLARE @schemaname nvarchar(130);  
DECLARE @objectname nvarchar(130);  
DECLARE @indexname nvarchar(130);  
DECLARE @numrows int 
DECLARE @density float; 
DECLARE @fragmentation float; 
DECLARE @command nvarchar(4000);  
DECLARE @fillfactorset bit 
DECLARE @numpages int 
 
-- Select indexes that need to be defragmented based on the following 
-- * Page density is low 
-- * External fragmentation is high in relation to index size 
PRINT 'Estimating fragmentation: Begin. ' + convert(nvarchar, getdate(), 121)  
INSERT @work_to_do 
SELECT 
    f.object_id 
    , index_id 
    , avg_page_space_used_in_percent 
    , avg_fragmentation_in_percent 
    , record_count 
FROM  
    sys.dm_db_index_physical_stats (DB_ID(), NULL, NULL , NULL, 'SAMPLED') AS f 
WHERE 
    (f.avg_page_space_used_in_percent < 85.0 and f.avg_page_space_used_in_percent/100.0 * page_count < page_count - 1) 
    or (f.page_count > 50 and f.avg_fragmentation_in_percent > 15.0) 
    or (f.page_count > 10 and f.avg_fragmentation_in_percent > 80.0) 
 
PRINT 'Number of indexes to rebuild: ' + cast(@@ROWCOUNT as nvarchar(20)) 
 
PRINT 'Estimating fragmentation: End. ' + convert(nvarchar, getdate(), 121) 
 
SELECT @numpages = sum(ps.used_page_count) 
FROM 
    @work_to_do AS fi 
    INNER JOIN sys.indexes AS i ON fi.objectid = i.object_id and fi.indexid = i.index_id 
    INNER JOIN sys.dm_db_partition_stats AS ps on i.object_id = ps.object_id and i.index_id = ps.index_id 
 
-- Declare the cursor for the list of indexes to be processed. 
DECLARE curIndexes CURSOR FOR SELECT * FROM @work_to_do 
 
-- Open the cursor. 
OPEN curIndexes 
 
-- Loop through the indexes 
WHILE (1=1) 
BEGIN 
    FETCH NEXT FROM curIndexes 
    INTO @objectid, @indexid, @density, @fragmentation, @numrows; 
    IF @@FETCH_STATUS < 0 BREAK; 
 
    SELECT  
        @objectname = QUOTENAME(o.name) 
        , @schemaname = QUOTENAME(s.name) 
    FROM  
        sys.objects AS o 
        INNER JOIN sys.schemas as s ON s.schema_id = o.schema_id 
    WHERE  
        o.object_id = @objectid; 
 
    SELECT  
        @indexname = QUOTENAME(name) 
        , @fillfactorset = CASE fill_factor WHEN 0 THEN 0 ELSE 1 END 
    FROM  
        sys.indexes 
    WHERE 
        object_id = @objectid AND index_id = @indexid; 
 
    IF ((@density BETWEEN 75.0 AND 85.0) AND @fillfactorset = 1) OR (@fragmentation < 30.0) 
        SET @command = N'ALTER INDEX ' + @indexname + N' ON ' + @schemaname + N'.' + @objectname + N' REORGANIZE'; 
    ELSE IF @numrows >= 5000 AND @fillfactorset = 0 
        SET @command = N'ALTER INDEX ' + @indexname + N' ON ' + @schemaname + N'.' + @objectname + N' REBUILD WITH (FILLFACTOR = 90)'; 
    ELSE 
        SET @command = N'ALTER INDEX ' + @indexname + N' ON ' + @schemaname + N'.' + @objectname + N' REBUILD'; 
    PRINT convert(nvarchar, getdate(), 121) + N' Executing: ' + @command; 
    EXEC (@command); 
    PRINT convert(nvarchar, getdate(), 121) + N' Done.'; 
END 
 
-- Close and deallocate the cursor. 
CLOSE curIndexes; 
DEALLOCATE curIndexes; 
 
 
IF EXISTS (SELECT * FROM @work_to_do) 
BEGIN 
    PRINT 'Estimated number of pages in fragmented indexes: ' + cast(@numpages as nvarchar(20)) 
    SELECT @numpages = @numpages - sum(ps.used_page_count) 
    FROM 
        @work_to_do AS fi 
        INNER JOIN sys.indexes AS i ON fi.objectid = i.object_id and fi.indexid = i.index_id 
        INNER JOIN sys.dm_db_partition_stats AS ps on i.object_id = ps.object_id and i.index_id = ps.index_id 
 
    PRINT 'Estimated number of pages freed: ' + cast(@numpages as nvarchar(20)) 
END 
GO 
 
 
--Update all statistics 
PRINT 'Updating all statistics.' + convert(nvarchar, getdate(), 121)  
EXEC sp_updatestats 
PRINT 'Done updating statistics.' + convert(nvarchar, getdate(), 121)  
GO

Запустил сервисы WSUS Service и World Wide Web Publishing Service

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

Результат, как говорится — на лицо, БД стала занимать меньше места, процедура очистки сервера прошла достаточно быстро и без ошибок.

Ниже приведу некоторые опции и запросы, которыми пользовался.

Посмотреть топ 10 выполняющихся запросов в БД

SELECT TOP 10 s.session_id,
           r.status,
           r.cpu_time,
           r.logical_reads,
           r.reads,
           r.writes,
           r.total_elapsed_time / (1000 * 60) 'Elaps M',
           SUBSTRING(st.TEXT, (r.statement_start_offset / 2) + 1,
           ((CASE r.statement_end_offset
                WHEN -1 THEN DATALENGTH(st.TEXT)
                ELSE r.statement_end_offset
            END - r.statement_start_offset) / 2) + 1) AS statement_text,
           COALESCE(QUOTENAME(DB_NAME(st.dbid)) + N'.' + QUOTENAME(OBJECT_SCHEMA_NAME(st.objectid, st.dbid)) 
           + N'.' + QUOTENAME(OBJECT_NAME(st.objectid, st.dbid)), '') AS command_text,
           r.command,
           s.login_name,
           s.host_name,
           s.program_name,
           s.last_request_end_time,
           s.login_time,
           r.open_transaction_count
FROM sys.dm_exec_sessions AS s
JOIN sys.dm_exec_requests AS r ON r.session_id = s.session_id CROSS APPLY sys.Dm_exec_sql_text(r.sql_handle) AS st
WHERE r.session_id != @@SPID
ORDER BY r.cpu_time DESC

Узнать количество «ненужных» обновлений

USE SUSDB 
GO
EXEC spGetObsoleteUpdatesToCleanup

Изменить или убрать таймаут выполнения запросов

USE SUSDB;
GO
EXEC sp_configure 'remote query timeout', 0 ;
GO
RECONFIGURE ;

Изменить или убрать таймаут в пуле IIS

Get-Module WebAdministration
Import-Module WebAdministration

Get-ChildItem IIS:\AppPools | ? name -eq "WsusPool" |
select name, @{Name="Timeout"; Exp={$_.processmodel.idletimeout}} 

Set-ItemProperty IIS:\AppPools\WsusPool -Name processModel.idleTimeout -Value "00:00:00"

Изменить или убрать ограничение используемой памяти в пуле IIS

Get-ChildItem IIS:\AppPools | ? name -eq "WsusPool" |
select name, @{Name="Memory"; Exp={$_.recycling.periodicrestart.privateMemory}}

Set-ItemProperty IIS:\AppPools\WsusPool -Name recycling.periodicrestart.privateMemory -Value 0

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Яндекс.Метрика