6.7.26

Как исследовать задержки подсистемы ввода-вывода изнутри SQL Server

Автор: Paul Randal, How to examine IO subsystem latencies from within SQL Server

За последние несколько месяцев я читал лекции на курсах и конференциях о том, как получить представление SQL Server о подсистеме ввода-вывода и какие задержки она испытывает, так что пришло время написать об этом в блоге, чтобы помочь всем остальным.

Большинство современных SQL Server чувствительны к вводу-выводу — это общепризнанное утверждение среди администраторов баз данных и консультантов в этой области. Это означает, что основным фактором производительности сервера является его способность быстро выполнять операции ввода-вывода. Если подсистема ввода-вывода не справляется с предъявляемой к ней нагрузкой, то рабочая нагрузка SQL Server будет страдать от проблем с производительностью.

При этом одна из ловушек, в которую часто попадают многие, — это приравнивание повышенных задержек подсистемы ввода-вывода к плохой производительности самой подсистемы. Часто это совсем не так. Обычно подсистема ввода-вывода работает нормально при расчётной нагрузке, но становится узким местом производительности, когда нагрузка ввода-вывода превышает расчётную точку. Увеличение нагрузки ввода-вывода является причиной проблемы, а не подсистема ввода-вывода, — если вы спроектировали подсистему ввода-вывода для поддержки 10000 IOPS (операций ввода-вывода в секунду — убедившись, что вы используете правильный размер ввода-вывода и характеристики нагрузки имеют смысл в терминах количества случайных IOPS), а SQL Server пытается выдать 20000 IOPS, производительность будет страдать.

Если вы обнаружили, что задержка ввода-вывода увеличилась, ищите изменение в поведении SQL Server, прежде чем винить подсистему ввода-вывода. Например:

  • Изменения планов запросов из-за устаревшей статистики, изменений в коде, неявных преобразований, плохого индексирования, вызывающих просмотры таблиц вместо поиска по индексу
  • Создание дополнительных индексов, вызывающее увеличение нагрузки на обслуживание индексов и журналирование
  • Изменение шаблонов доступа/ключей, вызывающее разбиения страниц и, следовательно, дополнительные чтения/записи страниц и журналирование
  • Включение системы отслеживания изменений (change data capture или change tracking), вызывающее дополнительные записи и журналирование
  • Включение изоляции снимков (snapshot isolation), вызывающее операции ввода-вывода в tempdb, а также потенциально разбиения страниц
  • Уменьшение памяти сервера, приводящее к меньшему буферному пулу и увеличению активности lazy writer и чтения

И целый ряд других причин может привести к увеличению нагрузки ввода-вывода, где подсистема ввода-вывода не виновата.

С другой стороны, однако, вполне возможно, что проблема именно в подсистеме ввода-вывода, если рабочая нагрузка SQL Server осталась прежней. Администратор SAN мог решить отдать часть пространства на одном из LUN SQL Server другому серверу, что может привести к перегрузке.

Используя счётчики производительности, вы можете увидеть на уровне физического диска, каковы задержки (показатели Avg. Disk sec/Read и Avg. Disk sec/Write), но если ваши базы данных распределены по нескольким LUN, это не поможет вам определить, какие файлы базы данных испытывают проблемы с задержками и генерируют наибольшее количество операций ввода-вывода к подсистеме.

sys.dm_io_virtual_file_stats

Здесь на помощь приходит sys.dm_io_virtual_file_stats. Оно появилось аж в SQL Server 2005 как улучшенная замена fn_virtualfilestats и показывает, сколько операций ввода-вывода было выполнено, с задержками для всех файлов. Вы можете передать ему идентификатор базы данных и идентификатор файла, но я считаю наиболее полезным просматривать все файлы на сервере и сортировать по одному из показателей.

Вот стандартный вывод (с некоторыми незначительными изменениями названий столбцов для удобства отображения):

