7.8.26

Полное руководство по xEvents

Автор: Vivek Johari, Extended Events (xEvents) in SQL Server & Azure SQL: Complete Guide;

Каждому администратору баз данных рано или поздно приходится сталкиваться с этим: приходит заявка в службу поддержки с сообщением «приложение работает медленно» и без каких-либо других подробностей, и ваша задача — выяснить, какая из тысячи вещей, происходящих внутри SQL Server, на самом деле является виновником. Это была блокировка? Плохой план? Шквал входов в систему? Чей-то ситуативный запрос, в котором забыли условие WHERE и который сейчас сканирует одиннадцать миллионов строк?

Долгое время ответом на вопрос «давайте посмотрим, что происходит в реальном времени» был SQL Server Profiler, работающий поверх SQL Trace. Он работал, но работал так, как работает прожектор, когда на самом деле вам нужен был фонарик — он захватывал всё без разбора, выполнялся на стороне клиента и мог заметно замедлить загруженный рабочий сервер просто фактом своего включения. Достаточно много администраторов баз данных имеют историю о том, как благонамеренная трассировка Profiler ухудшала состояние и без того перегруженного сервера, что в конечном итоге привело к тому, что Microsoft перестала рекомендовать его вообще.

Расширенные события (Extended Events, xEvents) пришли на смену, и это не просто незначительное обновление, — это принципиально иная архитектура, и это инструмент, который экзамен DP-300 требует знать досконально. Эта статья описывает, что это такое, почему он превосходит старые инструменты и — что действительно важно в повседневной работе — как использовать его для отслеживания блокировок, взаимоблокировок, медленных запросов, таймаутов, высокой загрузки ЦП, сбоев входа в систему и статистики ожиданий.

Что на самом деле представляют собой расширенные события

Расширенные события — это легковесная система обработки событий, встроенная непосредственно в ядро SQL Server (и его «облачные собратья»). Вместо отдельного механизма трассировки, прикрученного сбоку, XEvents напрямую взаимодействует с компонентами ядра и позволяет подписываться на конкретные события, происходящие внутри: запуск запроса, захват блокировки, обнаружение взаимоблокировки, сбой входа в систему — практически без тех накладных расходов, которые делали SQL Trace рискованным для использования в рабочей среде.

Архитектура строится из небольшого числа базовых блоков, которые, если их однажды понять, делают всю систему гораздо менее загадочной:

┌──────────────────────────────────────────────────────────────┐
│                         СЕАНС СОБЫТИЙ                        │
│                                                              │
│   СОБЫТИЯ              ДЕЙСТВИЯ            ПРЕДИКАТЫ         │
│   "что произошло"  "дополнительный     "захватывать только   │
│   например,          контекст для        события,            │
│   sql_statement_    прикрепления"       соответствующие      │
│   completed,        например,           этому условию"       │
│   deadlock_graph,   sql_text,           например,            │
│   login_failed      client_hostname,    duration >           │
│                     session_id          5000ms               │
│        │                  │                  │               │
│        └──────────────────┴──────────────────┘               │
│                              │                               │
│                              ▼                               │
│                          ЦЕЛЬ                                │
│              куда попадают отфильтрованные,                  │
│              обогащённые события                             │
│      ring_buffer (в памяти) │ event_file (на диске, .xel)    │
│      histogram │ event_counter │ event_stream (в реальном    │
│                                             времени)         │
└──────────────────────────────────────────────────────────────┘

События — «то, что произошло», о чём вы хотите узнать (завершение запроса, взаимоблокировка, попытка входа).
Действия — дополнительные фрагменты контекста, автоматически прикрепляемые к каждому захваченному событию, такие как текст SQL-запроса, имя клиентского хоста или идентификатор сеанса, — без необходимости самостоятельно выстраивать эту корреляцию.
Предикаты — фильтры, применяемые до того, как событие будет полностью обработано, поэтому вы не платите за захват тысяч нерелевантных событий, чтобы потом их отбросить. Это самая важная причина, по которой XEvents намного дешевле SQL Trace.
Цели — куда на самом деле попадают захваченные данные: быстрый кольцевой буфер в памяти для быстрых проверок, файл на диске для всего, что нужно сохранить, или цель прямой трансляции для просмотра в реальном времени.

Почему расширенные события превосходят SQL Trace и SQL Profiler

