Автор: Pinal Dave, A Better Fire Alarm Is Still a Fire
Многие инциденты с SQL Server, по которым меня вызывают, можно было предотвратить с помощью средств, уже имеющихся в среде заказчика. Вот аудит, который я хотел бы видеть выполненным до того, как прозвучал пейджер, — с полным T-SQL.
Недавно я завершил анализ первопричин, которым тихо гордился. В нём было всё, что положено иметь уважаемому документу об инциденте: временная шкала с точностью до секунды, регрессия плана, с которой всё началось, параметр, стоящий за регрессией, и развёртывание тремя неделями ранее, которое изменило этот параметр.
Документ был точен. Это была неудобная часть. Ни один важный факт не требовал ретроспективного взгляда. Каждое предупреждение существовало до сбоя. Я не разгадал тайну. Я написал отличный отчёт о событии, которого можно было избежать.
Так что эта статья — другой документ, тот, который должен существовать до инцидента. Это восемь проверок, которые я теперь выполняю в каждом проекте, объяснение, почему каждая из них важна, и T-SQL, чтобы увидеть, в каком положении находитесь вы. Первый проход занимает около часа. Тестирование и внедрение любых изменений по-прежнему должны выполняться в рамках обычного контроля изменений.
Краткое примечание. Детали объединены из разных проектов и изменены так, чтобы ничто не идентифицировало клиента. Проверяйте всё, что написано ниже, сначала вне производства, как вы поступили бы с любой информацией из интернета.
Есть простой способ испортить этот аудит до того, как он начнётся: предположить, что любое издание может делать всё. Сначала запишите точную версию и редакцию. Автоматическая настройка (automatic tuning) остаётся функцией Enterprise в коробочном SQL Server. Developer edition до SQL Server 2022 включительно и Enterprise Developer в SQL Server 2025 предоставляют функциональность Enterprise для непроизводственной разработки. Resource Governor доступен в Enterprise и Developer до SQL Server 2022. SQL Server 2025 делает его доступным в Enterprise, Enterprise Developer, Standard и Standard Developer.
SELECT SERVERPROPERTY('ProductVersion') AS product_version,
SERVERPROPERTY('ProductMajorVersion') AS major_version,
SERVERPROPERTY('Edition') AS edition,
SERVERPROPERTY('EngineEdition') AS engine_edition;
Запросы аудита доступны только для чтения, но «только для чтения» не означает «без прав». Требования различаются в зависимости от представления и версии SQL Server. Общие разрешения включают VIEW DATABASE STATE или VIEW DATABASE PERFORMANCE STATE для информации на уровне базы данных и VIEW SERVER STATE или VIEW SERVER PERFORMANCE STATE для информации на уровне экземпляра. Изменение настройки требует отдельных прав и утверждения изменения.
Проверки 1 и 2 выполняются в области базы данных. Детальные запросы статистики и размера таблиц из проверок 5 и 6 также относятся к области базы данных, поэтому выполняйте их в каждой базе данных с поддержкой записи, которую вы намерены проверить. Остальные запросы исследуют общие для экземпляра настройки, tempdb, Resource Governor или все подключённые пользовательские базы данных.
Профилактический проход за один час
| Проверка | Класс отказа | Первые данные для сбора |
|---|---|---|
| 1 | Регрессия плана | Состояние автоматической настройки и текущие рекомендации |
| 2 | Отсутствие истории производительности | Состояние Query Store, ёмкость и свежесть сбора |
| 3 | Давление параллельных рабочих процессов | Порог стоимости, MAXDOP, ожидания и данные нагрузки |
| 4 | Конкуренция за выделение в tempdb | Раскладка файлов и устойчивые ожидания страниц выделения |
| 5 | Смещение оценки кардинальности | Настройки статистики, свежесть, выборка и оценки плана |
| 6 | Риск развёртывания больших таблиц | Количество строк, пространство, влияние блокировок, влияние на журнал и откат |
| 7 | Коллизия рабочей нагрузки | Политика Resource Governor и классификация сеансов |
| 8 | Медленно развивающиеся условия сбоя | Устойчивые блокировки и давление журнала транзакций |
Проверка 1: автоматическая коррекция планов с подтверждением
Это та проверка, которая больше всего меня раздражает в поддерживаемых редакциях. Возможность существует с SQL Server 2017, но я до сих пор встречаю системы, где никто даже не проверил её состояние. Включение — это одна команда. Решение о включении — всё ещё изменение, и это не одно и то же.
Когда SQL Server определяет регрессию выбора плана для подходящего запроса, автоматическая коррекция плана может принудительно применить последний известный хороший план, проверить результат и отменить принуждение, если производительность не улучшилась. Она перехватывает не каждую регрессию, но может сделать полезный класс плановых инцидентов самокорректирующимся.
SELECT name,
desired_state_desc,
actual_state_desc,
reason_desc
FROM sys.database_automatic_tuning_options;
Если actual_state_desc равно OFF, не переходите сразу к команде ALTER. Прочитайте reason_desc, убедитесь, что версия и редакция поддерживают функцию, и проверьте, что Query Store работоспособен. Одна команда может включить функцию, но она не может создать историю, которая ей нужна.
ALTER DATABASE CURRENT
SET AUTOMATIC_TUNING (FORCE_LAST_GOOD_PLAN = ON);
Автоматическая коррекция плана требует, чтобы Query Store был в режиме READ_WRITE, что является проверкой 2. Через неделю запросите у базы данных подтверждения вместо того, чтобы предполагать, что ON означает полезность:
SELECT type,
reason,
score,
execute_action_initiated_by,
execute_action_initiated_time,
revert_action_initiated_by,
revert_action_initiated_time,
JSON_VALUE(state, '$.currentValue') AS current_state,
JSON_VALUE(state, '$.reason') AS state_reason,
JSON_VALUE(details, '$.planForceDetails.queryId') AS query_id,
JSON_VALUE(details, '$.planForceDetails.regressedPlanId') AS regressed_plan,
JSON_VALUE(details, '$.planForceDetails.recommendedPlanId') AS recommended_plan,
JSON_VALUE(details, '$.implementationDetails.script') AS script
FROM sys.dm_db_tuning_recommendations
ORDER BY score DESC;
Оценка (score) — это расчётная ценность или эффект рекомендации по шкале от 0 до 100, где большие значения считаются лучше. Если вы не готовы включить автоматическую коррекцию, оставьте её выключенной и просматривайте это DMV в течение двух недель. SQL Server всё ещё может выявлять потенциальные регрессии, когда опция отключена. Относитесь к каждой рекомендации как к поводу для расследования, а не как к гарантированному будущему сбою. Это DMV не сохраняется, поэтому перезапуск ядра базы данных очищает его рекомендации. Если история важна, собирайте её в другом месте.
Проверка 2: Query Store включён, но работает ли он?
SQL Server 2022 включает Query Store по умолчанию для вновь созданных баз данных. Базы данных, восстановленные из более ранних версий, и базы данных, перенесённые через обновление на месте, сохраняют предыдущую настройку. В любом случае «включён» не равно «работает».
Отказ, который я нахожу чаще всего, — это Query Store в состоянии READ_ONLY, потому что его хранилище заполнилось месяцы назад. Оно перестало собирать данные бесшумно, никто не был оповещён, и первый, кто замечает это, — тот, кому отчаянно нужна вчерашняя история. Это ужасное время, чтобы обнаружить, что камера наблюдения не записывала.
SELECT actual_state_desc,
desired_state_desc,
readonly_reason,
current_storage_size_mb,
max_storage_size_mb,
query_capture_mode_desc,
size_based_cleanup_mode_desc,
stale_query_threshold_days
FROM sys.database_query_store_options;
В базе данных с активной рабочей нагрузкой также убедитесь, что Query Store содержит недавние данные о выполнении:
SELECT MAX(last_execution_time) AS latest_captured_execution
FROM sys.query_store_runtime_stats;
Значение NULL или неожиданно старая временная метка в загруженной базе данных — повод проверить политику сбора и работоспособность Query Store. Интерпретируйте это с учётом рабочей нагрузки и интервала сбора, потому что простаивающая база данных не должна выдавать свежие выполнения.
Если actual_state_desc и desired_state_desc не совпадают, что-то принудительно перевело его в READ_ONLY, и readonly_reason сообщает, что именно. Это битовая маска. 65536 означает, что превышен размер хранилища — это самый частый случай.
Вот разумная начальная конфигурация, а не универсальный рецепт. Настройте ёмкость и срок хранения под рабочую нагрузку, а затем отслеживайте их:
ALTER DATABASE CURRENT SET QUERY_STORE (
OPERATION_MODE = READ_WRITE,
MAX_STORAGE_SIZE_MB = 2048,
QUERY_CAPTURE_MODE = AUTO,
SIZE_BASED_CLEANUP_MODE = AUTO,
CLEANUP_POLICY = (STALE_QUERY_THRESHOLD_DAYS = 60),
MAX_PLANS_PER_QUERY = 200,
INTERVAL_LENGTH_MINUTES = 60,
DATA_FLUSH_INTERVAL_SECONDS = 900
);
Два замечания важны. QUERY_CAPTURE_MODE = ALL может заполнить хранилище одноразовыми ad hoc-запросами в некоторых рабочих нагрузках. Кроме того, установите оповещение о состоянии. Красная строка в эпизодическом аудите — это не стратегия оповещения:
IF DATABASEPROPERTYEX(DB_NAME(), 'Updateability') = 'READ_WRITE'
AND EXISTS (SELECT 1 FROM sys.database_query_store_options
WHERE actual_state_desc <> 'READ_WRITE')
RAISERROR('Query Store не записывает данные', 16, 1);
Выполняйте эту проверку как шаг задания SQL Server Agent и настройте задание на уведомление кого-либо при сбое шага. RAISERROR сам по себе не отправляет электронное письмо.
Заодно проверьте, что уже принудительно зафиксировано, включая планы с зафиксированными сбоями принуждения:
SELECT p.query_id,
p.plan_id,
p.is_forced_plan,
p.plan_forcing_type_desc,
p.force_failure_count,
p.last_force_failure_reason_desc
FROM sys.query_store_plan AS p
WHERE p.is_forced_plan = 1
OR p.force_failure_count > 0;
Ненулевой force_failure_count означает, что принуждение плана не удалось по крайней мере один раз. Счётчик увеличивается, когда принуждение не удаётся во время перекомпиляции, а не при каждом выполнении. Проверьте last_force_failure_reason_desc, текущий план и недавние компиляции, прежде чем предполагать, что запрос всё ещё защищён.
Проверка 3: параллелизм без нашествия рабочих процессов
SELECT name, value_in_use
FROM sys.configurations
WHERE name IN ('cost threshold for parallelism',
'max degree of parallelism');
Порог стоимости по-прежнему по умолчанию равен 5. Пять — это заводское значение по умолчанию, а не рекомендация и уж точно не семейная традиция. SQL Server рассматривает параллельные альтернативы, когда расчётная стоимость наилучшего последовательного плана превышает порог. Эта стоимость — оценка оптимизатора, а не секунды. Если порог слишком низок для OLTP-нагрузки, слишком много скромных запросов могут получить параллельные планы и способствовать давлению на рабочие процессы. Ожидания CXPACKET и CXCONSUMER — это данные для интерпретации, а не диагноз сами по себе.
DECLARE @target_cost_threshold int = 20; -- Пример. Выберите на основе данных.
DECLARE @apply_change bit = 0; -- Измените на 1 только после утверждения.
SELECT value_in_use AS current_value,
@target_cost_threshold AS proposed_value,
@apply_change AS apply_change
FROM sys.configurations
WHERE name = 'cost threshold for parallelism';
IF @apply_change = 0
RETURN;
IF @target_cost_threshold NOT BETWEEN 0 AND 32767
THROW 50000, 'Выберите порог стоимости от 0 до 32767.', 1;
DECLARE @advanced_options_was_on bit =
(
SELECT CONVERT(bit, value_in_use)
FROM sys.configurations
WHERE name = 'show advanced options'
);
IF @advanced_options_was_on = 0
BEGIN
EXEC sys.sp_configure 'show advanced options', 1;
RECONFIGURE;
END;
EXEC sys.sp_configure 'cost threshold for parallelism', @target_cost_threshold;
RECONFIGURE;
IF @advanced_options_was_on = 0
BEGIN
EXEC sys.sp_configure 'show advanced options', 0;
RECONFIGURE;
END;
Скрипт намеренно безопасен по умолчанию и использует 20 только как пример. Повышайте порог небольшими, проверенными шагами, наблюдайте за полным бизнес-циклом и сравнивайте данные Query Store до и после. Устанавливайте MAXDOP также осознанно, исходя из версии SQL Server, доступных логических процессоров, архитектуры NUMA и поведения рабочей нагрузки, а не предполагая, что 0 подходит.
Ни одна из этих настроек не требует перезапуска, но обе могут изменить выбор планов на уровне экземпляра. Относитесь к ним как к измеряемым изменениям рабочей нагрузки, а не как к безобидным флажкам.
Проверка 4: tempdb — проверьте ожидания перед добавлением файлов
SELECT file_id,
name,
type_desc,
size / 128.0 AS size_mb,
CAST(CASE WHEN is_percent_growth = 1
THEN growth
ELSE growth * 8.0 / 1024
END AS decimal(18,2)) AS growth_value,
CASE WHEN is_percent_growth = 1
THEN 'PERCENT' ELSE 'MB'
END AS growth_unit
FROM tempdb.sys.database_files;
tempdb обросла достаточным количеством фольклора, чтобы претендовать на собственную мифологию. Начните с данных. Ищите файлы данных, размер которых соответствует рабочей нагрузке, одинаковые размеры и приращения роста для всех файлов данных, а также рост в фиксированных мегабайтах, а не в процентах. Несколько файлов данных одинакового размера — это стандартная отправная точка для борьбы с конкуренцией за выделение, обычно по одному на логический процессор, но не более восьми, но не размножайте файлы, если ожидания не подтверждают этот диагноз.
Чтобы подтвердить конкуренцию за выделение, а не диагностировать по традиции, посмотрите, что ожидает прямо сейчас:
SELECT session_id,
wait_type,
wait_duration_ms,
blocking_session_id,
resource_description
FROM sys.dm_os_waiting_tasks
WHERE wait_type LIKE 'PAGELATCH[_]%'
AND resource_description LIKE '2:%';
2:1:1, 2:1:2 и 2:1:3 — известные примеры первых страниц для PFS, GAM и SGAM в первом файле данных tempdb. Страницы выделения повторяются позднее в каждом файле, так что эти три номера страниц — примеры, а не полный детектор. Это DMV также является мгновенным снимком. Опрашивайте его многократно и ищите устойчивые ожидания PAGELATCH на страницах выделения tempdb, прежде чем менять количество файлов. Один скриншот — это подсказка. Повторяющийся паттерн — это доказательство.
Проверка 5: статистика — устаревшая означает диагноз
SELECT name,
is_auto_create_stats_on,
is_auto_update_stats_on,
is_auto_update_stats_async_on
FROM sys.databases
WHERE database_id > 4;
Автоматическое создание и автоматическое обновление почти всегда должны быть включены. Интересная настройка — is_auto_update_stats_async_on. Когда она выключена, запрос, вызывающий автоматическое обновление статистики, ждёт завершения обновления, прежде чем продолжить компиляцию. Включение позволяет избежать этой синхронной задержки, но вызвавший запрос компилируется с существующей статистикой, пока обновление выполняется в фоне. Это компромисс, а не универсальная рекомендация. В SQL Server 2022 и новее также оценивайте ASYNC_STATS_UPDATE_WAIT_AT_LOW_PRIORITY для снижения конкуренции за блокировки от фонового обновления.
Запрос настроек выполняется на уровне экземпляра. Следующий запрос — на уровне базы данных, поэтому выполняйте его внутри каждой базы данных, которая имеет значение. Он находит сильно изменённую статистику на больших наборах строк, заслуживающих внимания:
SELECT OBJECT_SCHEMA_NAME(s.object_id) AS schema_name,
OBJECT_NAME(s.object_id) AS table_name,
s.name AS stats_name,
sp.last_updated,
sp.rows,
sp.rows_sampled,
sp.modification_counter
FROM sys.stats AS s
CROSS APPLY sys.dm_db_stats_properties(s.object_id, s.stats_id) AS sp
WHERE sp.rows > 1000000
AND sp.modification_counter > sp.rows * 0.1
ORDER BY sp.modification_counter DESC;
Счётчик изменений (modification_counter) отслеживает изменения в первом столбце статистики. Фильтр 10 процентов — это эвристика для сортировки, а не внутренний порог автоматического обновления SQL Server. Большой счётчик также не доказывает, что устаревшая статистика вызвала медленный запрос. Сравните rows_sampled с rows, затем изучите оценочное и фактическое количество строк для затронутого запроса. Целевое обновление с более высоким процентом выборки может помочь, если статистика действительно является причиной. Полное сканирование всех больших таблиц — это способ превратить задачу обслуживания в следующий отчёт об инциденте.
Проверка 6: размер меняет значение слова «безопасно»
Большинство самых худших инцидентов, которые я расследую, восходят к изменению, которое было технически корректным, но выполнено в неправильном масштабе. Инструкция была верной. Таблица была огромной. SQL Server учёл оба факта.
Вам не нужна новая платформа для начала. Вам нужен список, опубликованный до окна изменений, таблиц, в которых ничего «обычного» не происходит:
WITH table_footprint AS
(
SELECT object_id,
SUM(CASE WHEN index_id IN (0, 1)
THEN row_count ELSE 0 END) AS row_count,
SUM(reserved_page_count) * 8 / 1024.0 AS reserved_mb
FROM sys.dm_db_partition_stats
GROUP BY object_id
)
SELECT s.name AS schema_name,
t.name AS table_name,
f.row_count,
f.reserved_mb
FROM table_footprint AS f
JOIN sys.tables AS t
ON t.object_id = f.object_id
JOIN sys.schemas AS s
ON s.schema_id = t.schema_id
WHERE f.row_count > 50000000
ORDER BY f.row_count DESC;
Количество строк является приблизительным, а reserved_mb включает индексы таблицы. Замените 50 миллионов на порог, отражающий вашу среду. Этого достаточно для создания «входного барьера» для развёртывания. Правило умещается в одном предложении: никакого потенциально блокирующего или зависящего от размера DDL для чего-либо из этого списка без письменного плана выполнения, проверенного пути отката и оценок продолжительности, роста журнала транзакций и влияния на блокировки. Это одно правило предотвратило больше сбоев для моих клиентов, чем многие куда более впечатляющие проекты.
Проверка 7: Resource Governor для рабочей нагрузки, которая считает, что владеет сервером
В каждом магазине есть такая. Отчётная нагрузка в конце месяца приходит, «укладывает» транзакционную нагрузку, её расследуют, объясняют, а затем она возвращается в следующем месяце, как собрание, которое никто не осмелился отклонить.
Управление этой нагрузкой поддерживается там, где позволяет редакция. Значения ниже — это заполнители для проверенной политики, а не рекомендуемые рабочие значения. MAX_CPU_PERCENT — это оппортунистический максимум, который применяется при конкуренции за CPU, в то время как CAP_CPU_PERCENT — жёсткий потолок CPU. Для обычных дисковых нагрузок MAX_MEMORY_PERCENT управляет рабочей памятью запросов для пула, а не общей памятью SQL Server или буферным пулом. Таблицы, оптимизированные для памяти, имеют дополнительное поведение пула, которое должно оцениваться отдельно.
USE master;
GO
CREATE RESOURCE POOL ReportingPool
WITH (MAX_CPU_PERCENT = 25,
CAP_CPU_PERCENT = 40,
MAX_MEMORY_PERCENT = 25);
CREATE WORKLOAD GROUP ReportingGroup
USING ReportingPool;
GO
CREATE FUNCTION dbo.fn_ClassifyWorkload()
RETURNS SYSNAME
WITH SCHEMABINDING
AS
BEGIN
RETURN CASE
WHEN SUSER_SNAME() = N'DOMAIN\ReportingService'
THEN N'ReportingGroup'
ELSE N'default'
END;
END;
GO
ALTER RESOURCE GOVERNOR
WITH (CLASSIFIER_FUNCTION = dbo.fn_ClassifyWorkload);
ALTER RESOURCE GOVERNOR RECONFIGURE;
Это иллюстративная новая конфигурация. Если на сервере уже есть функция классификатора, добавьте правило маршрутизации в эту функцию вместо замены её этим примером. Классификатор принадлежит базе данных master и оценивается для каждого нового сеанса, даже когда включено пулирование соединений. Повторное использование существующего пулированного сеанса не создаёт новое событие классификации, и существующие сеансы сохраняют свою текущую группу. Держите функцию простой, тестируйте с действительно новым подключением, проверяйте назначенную группу рабочей нагрузки и подтверждайте доступ к выделенному административному подключению перед развёртыванием. Чрезмерно ограничивающий пул может заставить запрос выполняться дольше и удерживать блокировки дольше, поэтому проверяйте влияние на защищаемую транзакционную нагрузку так же тщательно, как и на отчётность.
Проверка 8: оповещайте о дыме, а не о пепле
Почти все оповещают о сбое. Это полезно, но поздно. Гораздо меньше команд оповещают о состоянии, которое нарастало несколько минут, пока база данных всё ещё отвечает на запросы и делает вид, что всё в порядке.
Текущий заблокированный запрос, ожидающий более тридцати секунд, — один из самых ценных сигналов, которые я знаю. Тридцать секунд — это пример порога, а не закон. Настройте его под рабочую нагрузку, опрашивайте запрос из задания, сохраняйте результаты и учитывайте обслуживание, которое должно блокировать:
SELECT r.session_id,
r.blocking_session_id,
r.wait_time / 1000 AS wait_seconds,
r.wait_type,
r.wait_resource,
DB_NAME(r.database_id) AS database_name,
t.text AS running_sql
FROM sys.dm_exec_requests AS r
OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) AS t
WHERE r.blocking_session_id <> 0
AND r.wait_time > 30000;
Положительные значения blocking_session_id идентифицируют другой сеанс. Отрицательные значения имеют специальные значения, включая осиротевшие распределённые транзакции и владельцев кратких блокировок, которых SQL Server не может идентифицировать. В частности, -5 сам по себе не доказывает проблему с производительностью. Этот запрос — сигнал раннего предупреждения, а не полный анализ цепочки блокировок.
Добавьте использование журнала транзакций и причину, по которой усечение журнала удерживается. Это может выявить давление, вызванное активной транзакцией, отсутствием резервных копий журнала, репликацией или репликой доступности до того, как том заполнится:
SELECT d.name AS database_name,
ls.total_log_size_mb,
ls.active_log_size_mb,
CAST(100.0 * ls.active_log_size_mb /
NULLIF(ls.total_log_size_mb, 0) AS decimal(6,2)) AS active_log_percent,
ls.log_since_last_log_backup_mb,
ls.log_truncation_holdup_reason
FROM sys.databases AS d
CROSS APPLY sys.dm_db_log_stats(d.database_id) AS ls
WHERE d.state_desc = 'ONLINE'
AND d.database_id > 4
ORDER BY active_log_percent DESC;
sys.dm_db_log_stats доступен в SQL Server 2016 SP2 и новее. На вторичной реплике группы доступности функция возвращает только подмножество своих обычных столбцов, поэтому отсутствующие значения размера не следует интерпретировать как нулевое давление. Запрос показывает текущее давление и причину задержки усечения, а не историю событий роста файлов. Захватывайте авто-рост отдельно с помощью Extended Events или вашей платформы мониторинга. Сигнал предупреждения часто виден до того, как сработает пейджер, но только если вы сохраняете базовую линию и оповещаете об устойчиво аномальных значениях.
Почему ничего из этого не делается
Первый аудит занимает около часа. Большая часть исправлений — это настройка, код и дисциплина эксплуатации с использованием возможностей, которыми организация уже владеет. Функции, зависящие от редакции, всё равно нужно проверять, прежде чем кто-либо пообещает изменение. Так почему же такой большой пласт практической профилактики остаётся неиспользованным?
Потому что профилактика невидима, а у невидимой работы нет защитника.
Человек, который проводит спокойный четверг, оценивая автоматическую коррекцию планов, исправляя подтверждённую проблему с tempdb, тестируя конфигурацию параллелизма и сочиняя оповещение о блокировках, со стороны не создал ничего. Никакого отчёта об инциденте. Никакого совещания по телефону, где он был героем. Ничего драматичного для включения в отчёт о работе.
Человек, который исправляет катастрофический сбой в четыре утра, получает благодарность в корпоративном письме. Человек, чья подготовка предотвратила сбой, получает спокойную ночь и никакого письма. Я знаю, какую награду я предпочитаю, но я также знаю, какую из них организации обычно замечают.
Никто здесь не ведёт себя иррационально. Стимулы направлены на огонь, а не на проводку, и так было долгое время.
Мой практический совет — описывать профилактику на языке инцидента, который она устраняет. Не «я включил автоматическую настройку». Вместо этого: «регрессии планов, подпадающие под критерии, теперь могут автоматически исправляться и проверяться после принуждения». Не «я добавил оповещение». Вместо этого: «мы теперь обнаруживаем устойчивые блокировки, пока ещё есть время действовать». Та же техническая работа, гораздо более чёткая бизнес-ценность.
Где у этого аргумента есть границы
Здесь уместны две честные оговорки, и вторая — более сильная.
У профилактики есть потолок. Вы можете устранить известные классы отказов. Вы не можете устранить беспрецедентное. Мониторинг и объяснение никогда не будут стоить ноль, и любой, кто обещает мир без инцидентов, что-то вам продаёт.
Вы не можете предотвратить класс отказов, который никогда не понимали. Анализ первопричин — это входные данные для профилактической работы. Моя жалоба никогда не была в том, что мы их пишем. А в том, что мы их пишем, архивируем и не делаем следующего шага.
Так что утверждение уже, чем предполагает заголовок. Объяснение необходимо. Рассматривать объяснение как конечную цель — вот ошибка.
Раздел, который я теперь добавляю к каждому анализу
В конце каждого документа об инциденте, который я пишу, теперь есть раздел, который не об инциденте. Он о том, как сделать тот же класс отказов менее желанным в следующий раз.
В нём назван класс отказов, указано, что сделало бы его невозможным, а не просто видимым, и дана примерная стоимость. Иногда стоимость — это день и флажок. Иногда — это запланированный проект. И то, и другое легче финансировать, когда стоимость и устраняемый отказ явно указаны.
Некоторые клиенты пропускают этот раздел. Некоторые — нет, и это те клиенты, от которых я в итоге перестаю получать звонки. Это самая странная форма профессионального успеха, с которой я сталкивался, и я решил наслаждаться ею.
Нить, проходящая через всё это, — кто берёт на себя суждение, когда инструментарий звучит уверенно, что является темой всех тридцати эссе в моей книге AI: Nobody's in There: Но мы всё ещё здесь. Все тридцать доступны для бесплатного чтения на pinaldave.com. Если вы предпочитаете держать копию в руках, она доступна на Amazon в мягкой обложке, Kindle и аудиокниге.
Если вы возьмёте что-то одно из этого, возьмите самое маленькое. Запишите версию и редакцию, затем выполните запрос состояния Query Store из проверки 2 для вашей самой загруженной базы данных. Это займёт около двух минут. Если ответ — READ_ONLY, самая ценная история производительности на сервере может уже бесшумно исчезать.
Это не история о том, как лучше объяснять инциденты, это история о том, чтобы сделать объяснение тем, что требуется реже.

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