15.12.25

Что такое спинлок FCB_REPLICA_SYNC?

Автор: Paul Randal, What is the FCB_REPLICA_SYNC spinlock?

В списке рассылки 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

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

Я запустил сессию событий и повторил тест. Ниже приведена выборка стеков вызовов с соответствующими пояснениями.

Объяснение:

  1. Добавление страницы в моментальный снимок непосредственно перед её изменением в исходной базе данных.
  2. Чтение страницы из моментального снимка (в данном случае, из одного из параллельных потоков DBCC CHECKDB, выполняющих упреждающее чтение).
  3. Загрузка страницы в моментальный снимок во время выполнения аварийного восстановления для нового снимка, чтобы сделать его транзакционно-согласованным представлением исходной базы данных (в данном случае это «временный» моментальный снимок базы данных, созданный DBCC CHECKDB).
  4. И множество других похожих стеков вызовов.

Стек вызовов (пример):

  1. 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
  2. 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
  3. 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 связан с чтением и записью моментальных снимков базы данных, и большие числа вокруг него ожидаемы при параллельных обновлениях в исходной базе данных и чтениях в снимке.




Комментариев нет:

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