Это вопрос не вкуса — Microsoft официально объявила SQL Trace и SQL Server Profiler устаревшими и направляет всех к использованию расширенных событий для всех видов мониторинга в будущем. Вот почему произошёл этот сдвиг.

Аспект SQL Trace / Profiler Расширенные события
Архитектура Отдельная подсистема трассировки, надстроенная над ядром Встроена непосредственно в основные компоненты ядра
Фильтрация Применяется поздно — часто после того, как данные уже захвачены Предикаты применяются на раннем этапе, до ненужной обработки
Накладные расходы на производительность Могут быть значительными, особенно с подключённым клиентским GUI Profiler Минимальны по замыслу; могут работать непрерывно в рабочей среде при правильной настройке
Где выполняется Трассировки Profiler выполняются на стороне клиента, добавляя сетевые и клиентские накладные расходы По умолчанию на стороне сервера; XEvent Profiler в SSMS даёт похожее представление в реальном времени без старых накладных расходов
Гибкость Фиксированный набор столбцов трассировки и событий Сотни детализированных событий, настраиваемые действия, несколько типов целей
Доступность в Azure SQL Database Недоступен — Profiler/Trace никогда не поддерживался в Azure SQL Database Полностью поддерживается, с сеансами в рамках базы данных
Статус Устарел Активно развивается; рекомендуемый инструмент на будущее
Долгосрочное хранение данных Файлы трассировки (.trc) на локальном диске Цель event_file — локальный диск (SQL Server) или большой двоичный объект хранилища Azure (Azure SQL DB/MI)
Просмотр в реальном времени Интерфейс Profiler «Просмотр динамических данных» / XEvent Profiler в SSMS (19.2+), дающий почти идентичный интерфейс с долей затрат

Практический итог: с SQL Trace «давайте посмотрим, что медленно» часто означало принятие некоторого количества эффекта Гейзенберга — сам акт наблюдения замедлял работу. С расширенными событиями правильно спроектированные сеансы могут работать непрерывно в рабочей среде, и никто не заметит их присутствия.

Расширенные события в SQL Server, Azure SQL Database и Managed Instance

Основные концепции идентичны везде, но существуют реальные различия платформ, которые стоит знать перед созданием первого сеанса:

Аспект SQL Server Azure SQL Managed Instance Azure SQL Database
Область действия сеанса На уровне сервера Как на уровне сервера, так и на уровне базы данных (для большинства случаев рекомендуется серверная область) Всегда на уровне базы данных — сеанс может видеть события только из своей собственной базы данных
Место хранения цели event_file Локальный диск или большой двоичный объект хранилища Azure Только большой двоичный объект хранилища Azure Только большой двоичный объект хранилища Azure
Аутентификация для хранилища Не требуется (локальный диск) или ключ хранилища/SAS Управляемое удостоверение или учётные данные SAS Управляемое удостоверение или учётные данные SAS (настоятельно рекомендуется управляемое удостоверение)
XEvent Profiler в SSMS Поддерживается Поддерживается Поддерживается (SSMS 19.2+)

Это правило «всегда область действия базы данных» для Azure SQL Database постоянно сбивает с толку тех, кто привык к общесерверной видимости SQL Server, — в Azure SQL Database событие, происходящее в Базе данных A, просто не может отображаться в сеансе, созданном для Базы данных B, и точка.

Создание сеансов и управление ими: все интерфейсы, которые вы будете использовать

T-SQL — основа, на которой строится всё остальное

Это сеанс, который захватывает долго выполняющиеся запросы (длительностью более 5 секунд), а также текст SQL и имя клиентского хоста, записывая их в файловую цель:

CREATE EVENT SESSION [SlowQueries] ON SERVER 
ADD EVENT sqlserver.sql_statement_completed
    (
    ACTION (sqlserver.sql_text,
            sqlserver.client_hostname,
            sqlserver.session_id)
    WHERE duration > 5000000 -- длительность в микросекундах; это 5 секунд
    ) 
ADD TARGET package0.event_file
    (
    SET filename = N'SlowQueries'
    )
WITH  (
        MAX_MEMORY = 4096 KB,
        EVENT_RETENTION_MODE = ALLOW_SINGLE_EVENT_LOSS,
        MAX_DISPATCH_LATENCY = 30 SECONDS,
        STARTUP_STATE = ON
      );