SELECT * FROM sys.dm_io_virtual_file_stats (NULL, NULL); GO
db_id file_id sample_ms reads bytes_read io_stall_read_ms writes bytes_written io_stall_write_ms io_stall size_on_disk_bytes file_handle
----- ------- --------- ----- ---------- ---------------- ------ ------------- ----------------- -------- ------------------ ------------------
1     1       14433009  52    3227648    58               10     90112         12                70       515964928          0x000000000000013C
1     2       14433009  55    1241088    36               28     122880        115               151      12648448           0x0000000000000474
2     1       14433009  22    1261568    22               4      147456        1                 23       8388608            0x000000000000065C
2     2       14433009  6     385024     2                7      154112        3                 5        524288             0x0000000000000660
3     1       14433009  31    1859584    18               1      8192          0                 18       1310720            0x0000000000000524
3     2       14433009  6     385024     5                3      16384         1                 6        524288             0x000000000000056C
4     1       14433009  139   8175616    369              13     106496        45                414      260767744          0x0000000000000958
4     2       14433009  136   712704     485              7      53248         25                510      104595456          0x00000000000009F0
5     1       14433009  20    1138688    204              0      0             0                 204      419430400          0x0000000000000980
5     2       14433009  13    411136     466              1      2048          13                479      104857600          0x0000000000000A44
6     1       14433009  15    811008     81               1      8192          8                 89       1310720            0x0000000000000954
6     2       14433009  7     386560     89               4      11264         80                169      516096             0x00000000000009EC
7     1       14433009  15    811008     80               1      8192          9                 89       1310720            0x0000000000000970
7     2       14433009  7     386560     130              3      9216          17                147      516096             0x00000000000009B4
8     1       14433009  16    876544     37               1      8192          103               140      136577024          0x0000000000000988
8     2       14433009  9     229888     11               4      10752         59                70       5242880            0x00000000000009E4
.
.

Это не очень полезно, потому что: а) я не помню наизусть идентификаторы баз данных и пути к файлам, и б) он показывает совокупные задержки (io_stall_read_ms и io_stall_write_ms).

Просмотр агрегированной информации

Обычно я использую скрипт ниже — часть моего стандартного набора скриптов, которые я использую при проверке работоспособности сервера для клиента. Он частично основан на коде моего хорошего друга Джимми Мэя (Jimmy May), с рядом доработок. Он позволяет мне фильтровать по задержкам чтения или записи и объединяется с sys.master_files для получения имён баз данных и путей к файлам.

