В списке рассылки MVP по платформе данных возник вопрос о том, что такое спинлок FCB_REPLICA_SYNC. Я ответил на вопрос и пообещал сделать небольшую запись в блоге, поскольку в сети не нашёл о нём никакой информации.
Объяснение
В двух словах, этот спинлок используется для синхронизации доступа к списку страниц, присутствующих в моментальном снимке базы данных, следующим образом:
- Если страница в базе данных с одним или несколькими моментальными снимками обновляется, проверяется список каждого снимка, чтобы узнать, есть ли страница уже в снимке. Если да, ничего делать не нужно. Если нет, копируется образ страницы до изменения в снимок.
- Если запрос читает страницу в контексте моментального снимка базы данных, проверяется список страниц, чтобы решить, читать ли из снимка или из исходной базы данных.
Эта синхронизация гарантирует, что запрос, использующий снимок, прочтёт правильную копию страницы и что обновлённые страницы не будут скопированы в снимок более одного раза.
Изначальный вопрос возник потому, что человек видел триллионы оборотов (spins) для спинлока FCB_REPLICA_SYNC. Это совершенно нормально, если существует хотя бы один моментальный снимок базы данных, рабочая нагрузка на чтение из снимка и параллельная интенсивная рабочая нагрузка на обновление в исходной базе данных.
Пример
Например, используя нашу тестовую базу данных SalesDB (zip-архив здесь), я создал этот запрос и запустил его:
WHILE (1=1)
BEGIN
UPDATE [SalesDB].[dbo].[Sales] SET [Quantity] = [Quantity] + 1;
END;
GO
Затем я взял свой скрипт для сбора метрик спинлоков за период времени (см. эту статью), изменил его для сбора за 20 секунд и запустил DBCC CHECKDB для базы данных SalesDB, что заняло 18 секунд.
Возвращённые метрики спинлоков были:
Spinlock DiffCollisions DiffSpins SpinsPerCollision DiffSleepTime DiffBackoffs
------------------------- -------------- --------- ----------------- ------------- ------------
BUF_HASH 2 500 250 0 0
DBTABLE 5 1250 250 0 0
FCB_REPLICA_SYNC 5716270 1513329500 264 0 154380
LOCK_HASH 12 3500 291 0 1
LOGCACHE_ACCESS 6 387 64 0 3
LOGFLUSHQ 4 75840 18960 0 3
LOGPOOL_HASHBUCKET 15 3750 250 0 0
LOGPOOL_SHAREDCACHEBUFFER 32 8000 250 0 0
LSLIST 8 2000 250 0 0
SOS_SCHEDULER 3 1114 371 0 0
SOS_TASK 1 356 356 0 0
Вы можете видеть, что даже для 20-секундного теста одиночный DBCC CHECKDB вызвал 1,5 миллиарда оборотов спинлока FCB_REPLICA_SYNC.
Это совершенно нормально.
Одна из опасностей при просмотре метрик спинлоков заключается в том, что задействованные числа могут быть настолько высокими, что легко убедить себя в наличии какой-то проблемы, особенно учитывая, как мало информации доступно в сети о том, что на самом деле означают спинлоки. В подавляющем большинстве случаев проблемы нет, но требуется много знаний о внутреннем устройстве, чтобы понимать, что происходит.
Единственное, о чём я бы беспокоился, — это наличие нескольких параллельных моментальных снимков в базе данных с интенсивной рабочей нагрузкой на обновление, так как это вызовет синхронные записи во все снимки при первом обновлении страницы в исходной базе данных, замедляя рабочую нагрузку.
Исследование
Если вам интересно, что означает конкретный спинлок, вы всегда можете исследовать его с помощью расширенных событий (Extended Events). Есть технический документ, который я помогал рецензировать, под названием «Диагностика и устранение конфликтов спинлока в SQL Server». В нём описана сессия расширенных событий, которую я использую, чтобы увидеть, где происходят откаты (backoffs) спинлоков.
Вот сессия, которую я использовал для FCB_REPLICA_SYNC (который соответствует значению типа 136 в sys.dm_xe_map_values):
-- Удаляем сессию, если она существует.
IF EXISTS (SELECT * FROM sys.server_event_sessions WHERE [name] = N'WatchSpinlocks')
DROP EVENT SESSION [WatchSpinlocks] ON SERVER
GO
CREATE EVENT SESSION [WatchSpinlocks] ON SERVER
ADD EVENT [sqlos].[spinlock_backoff]
(ACTION ([package0].[callstack])
WHERE [type] = 136) -- Только FCB_REPLICA_SYNC
ADD TARGET [package0].[asynchronous_bucketizer] (
SET filtering_event_name = N'sqlos.spinlock_backoff',
source_type = 1, -- source_type = 1 — это действие
source = N'package0.callstack') -- Группировка по стек вызовов
WITH (MAX_MEMORY = 50MB, MAX_DISPATCH_LATENCY = 5 seconds)
GO
-- Запускаем сессию
ALTER EVENT SESSION [WatchSpinlocks] ON SERVER STATE = START;
GO
-- Флаг трассировки для разрешения стека вызовов
DBCC TRACEON (3656, -1);
GO
-- Вызываем некоторые откаты спинлоков
-- Получаем стеки вызовов из цели bucketizer
SELECT
[event_session_address],
[target_name],
[execution_count],
CAST ([target_data] AS XML)
FROM sys.dm_xe_session_targets [xst]
INNER JOIN sys.dm_xe_sessions [xs]
ON ([xst].[event_session_address] = [xs].[address])
WHERE [xs].[name] = N'WatchSpinlocks';
GO
-- Останавливаем сессию событий
ALTER EVENT SESSION [WatchSpinlocks] ON SERVER
STATE = STOP;
GO
Вам потребуется загрузить отладочные символы для используемой вами сборки — см. мои инструкции здесь о том, как это сделать.
Я запустил сессию событий и повторил тест. Ниже приведена выборка стеков вызовов с соответствующими пояснениями.
Объяснение:
- Добавление страницы в моментальный снимок непосредственно перед её изменением в исходной базе данных.
- Чтение страницы из моментального снимка (в данном случае, из одного из параллельных потоков
DBCC CHECKDB, выполняющих упреждающее чтение). - Загрузка страницы в моментальный снимок во время выполнения аварийного восстановления для нового снимка, чтобы сделать его транзакционно-согласованным представлением исходной базы данных (в данном случае это «временный» моментальный снимок базы данных, созданный
DBCC CHECKDB). - И множество других похожих стеков вызовов.
Стек вызовов (пример):
- XeSosPkg::spinlock_backoff::Publish+0x138
SpinlockBase::Sleep+0xc5
SpinlockBase::Backoff+0x145
Spinlock<136,4,1>::SpinToAcquireWithExponentialBackoff+0x169
FCBReplicaSync::StartWrite+0x7f
FCB::CopyPageToReplicas+0x212
BUF::CopyOnWrite+0x60
BPool::PrepareToDirty+0x180
IndexPageRef::Modify+0x146
BTreeRow::UpdateRecord+0x20ab
IndexDataSetSession::SetDataInternal+0x9a03
DatasetSession::SetData+0x16d
RowsetNewSS::SetData+0x6a
CValRow::SetDataX+0x63
CEsExec::GeneralEval4+0xe7
CQScanUpdateNew::GetRow+0x24b
CQueryScan::GetRow+0x81
CXStmtQuery::ErsqExecuteQuery+0x5be
CXStmtDML::XretDMLExecute+0x31c
CXStmtDML::XretExecute+0xad
CMsqlExecContext::ExecuteStmts<0,1>+0x8bd
CMsqlExecContext::FExecute+0xa68
CSQLSource::Execute+0x86c
CStmtPrepQuery::XretPrepQueryExecute+0x464
- XeSosPkg::spinlock_backoff::Publish+0x138
SpinlockBase::Sleep+0xc5
Spinlock<136,4,1>::SpinToAcquireWithExponentialBackoff+0x169
FCBReplicaSync::StartRead+0x86
FCB::ScatterRead+0x1b3
RecoveryUnit::ScatterRead+0xa9
BPool::GetFromDisk+0x719
BPool::ReadAhead+0x7e
MultiObjectScanner::GetNextPageAndReadAhead+0x38e
MultiObjectScanner::GetNext+0x98
MultiObjectScanner::GetNextPageAndBatch+0x2fc
CheckTables::ProcessNextData+0x1bb
CheckAggregateSingleInstance::GetNextFact+0x28e
CTRowsetInstance::FGetNextRow+0x3c
CUtRowset::GetNextRows+0xa0
CQScanRmtScanNew::GetRowHelper+0x3b8
CQScanXProducerNew::GetRowHelper+0x53
CQScanXProducerNew::GetRow+0x15
FnProducerOpen+0x57
FnProducerThread+0x8c3
SubprocEntrypoint+0xa7f
SOS_Task::Param::Execute+0x21e
SOS_Scheduler::RunTask+0xab
SOS_Scheduler::ProcessTasks+0x279
- XeSosPkg::spinlock_backoff::Publish+0x138
SpinlockBase::Sleep+0xc5
Spinlock<136,4,1>::SpinToAcquireWithExponentialBackoff+0x169
FCBReplicaSync::StartWrite+0x7f
FCB::PullPageToReplica+0x35
FCB::CopyPageToReplicas+0x12c
BUF::CopyOnWrite+0x60
BPool::PrepareToDirty+0x180
PageRef::ModifyRow+0x24a
IndexPageRef::Modify+0x19f2
BTreeRow::UpdateRecord+0x20ab
IndexDataSetSession::UndoSetData+0x4d9
XdesRMReadWrite::IndexModify+0x61
XdesRMReadWrite::UndoPageOperation+0x10da
XdesRMReadWrite::RollbackToLsn+0x7d6
RecoveryMgr::UndoRegularXacts+0xb09
RecoveryMgr::RollbackRemaining+0x137
RecoveryUnit::DoRollbackRecovery+0x19
RecoveryUnit::CompleteRecovery+0x6b8
RecoveryUnit::PhaseStart+0x87
DBTABLE::StartupPostRecovery+0x4d
DBTABLE::ReplicaCreateStartup+0x284
DBMgr::SyncAndLinkReplicaRecoveryPhase+0x787
DBMgr::CreatePhasedTransientReplica+0x717
Итог
Вот и всё. Спинлок FCB_REPLICA_SYNC связан с чтением и записью моментальных снимков базы данных, и большие числа вокруг него ожидаемы при параллельных обновлениях в исходной базе данных и чтениях в снимке.

Комментариев нет:
Отправить комментарий