GO
-- Сеансы создаются в остановленном состоянии — их нужно запускать явно
ALTER EVENT SESSION [SlowQueries] ON SERVER 
STATE = START

SSMS — графический путь

  1. В обозревателе объектов подключитесь к вашему SQL Server / Azure SQL Managed Instance / Azure SQL Database (подключайтесь на уровне базы данных для Azure SQL Database).
  2. Разверните Management → Extended Events → Sessions
  3. Щёлкните правой кнопкой мыши Sessions → New Session Wizard (с пошаговым руководством) или New Session (полный контроль).
  4. Выберите шаблон (SSMS поставляется с несколькими — «Query Batch Sampling», «Locks» и т. д.) или создайте с нуля.
  5. На странице Events найдите и добавьте нужные события (например, sql_statement_completed, blocked_process_report).
  6. На странице Global Fields (Actions) добавьте контекстные поля, такие как sql_text и client_hostname.
  7. На странице Filter (Predicate) добавьте свои условия (например, duration > 5000000).
  8. На странице Data Storage добавьте цель(и).
  9. Отметьте «Start the event session immediately after session creation», затем завершите.

XEvent Profiler — современная замена Profiler в реальном времени в SSMS

Для быстрых, ситуативных проверок «покажи мне, что происходит прямо сейчас» SSMS 19.2+ включает XEvent Profiler, который ведёт себя почти так же, как старый интерфейс Profiler, но построен на основе расширенных событий «под капотом» (используя цель event_stream вместо записи на диск). Щёлкните правой кнопкой мыши экземпляр или базу данных в Object Explorer → XEvent Profiler → Launch Standard Session или Launch TSQL Session, и вы получите живую прокручиваемую сетку активности без старых накладных расходов Profiler.

Azure Data Studio

Azure Data Studio (на момент написания) не включает полноценный графический конструктор сеансов, аналогичный мастеру SSMS, но это вполне подходящий инструмент для выполнения приведённых выше T-SQL-скриптов, и его сетка результатов запросов полезна для запросов к захваченным данным .xel после их сохранения.

PowerShell

PowerShell не имеет специальной библиотеки командлетов для создания сеансов расширенных событий (это по-прежнему в основном задача T-SQL/SMO), но он часто используется для оркестрации вспомогательной инфраструктуры и автоматизации вокруг сеансов — подготовки учётной записи хранения, назначения ролей RBAC или планирования запуска/остановки сеансов через автоматизацию:

# Создание учётной записи хранения для целей XEvent
New-AzStorageAccount -ResourceGroupName "prod-data-rg" `
  -Name "xeventsstorage01" -Location "eastus2" -SkuName "Standard_LRS"

# Предоставление управляемому удостоверению сервера SQL прав на запись в него
$sqlServer = Get-AzSqlServer -ResourceGroupName "prod-data-rg" -ServerName "prod-sql-server"
New-AzRoleAssignment -ObjectId $sqlServer.Identity.PrincipalId `
  -RoleDefinitionName "Storage Blob Data Contributor" `
  -Scope (Get-AzStorageAccount -ResourceGroupName "prod-data-rg" -Name "xeventsstorage01").Id

Затем вы можете выполнить собственно T-SQL CREATE EVENT SESSION через Invoke-Sqlcmd из того же скрипта, если вы хотите полностью автоматизированное, воспроизводимое развёртывание сеансов мониторинга.

Практические сеансы для реальных проблем

Блокировки

CREATE EVENT SESSION [CaptureBlocking] ON SERVER 
ADD EVENT sqlserver.blocked_process_report 
ADD TARGET package0.event_file
    (
    SET filename = N'CaptureBlocking'
    )
WITH  (
        MAX_DISPATCH_LATENCY = 5 SECONDS
      );
GO
ALTER EVENT SESSION [CaptureBlocking] ON SERVER 
STATE = START;

Это зависит от установки серверной конфигурации blocked process threshold (в секундах) — без неё blocked_process_report никогда не сработает:

EXEC sp_configure 'show advanced options', 1; RECONFIGURE;
EXEC sp_configure 'blocked process threshold', 5; RECONFIGURE;

Взаимоблокировки

CREATE EVENT SESSION [CaptureDeadlocks] ON SERVER 
ADD EVENT sqlserver.xml_deadlock_report 
ADD TARGET package0.event_file
    (
    SET filename = N'CaptureDeadlocks'
    )
WITH  (
        STARTUP_STATE = ON
      );
GO
ALTER EVENT SESSION [CaptureDeadlocks] ON SERVER 
STATE = START;

Захваченный xml_deadlock_report даёт полный граф взаимоблокировки — конкурирующие сеансы, задействованные ресурсы и то, какой из них был выбран в качестве «жертвы».

Медленные запросы и таймауты запросов

CREATE EVENT SESSION [SlowAndTimedOutQueries] ON SERVER
ADD EVENT sqlserver.sql_statement_completed
(
    ACTION (sqlserver.sql_text, sqlserver.client_hostname, sqlserver.username)
    WHERE duration > 3000000  -- 3 секунды
)
ADD EVENT sqlserver.attention  -- срабатывает при отмене/таймауте со стороны клиента
(
    ACTION (sqlserver.sql_text, sqlserver.client_hostname)
)
ADD TARGET package0.event_file (SET filename = N'SlowAndTimedOutQueries')
WITH (MAX_DISPATCH_LATENCY = 15 SECONDS);
GO

Событие attention — это то, о чём часто забывают; оно срабатывает, когда клиент отменяет запрос, что именно и происходит «под капотом» при таймауте команды.

Высокая загрузка ЦП

CREATE EVENT SESSION [HighCpuQueries] ON SERVER 
ADD EVENT sqlserver.sql_statement_completed
    (
    ACTION (sqlserver.sql_text,
            sqlserver.session_id)
    WHERE cpu_time > 2000000 -- 2 секунды процессорного времени в микросекундах
    ) 
ADD TARGET package0.event_file
    (
    SET filename = N'HighCpuQueries'
    )
WITH  (
        MAX_DISPATCH_LATENCY = 30 SECONDS
      );

Сбои входа в систему

CREATE EVENT SESSION [LoginFailures] ON SERVER 
ADD EVENT sqlserver.error_reported
    (
    ACTION (sqlserver.client_hostname,
            sqlserver.username,
            sqlserver.session_id)
    WHERE ([severity] = 14
           AND [error_number] = 18456) -- классическая ошибка сбоя входа
    ) 
ADD TARGET package0.event_file
    (
    SET filename = N'LoginFailures'
    )
WITH  (
        STARTUP_STATE = ON
      );

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

Статистика ожиданий и узкие места производительности

CREATE EVENT SESSION [WaitStats] ON SERVER 
ADD EVENT sqlserver.wait_info
    (
    ACTION (sqlserver.session_id)
    WHERE duration > 1000 -- только значимые ожидания в миллисекундах
    ) 
ADD TARGET package0.histogram
    (
    SET filtering_event_name = 'sqlserver.wait_info',
        source_type = 0,
        source = 'wait_type'
    )
WITH  (
        MAX_DISPATCH_LATENCY = 30 SECONDS
      );

Цель histogram здесь делает кое-что полезное — вместо того чтобы выгружать каждое отдельное событие ожидания, она группирует и подсчитывает вхождения по типу ожидания, давая вам сводку «на что этот сервер тратит время ожидания» без необходимости пробираться через необработанные данные событий.

Чтение захваченных данных .xel

SELECT event_data.value('(event/@name)[1]', 'varchar(100)') AS event_name, event_data.value('(event/@timestamp)[1]', 'datetime2') AS event_time, event_data.value('(event/data[@name="duration"]/value)[1]', 'bigint') AS duration_us, event_data.value('(event/action[@name="sql_text"]/value)[1]', 'nvarchar(max)') AS sql_text FROM (SELECT CAST (event_data AS XML) AS event_data FROM sys.fn_xe_file_target_read_file('SlowQueries*.xel', NULL, NULL, NULL)) AS tab ORDER BY event_time DESC;

Реальные сценарии устранения неисправностей

Сценарий 1 — «Приложение зависает случайным образом, но только в часы пик»

Администратор баз данных в логистической компании настраивает сеанс типа SlowAndTimedOutQueries (как описано выше) с STARTUP_STATE = ON, работающий непрерывно с низконагруженной файловой целью. Два дня спустя жалобы на таймауты снова возрастают. Вместо попытки воспроизвести это в реальном времени администратор запрашивает файлы .xel за нужный период времени и находит конкретный отчётный запрос, выполняемый одним конкретным клиентским хостом, который стабильно занимает 25+ секунд во время утренней пакетной загрузки, — пропущенный индекс, который становится проблемным только после того, как таблица достигает определённого количества строк. Индекс добавлен, таймауты исчезли.

Сценарий 2 — «У нас периодически возникают взаимоблокировки, но никто не может их поймать в реальном времени»

Розничная компания включает CaptureDeadlocks с STARTUP_STATE = ON, чтобы он переживал перезапуски сервера и просто тихо работал. Через неделю, наконец, в 3 часа ночи во время пакетного задания, когда никто не бодрствовал, происходит взаимоблокировка. xml_deadlock_report уже лежал в файле событий, показывая, что два пакетных процесса захватывали блокировки на одни и те же таблицы в обратном порядке, — классический шаблон взаимоблокировки, и исправление (согласованный порядок блокировок в пакетных скриптах) было развёрнуто без необходимости «ловить это на месте».

Сценарий 3 — «Кто-то пытается взломать наш SQL-логин»

Сеанс LoginFailures в медицинской компании показывает необычный всплеск — сотни неудачных входов за десять минут, все с одного внешнего IP-адреса, все с попытками разных имён пользователей. Это не похоже на обычную опечатку, это сканирование. Команда безопасности блокирует IP-адрес на межсетевом экране и подтверждает (следуя руководству из предыдущей статьи об аутентификации Azure), что аутентификация только через Entra ID полностью предотвратила бы этот вектор атаки, ускоряя миграцию, которая давно ждала своей очереди.

Рекомендации

  • Всегда указывайте предикаты — захват всего с последующей фильтрацией сводит на нет всё преимущество XEvents перед SQL Trace.
  • Предпочитайте цель event_file для всего, что нужно будет просматривать позже; ring_buffer используйте только для быстрых, одноразовых проверок «в моменте» (он очищается при перезапуске и имеет жёсткий лимит размера).
  • Устанавливайте MAX_DISPATCH_LATENCY осознанно — очень низкие значения (доставка, близкая к реальному времени) требуют больше накладных расходов; значение в диапазоне 15–30 секунд обычно является разумным компромиссом для большинства сеансов диагностики.
  • Используйте EVENT_RETENTION_MODE = ALLOW_SINGLE_EVENT_LOSS для большинства рабочих сеансов — это осознанный компромисс в пользу того, чтобы не блокировать ядро, а не гарантировать нулевую потерю данных, что почти всегда является правильным выбором.
  • Устанавливайте STARTUP_STATE = ON для сеансов, которые должны переживать перезапуск сервера (например, базовые сеансы для блокировок, взаимоблокировок, сбоев входа), чтобы не остаться без данных после плановой перезагрузки.
  • В Azure SQL Database/MI держите учётную запись хранения в том же регионе, используйте аутентификацию управляемым удостоверением вместо ключей хранения и согласуйте уровень избыточности учётной записи хранения с уровнем избыточности вашей базы данных.
  • Проверяйте, у кого есть доступ на чтение к учётной записи хранения или файловому ресурсу, содержащему ваши файлы .xel, — захваченный текст SQL может содержать конфиденциальные данные, и принцип наименьших привилегий здесь так же важен, как и везде.
  • Периодически удаляйте старые сеансы, которые больше не нужны — кладбище забытых сеансов «просто тестирую это» является частым источником ненужных накладных расходов и путаницы.

Распространённые ошибки

  • Забывание о том, что сеансы создаются в остановленном состоянии — одного CREATE EVENT SESSION недостаточно, пока вы не выполните ALTER ... STATE = START.
  • Неправильная настройка серверной конфигурации blocked process threshold (или её отсутствие) и удивление, почему blocked_process_report никогда не срабатывает.
  • Использование ring_buffer для того, что на самом деле нужно сохранить, и потеря данных при следующем перезапуске.
  • Захват sql_text для каждого оператора в высоконагруженной OLTP-системе без предиката по длительности — это может действительно добавить нагрузку, даже с учётом эффективности XEvents.
  • Не тестирование логики предиката — слишком узкое или неправильное условие WHERE может привести к сеансу, который «работает», но не захватывает ничего полезного.

Вопросы производительности

  • Накладные расходы XEvents масштабируются в первую очередь с частотой событий и размером полезной нагрузки, а не с простым существованием сеанса, — хорошо отфильтрованный сеанс, отслеживающий редкие события (взаимоблокировки, сбои входа), практически бесплатен для постоянного запуска.
  • События с высокой частотой (например, sql_statement_completed без фильтра по длительности на загруженном OLTP-сервере) — вот где накладные расходы становятся заметными; всегда агрессивно используйте предикаты.
  • Цели histogram и event_counter дешевле, чем event_file, для агрегатных вопросов («сколько произошло X»), потому что они не сериализуют полные полезные нагрузки событий в хранилище.
  • Настройки MAX_MEMORY и EVENT_RETENTION_MODE напрямую определяют компромисс между «никогда не терять событие» и «никогда не позволять захвату событий блокировать выполнение запроса» — для устранения неполадок в рабочей среде предпочтение последнего почти всегда правильно.

Часто задаваемые вопросы

Можно ли использовать расширенные события в Azure SQL Database так же, как в локальном SQL Server? В основном да — события, действия и предикаты работают одинаково. Два реальных отличия: сеансы всегда имеют область действия базы данных, а цель event_file всегда записывает в хранилище Azure, а не на локальный диск.

Переживают ли сеансы расширенных событий отказ или перезапуск? Только если установлен STARTUP_STATE = ON. Без этого сеанс останавливается после перезапуска и должен быть запущен вручную.

Является ли XEvent Profiler в SSMS тем же самым, что и старый SQL Server Profiler? Он выглядит и работает похоже, но полностью построен на расширенных событиях «под капотом» (используя цель event_stream), что означает, что он не имеет проблем устаревшей архитектуры или накладных расходов Profiler.

Могут ли расширенные события записывать напрямую в таблицу SQL? Нет — цели записывают в файлы, большие двоичные объекты, буферы памяти или гистограммы, а не напрямую в реляционные таблицы. Стандартный подход: захват в event_file, затем периодическое чтение и загрузка результатов в таблицу с помощью sys.fn_xe_file_target_read_file, если вы хотите, чтобы эти данные были доступны для запросов в долгосрочной перспективе.

Сколько накладных расходов действительно добавляет хорошо спроектированный сеанс? Для правильно отфильтрованного низкочастотного сеанса (взаимоблокировки, сбои входа, блокировки) накладные расходы близки к незначительным, — он специально разработан для безопасной непрерывной работы в производственной среде, в этом и заключается смысл перепроектирования по сравнению с SQL Trace.

Какие разрешения нужны для создания сеанса? ALTER ANY EVENT SESSION для сеансов на уровне сервера или CONTROL для базы данных для сеансов на уровне базы данных (что, опять же, единственный тип, поддерживаемый Azure SQL Database).

Матрица принятия решений: выбор правильного сеанса для задачи

Вам нужно… Рекомендуемое событие(я) Рекомендуемая цель
Поймать периодические блокировки blocked_process_report (с настроенным порогом) event_file
Диагностировать повторяющиеся взаимоблокировки xml_deadlock_report event_file, STARTUP_STATE = ON
Найти медленные запросы выше порога sql_statement_completed с предикатом длительности event_file
Поймать таймауты/отмены на стороне клиента attention event_file
Выявить запросы с высоким потреблением ЦП sql_statement_completed с предикатом cpu_time event_file
Обнаружить попытки взлома с перебором паролей error_reported с фильтром по ошибке 18456 event_file, STARTUP_STATE = ON
Обобщить узкие места по ожиданиям wait_info Цель histogram
Быстрая одноразовая проверка в реальном времени Любое соответствующее событие ring_buffer или представление в реальном времени XEvent Profiler
Долгосрочное хранение данных в Azure SQL DB/MI Любое соответствующее событие event_file → большой двоичный объект хранилища Azure через учётные данные управляемого удостоверения
Получение представления почти в реальном времени без хранилища Любое соответствующее событие event_stream (через XEvent Profiler)

Основная привычка, которую нужно выработать: начинайте с вопроса, на который пытаетесь ответить («почему это зависло», «кто перебирает логины», «чего мы ждём»), выберите событие, которое напрямую на него отвечает, агрессивно фильтруйте с помощью предикатов и выбирайте самую дешёвую цель, которая всё ещё даёт вам необходимое. В этом и заключается вся дисциплина, — всё остальное в этой статье является деталями, служащими этой одной привычке.


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

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