SELECT [ReadLatency] = CASE WHEN [num_of_reads] = 0 THEN 0 ELSE ([io_stall_read_ms] / [num_of_reads]) END, [WriteLatency] = CASE WHEN [num_of_writes] = 0 THEN 0 ELSE ([io_stall_write_ms] / [num_of_writes]) END, [Latency] = CASE WHEN ([num_of_reads] = 0 AND [num_of_writes] = 0) THEN 0 ELSE ([io_stall] / ([num_of_reads] + [num_of_writes])) END, [AvgBPerRead] = CASE WHEN [num_of_reads] = 0 THEN 0 ELSE ([num_of_bytes_read] / [num_of_reads]) END, [AvgBPerWrite] = CASE WHEN [num_of_writes] = 0 THEN 0 ELSE ([num_of_bytes_written] / [num_of_writes]) END, [AvgBPerTransfer] = CASE WHEN ([num_of_reads] = 0 AND [num_of_writes] = 0) THEN 0 ELSE (([num_of_bytes_read] + [num_of_bytes_written]) / ([num_of_reads] + [num_of_writes])) END, LEFT ([mf].[physical_name], 2) AS [Drive], DB_NAME ([vfs].[database_id]) AS [DB], [mf].[physical_name] FROM sys.dm_io_virtual_file_stats (NULL,NULL) AS [vfs] JOIN sys.master_files AS [mf] ON [vfs].[database_id] = [mf].[database_id] AND [vfs].[file_id] = [mf].[file_id] -- WHERE [vfs].[file_id] = 2 -- файлы журналов -- ORDER BY [Latency] DESC -- ORDER BY [ReadLatency] DESC ORDER BY [WriteLatency] DESC; GO
ReadLatency WriteLatency Latency AvgBPerRead AvgBPerWrite AvgBPerTransfer Drive DB                      physical_name
----------- ------------ ------- ----------- ------------ --------------- ----- ----------------------- ------------------------------------------
2           103          8       54784       8192         52043           D:    HotSpot                 D:\SQLskills\HotSpot_data.mdf
7           48           13      63061       5632         54857           C:    DemoCorruptSystemTables C:\SQLskills\corrupt_log.LDF
32          41           33      43861       2048         39680           D:    DemoSystemIndex3        D:\SQLskills\DemoSystemIndex3_LOG.ldf
27          40           28      43861       1024         39577           D:    DemoSystemIndex5        D:\SQLskills\DemoSystemIndex5_LOG.ldf
23          39           24      43861       7168         40192           D:    DemoSystemIndex4        D:\SQLskills\DemoSystemIndex4_LOG.ldf
7           38           10      43861       4608         39936           D:    DemoSystemIndex1        D:\SQLskills\DemoSystemIndex1_LOG.ldf
11          25           12      43861       7168         40192           C:    FSWaits                 C:\SQLskills\FSWaits_log.ldf
12          20           13      55222       6656         49152           C:    newdatabase             C:\SQLskills\newdatabase_log.LDF
12          20           15      55222       2816         36165           C:    LockResDemo             C:\SQLskills\LockResDemo_log.LDF
14          17           15      45933       2816         30254           C:    TempdbTest              C:\SQLskills\TempdbTest_log.LDF
1           14           5       25543       2688         18510           C:    HotSpot                 C:\SQLskills\HotSpot_log.ldf
40          14           39      31625       7680         29915           D:    RecompileTest           D:\SQLskills\RecompileTest_log.ldf
9           13           10      51858       7680         46336           D:    DemoFatalCorruption1    D:\SQLskills\DemoFatalCorruption1_log.ldf
35          13           34      31625       2048         29513           D:    GUIDTest                D:\SQLskills\GUIDTest_log.ldf
12          13           12      16861       1024         15872           C:    FNDBLogTest             C:\SQLskills\FNDBLogTest_log.LDF
.
.

Это гораздо полезнее, так как позволяет быстро увидеть, где находятся «горячие точки» чтения и записи, а затем углубиться в базу данных, чтобы понять, что происходит, и, если ничего необычного не обнаружено, попросить администратора SAN переместить эти файлы с «горячими точками» на выделенные и/или более быстрые хранилища.

Использование этой информации

Предостережение: Эта информация является агрегацией всех операций ввода-вывода, выполненных с момента ввода базы данных в эксплуатацию, поэтому время ввода-вывода, сообщаемое скриптом, является средним. Чем дольше охватываемый период, тем более вероятно, что кратковременные всплески задержек будут замаскированы. Лучший способ использовать эти данные для анализа — периодически захватывать снимки данных sys.dm_io_virtual_file_stats, а затем анализировать средние значения за этот небольшой период времени. Таким образом, вы сможете увидеть всплески задержек.

Один вопрос, который вы, я уверен, зададите: «какая задержка чтения или записи является приемлемой?» — и ответ — большое «зависит!». Это зависит от подсистемы ввода-вывода и того, как она настроена. Ключевым моментом является создание базового показателя производительности для периода, когда всё работает приемлемо хорошо, и затем сравнение результатов из DMV с вашим базовым показателем. На хорошо настроенном хранилище, которое не перегружено, я ожидал бы увидеть задержки в одну цифру (миллисекунды) как для чтения, так и для записи, но это будет варьироваться в зависимости от технологии дисков (SCSI против SATA против SSD и т. д.).

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

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