Спостережуваність і діагностика
Як зрозуміти, що відбувається із сервісом у продакшені: які сигнали збирати, як повідомити балансувальнику про здоров’я екземпляра, як розслідувати високий CPU або витік на живому процесі й як налаштувати алерти, що будять лише по ділу. На цій темі перевіряють досвід експлуатації: що ви робите, коли «все гальмує», а в логах тиша.
Перед цією темою: Конфігурація й логування.
Що таке спостережуваність і чим логи, метрики й трасування відрізняються одне від одного?
Section titled “Що таке спостережуваність і чим логи, метрики й трасування відрізняються одне від одного?”Що це
Спостережуваність — властивість системи, яка дає змогу за її зовнішніми даними зрозуміти внутрішній стан. Основних видів даних, або сигналів, три.
Три сигнали
| Сигнал | На яке питання відповідає |
|---|---|
| Метрика: число в часі | скільки і як швидко: RPS, частка помилок, затримка |
| Трасування: шлях одного запиту | де витрачається час і де впало |
| Лог: окрема подія | що саме сталося і з якими даними |
Як користуватися
- Алерт за метрикою повідомляє: зросла частка помилок.
- Трейс запиту, що впав, показує, у якому сервісі чи запиті до БД проблема.
- Логи з тим самим
TraceIdдають текст помилки й параметри.
Типова помилка
Рахувати метрики за логами: «скільки помилок за хвилину» через пошук в Elastic. Це повільно й дорого, для лічильників є метрики.
Як влаштовано
- Метрика — значення з мітками, агреговане за часом: лічильник, гістограма, поточне значення. Зберігається як часовий ряд, і його розмір не залежить від кількості запитів.
- Трейс — дерево спанів. Спан — одна операція (вхідний запит, SQL-запит, HTTP-виклик) з початком, тривалістю й атрибутами. Між сервісами контекст передає заголовок
traceparent. - Лог — подія з рівнем, шаблоном і полями. Обсяг росте разом із трафіком.
Де зберігають
| Сигнал | Типовий стек |
|---|---|
| Логи | ELK (Elasticsearch, Logstash, Kibana), OpenSearch, Seq, Loki |
| Метрики | Prometheus і Grafana, VictoriaMetrics |
| Трейси | Jaeger, Grafana Tempo, Zipkin |
| Усе разом | APM: Elastic APM, Application Insights, Datadog |
Пастки
- Немає
TraceIdу логах. Сигнали не пов’язані, і розслідування перетворюється на пошук за часом. - Pull проти push. Prometheus сам забирає метрики з ендпоінту сервісу, а OTLP надсилає їх із сервісу. Від моделі залежить, хто кому відкриває мережу.
Правило
Алерти — за метриками, пошук вузького місця — за трейсами, деталі — за логами.
Моніторинг і спостережуваність
- Моніторинг — заздалегідь задані дашборди й алерти. Він відповідає на відомі питання: чи живий сервіс, який p99.
- Спостережуваність — можливість відповісти на питання, про яке не думали заздалегідь. Наприклад, чому повільно лише в одному регіоні на одній версії клієнта. Для цього потрібні події з багатим контекстом і зрізи за будь-яким полем.
Ціна
- Метрика коштує однаково при 10 і 10 000 RPS, але кожна унікальна комбінація міток — окремий ряд.
- Трейси за повного збору дають десятки спанів на запит. Їх семплюють: head-based у застосунку або tail-based у колекторі, щоб зберегти помилки й повільні запити.
- Логи платять за індексацію й зберігання кожної події, тому
Debugу продакшені вимкнено.
Компроміси
- Агент APM-вендора дає швидкий старт, але прив’язує до нього. OpenTelemetry — стандартний API і протокол OTLP: бекенд змінюється конфігурацією, ціною власного конвеєра (OpenTelemetry Collector).
- Exemplars пов’язують точку на графіку метрики з конкретним
TraceId: з викиду p99 можна одразу перейти до повільного трейсу.
Як перевірити в продакшені
Розберіть останній інцидент: скільки минуло від алерту до знайденої причини і чи довелося додавати логи й передеплоювати, щоб її знайти. Якщо довелося, спостережуваності бракує.
Підсумок: моніторинг відповідає на відомі питання, спостережуваність — на нові, і платять за неї обсягом телеметрії.
# Метрика: агрегат за інтервал, без деталей окремого запитуhttp_server_request_duration_seconds_count{http_route="/orders",http_response_status_code="500"} 17
# Трейс: дерево спанів одного запитуPOST /orders 480 ms trace 4bf92f3577b34da6 ├─ SELECT orders 12 ms └─ POST payments-api 455 ms ← тут час
# Лог: подія з полями й тим самим TraceId{"Level":"Error","MessageTemplate":"Payment {PaymentId} failed","PaymentId":"p-42","TraceId":"4bf92f3577b34da6"}Один збій видно трьома способами: метрика повідомляє про 17 помилок, трейс указує на payments-api, лог дає номер платежу.
- Чим моніторинг відрізняється від спостережуваності? — Моніторинг відповідає на заздалегідь задані питання, спостережуваність дає змогу ставити нові без деплою.
- Чому не можна рахувати помилки за логами? — Пошук у логах повільний і дорогий, а лічильник у метриці коштує копійки й годиться для алерту.
- Що таке спан? — Одна операція всередині трейсу: ім’я, початок, тривалість, атрибути й посилання на батьківський спан.
- Як пов’язати логи з трейсами? — Писати в кожен запис
TraceIdіSpanIdпоточноїActivity, ASP.NET Core робить це сам. - Навіщо семплювати трейси? — Повний збір дорогий; tail-based семплювання зберігає всі помилки й повільні запити.
Як зробити health checks в ASP.NET Core і чим liveness відрізняється від readiness?
Section titled “Як зробити health checks в ASP.NET Core і чим liveness відрізняється від readiness?”Що це
Health check — HTTP-ендпоінт, який повідомляє стан застосунку: Healthy, Degraded або Unhealthy. Його опитують балансувальник, оркестратор (наприклад, Kubernetes) і моніторинг.
Як користуватися
builder.Services.AddHealthChecks()— реєструє сервіс перевірок..AddCheck<T>("name")— додає перевірку: клас, що реалізуєIHealthCheck.app.MapHealthChecks("/health")— публікує ендпоінт.
Коди за замовчуванням: Healthy і Degraded — 200, Unhealthy — 503.
Liveness і readiness
| Проба | Що за провалу |
|---|---|
| Liveness: процес живий і не завис? | процес перезапускають |
| Readiness: готовий приймати трафік? | екземпляр прибирають із балансування |
Типова помилка
Один /health з перевіркою БД для обох проб. Упала БД — оркестратор по колу перезапускає всі екземпляри.
Як влаштовано
- Кожна перевірка реєструється з ім’ям, тегами,
failureStatusі тайм-аутом. MapHealthChecksзPredicateвибирає перевірки за тегами. Так з одного набору виходять/health/liveі/health/ready.- Вибрані перевірки виконуються на кожен запит до ендпоінту, підсумок — найгірший статус.
ResponseWriterзмінює формат відповіді. За замовчуванням це одне слово:Healthy.
Що перевіряти
| Ендпоінт | Перевірки |
|---|---|
/health/live |
нічого зовнішнього: відповів — отже, живий (Predicate = _ => false) |
/health/ready |
БД, кеш, брокер — без чого екземпляр марний |
Готові перевірки: AddDbContextCheck з пакета Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore і бібліотека AspNetCore.Diagnostics.HealthChecks (SQL Server, PostgreSQL, Redis, RabbitMQ). Другу Microsoft не підтримує.
Пастки
- Немає тайм-ауту. Зависла БД тримає пробу довше, ніж чекає оркестратор.
- Частота. Оркестратор, балансувальник і моніторинг разом дають десятки запитів до БД за хвилину на кожен екземпляр.
AllowCachingResponsesкерує HTTP-заголовками кешування, а не кешем результату перевірки.- Детальна відповідь. Список залежностей і тексти помилок корисні зловмиснику. Ендпоінт закривають через
RequireHostна службовий порт абоRequireAuthorization.
Правило
Liveness — без зовнішніх залежностей, readiness — лише критичні залежності й завжди з тайм-аутом.
Під капотом
DefaultHealthCheckServiceзапускає перевірки паралельно черезTask.Runі чекає на всі. Кожна отримує свій DI-scope і токен, який скасовується за її тайм-аутом.- Виняток або тайм-аут дають
failureStatusреєстрації, за замовчуваннямUnhealthy. IHealthCheckPublisherотримуєHealthReportвід фонового сервісу: перший запуск через 5 секунд, далі кожні 30. З .NET 8 для окремої реєстрації можна задати своїDelayіPeriod.
Ціна
- Важка перевірка, помножена на часті проби, — відчутне навантаження на залежність. Його знімають кешем результату на кілька секунд усередині перевірки.
- Інший варіант — publisher зберігає останній звіт, а власний ендпоінт віддає його без запиту до БД. Вбудований
MapHealthChecksтак не вміє: він виконує перевірки за кожного звернення.
Компроміси
- Каскадна відмова. Readiness з перевіркою спільної залежності вимикає весь пул разом. Якщо без неї сервіс бодай частково працює (кеш, черга), краще віддати
Degraded, і нехай відмовляють окремі запити. - Глибокі перевірки. Сервіс A перевіряє B, B перевіряє C, і падіння C робить «хворими» всіх. Сусідні сервіси в readiness зазвичай не перевіряють: від їхніх збоїв захищають тайм-аути й circuit breaker.
Як перевірити в продакшені
Publisher пише статус і тривалість кожної перевірки в лог або метрику. Так видно флапи й повільні перевірки. Алерт ставлять не на одну невдалу пробу, а на частку неготових екземплярів.
Підсумок: health check керує трафіком, тому в ньому перевіряють лише те, що справді змінює рішення про трафік.
using Microsoft.AspNetCore.Diagnostics.HealthChecks;using Microsoft.Data.SqlClient;using Microsoft.Extensions.Diagnostics.HealthChecks;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHealthChecks() .AddCheck<SqlHealthCheck>("sql", tags: ["ready"], timeout: TimeSpan.FromSeconds(3));
var app = builder.Build();
// Liveness: жодної перевірки — відповів, отже процес живийapp.MapHealthChecks("/health/live", new HealthCheckOptions { Predicate = _ => false });
// Readiness: лише перевірки з тегом "ready"; Unhealthy → 503app.MapHealthChecks("/health/ready", new HealthCheckOptions{ Predicate = check => check.Tags.Contains("ready")});
app.Run();
sealed class SqlHealthCheck(IConfiguration config) : IHealthCheck{ public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken ct = default) { // Виняток або тайм-аут (ct скасується через 3 с) стануть Unhealthy await using var conn = new SqlConnection(config.GetConnectionString("Default")); await conn.OpenAsync(ct); await using var cmd = new SqlCommand("SELECT 1", conn); await cmd.ExecuteScalarAsync(ct); return HealthCheckResult.Healthy(); }}using Microsoft.Extensions.Diagnostics.HealthChecks;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHealthChecks() .AddCheck("self", () => HealthCheckResult.Healthy());builder.Services.Configure<HealthCheckPublisherOptions>(o => o.Period = TimeSpan.FromSeconds(15));builder.Services.AddSingleton<IHealthCheckPublisher, LogPublisher>();
var app = builder.Build();app.Run();
sealed class LogPublisher(ILogger<LogPublisher> logger) : IHealthCheckPublisher{ public Task PublishAsync(HealthReport report, CancellationToken ct) { foreach (var (name, entry) in report.Entries) { // Історію статусів і тривалостей видно в сховищі логів, а не лише в останній відповіді logger.LogInformation("Health check {Check}: {Status} in {Duration} ms", name, entry.Status, entry.Duration.TotalMilliseconds); } return Task.CompletedTask; }}- Чому liveness не повинен перевіряти БД? — Рестарт не лагодить БД, а оркестратор перезапускатиме всі екземпляри по колу.
- Який код поверне
Degraded? — 200 за замовчуванням; змінюється черезResultStatusCodes. - Що буде, якщо перевірка кине виняток? — Результат отримає
failureStatusреєстрації, за замовчуваннямUnhealthy. - Як не навантажувати БД частими пробами? — Тайм-аут, кеш результату на кілька секунд або віддача останнього звіту від publisher.
- Як захистити ендпоінт здоров’я? —
RequireHostна службовий порт,RequireAuthorization, коротка відповідь назовні.
Які метрики збирати для вебсервісу і як?
Section titled “Які метрики збирати для вебсервісу і як?”Що це
Метрика — числовий показник, який сервіс записує постійно, а система моніторингу зберігає як графік у часі.
Що збирати: RED
- Rate — запитів за секунду.
- Errors — частка запитів із помилкою, наприклад відповідей 5xx.
- Duration — час відповіді.
Як користуватися
- Вбудована метрика ASP.NET Core
http.server.request.duration(.NET 8+) уже дає всі три значення з розбиттям за маршрутом і кодом відповіді. - Бізнес-події («створено замовлення») рахують власним лічильником
Counter<T>. - Локально метрики дивляться через
dotnet-counters, у продакшені — у Prometheus і Grafana або в APM.
Типова помилка
Дивитися на середній час відповіді. 99 запитів по 10 мс і один на 10 секунд дають середнє близько 110 мс, а користувач чекав 10 секунд.
Як влаштовано
Meter— іменоване джерело метрик. В ASP.NET Core його створюють черезIMeterFactoryз DI (.NET 8+).- Мітки (tags) — пари «ключ — значення» в кожного вимірювання: маршрут, код відповіді, спосіб оплати.
| Інструмент | Для чого |
|---|---|
Counter<T> |
лічильник подій, що росте: замовлення, помилки |
UpDownCounter<T> |
значення росте й спадає: активні з’єднання |
Histogram<T> |
розподіл: тривалість, розмір відповіді |
ObservableGauge<T> |
поточне значення, читається під час збору: довжина черги |
З .NET 9 є і синхронний Gauge<T>: значення записують у момент зміни.
Коли що обирати
| Підхід | Що вимірює |
|---|---|
| RED | кожен сервіс чи ендпоінт: rate, errors, duration |
| USE | кожен ресурс (CPU, пам’ять, пул з’єднань): утилізація, насичення, помилки |
| Золоті сигнали | latency, traffic, errors, saturation — RED плюс насичення |
Вбудовані метрики
Microsoft.AspNetCore.Hosting:http.server.request.duration,http.server.active_requests.Microsoft.AspNetCore.Server.Kestrel: з’єднання, черги, TLS-рукостискання.System.Runtime(.NET 9+): GC, пул потоків, CPU,dotnet.exceptions.- У .NET 10 додалися метрики автентифікації, авторизації, Identity і Blazor.
Пастки
user.idабо шлях/orders/42у мітці — мільйони рядів. Мітка — шаблон маршрутуhttp.route.- Тривалість у мілісекундах в одному сервісі й у секундах в іншому. Одиниці вказують явно (
s,By,{order}), стандарт — секунди.
Правило
RED на кожен ендпоінт, USE на кожен обмежений ресурс, лічильники — на ключові бізнес-операції.
Під капотом
AddабоRecordбез слухача — перевірка прапорця й вихід. Слухач (OpenTelemetry SDK,MeterListener,dotnet-countersчерез EventPipe) агрегує в процесі: суму для лічильника, бакети для гістограми.- У сховище потрапляє не кожне вимірювання, а ряд на кожну комбінацію міток. Prometheus забирає агрегати по HTTP, OTLP надсилає їх сам.
Ціна
- Рядів = добуток кількостей значень міток × бакети гістограми × екземпляри. 50 маршрутів × 10 кодів × 4 методи × 15 бакетів × 20 подів — до 600 тисяч рядів в однієї метрики.
- Сховище метрик платить пам’яттю за кожен активний ряд, тому нова мітка з тисячею значень — рішення, а не дрібниця.
Компроміси
- Перцентиль за гістограмою приблизний: точність залежить від меж бакетів. Для
http.server.request.durationз OpenTelemetry за замовчуванням межі від 5 мс до 10 с. Свої межі задаютьInstrumentAdvice(.NET 9+) або View в OpenTelemetry. - Перцентилі не можна усереднювати за екземплярами: p99 рахують із суми бакетів, у Prometheus —
histogram_quantileзаsum by (le). - Середнє й максимум дешевші, але не відповідають на питання «скільки користувачів чекали довше за секунду». Частка запитів, швидших за поріг, рахується з тих самих бакетів.
Як перевірити в продакшені
dotnet-counters monitor -n MyApp --counters Shop.Orders— власні метрики живого процесу без експорту.- У Prometheus
count(http_server_request_duration_seconds_bucket)показує, скільки рядів дає одна метрика.
Підсумок: перцентилі рахують із підсумованих гістограм, а ціну метрик визначає кардинальність міток.
using System.Diagnostics;using System.Diagnostics.Metrics;
var builder = WebApplication.CreateBuilder(args);builder.Services.AddSingleton<OrderMetrics>();
var app = builder.Build();
app.MapPost("/orders", async (OrderMetrics metrics) =>{ long start = Stopwatch.GetTimestamp(); await Task.Delay(50); // обробка замовлення metrics.OrderCreated("card", Stopwatch.GetElapsedTime(start)); return Results.Accepted();});
app.Run();
sealed class OrderMetrics{ private readonly Counter<long> _created; private readonly Histogram<double> _duration; private int _queueLength;
public OrderMetrics(IMeterFactory factory) { Meter meter = factory.Create("Shop.Orders"); _created = meter.CreateCounter<long>("shop.orders.created", unit: "{order}"); _duration = meter.CreateHistogram<double>("shop.orders.duration", unit: "s", advice: new InstrumentAdvice<double> { HistogramBucketBoundaries = [0.01, 0.05, 0.1, 0.5, 1, 5] }); // Значення читається лише в момент збору meter.CreateObservableGauge("shop.orders.queue.length", () => Volatile.Read(ref _queueLength), unit: "{order}"); }
public void OrderCreated(string paymentMethod, TimeSpan elapsed) { // Кілька можливих значень — низька кардинальність; id замовлення сюди не кладуть var tag = new KeyValuePair<string, object?>("payment.method", paymentMethod); _created.Add(1, tag); _duration.Record(elapsed.TotalSeconds, tag); }}# Власні та вбудовані метрики запущеного процесуdotnet-counters monitor -n MyShop --counters Shop.Orders,Microsoft.AspNetCore.Hosting- Чим RED відрізняється від USE? — RED — для запитів до сервісу, USE — для ресурсів: CPU, пам’яті, пулів.
- Чому p99, а не середнє? — Середнє ховає повільний хвіст, який бачать реальні користувачі.
- Чи можна усереднити p99 за подами? — Ні, підсумовують бакети гістограм і рахують перцентиль із суми.
- Чому не можна класти
user.idу мітку? — Кожне значення — новий часовий ряд, сховище роздувається. - Чим
ObservableGauge<T>відрізняється відGauge<T>? — Перший читає значення колбеком під час збору, другий записують у момент зміни (.NET 9+).
Як розібрати проблему продуктивності чи пам’яті на живому сервері?
Section titled “Як розібрати проблему продуктивності чи пам’яті на живому сервері?”Що це
Діагностика на живому сервері — пошук причини без налагоджувача й без зупинки сервісу.
Як користуватися
- За метриками визначити симптом: що зросло і коли.
- Подивитися лічильники процесу:
dotnet-counters monitor -n MyApp. - Високий CPU — записати трейс на 30 секунд і відкрити у Visual Studio або PerfView.
- Росте пам’ять або процес завис — зняти дамп і відкрити в
dotnet-dump analyze.
Інструменти
| Інструмент | Що дає |
|---|---|
dotnet-counters |
живі метрики: CPU, GC, пул потоків, запити |
dotnet-trace |
де витрачається час, події рантайму |
dotnet-dump |
знімок усієї пам’яті й стеків потоків |
dotnet-gcdump |
лише граф об’єктів купи |
Типова помилка
Одразу перезапустити процес, нічого не знявши. Симптом зник, докази теж, і за день усе повториться.
Як влаштовано
Інструменти підключаються до процесу через diagnostic port: іменований канал у Windows або Unix-сокет у /tmp у Linux. Дані йдуть через EventPipe, тому не потрібні ні SDK на сервері, ні перезапуск. Потрібні той самий користувач, що в процесу, або root.
Коли що обирати
| Симптом | Що дивитися |
|---|---|
| Високий CPU | dotnet-trace: гарячі стеки в PerfView або speedscope |
| Росте пам’ять | зростання купи після GC у dotnet-counters, потім два dotnet-gcdump |
| Повільно, CPU низький | черга й потоки пулу в dotnet-counters, стеки в дампі |
| Повільні запити до БД | трейси з SQL-спанами, потім план запиту |
| Сплеск винятків | dotnet.exceptions за error.type |
Подробиці: витоки — у темі Збирання сміття й ресурси, голодування пулу — у Багатопотоковості, плани запитів — в Індексах.
Пастки
- Профіль
dotnet-traceза замовчуванням семплює всі керовані потоки, зокрема ті, що чекають. У звіті відокремлюють роботу від очікування. Профільcpu-samplingзі старих інструкцій у нових версіях інструмента видалено. - У Linux інструменту й процесу потрібен спільний
TMPDIR, інакше підключення відвалиться за тайм-аутом. dotnet-gcdumpвикликає повне збирання сміття, на великій купі це помітна пауза.
Правило
Одна гіпотеза — один точковий збір. Спочатку лічильники, потім трейс, дамп — останнім.
План за кроками
- Пом’якшити. Відкотити реліз, якщо проблема збіглася з деплоєм, або вивести екземпляр із балансування. Один «хворий» екземпляр залишити для збору.
- Метрики. Коли почалося, усі екземпляри чи один, чи збігається з деплоєм, зростанням трафіку, паузами GC.
- Легкий збір.
dotnet-counters, потімdotnet-traceна 30–60 секунд. - Важкий збір. Дамп — лише коли потрібен. Перед ним перевірити місце на диску (дамп важить як пам’ять процесу) і ліміт пам’яті контейнера: збір може підкачати пам’ять і довести до OOM kill.
- Аналіз поза продакшеном. Дамп копіюють на робочу машину й розбирають там.
- Перевірка. Виправлення підтверджують тими самими метриками.
Контейнери
- Sidecar бачить процес, якщо в контейнерів спільний простір процесів і спільний
/tmp(наприклад,emptyDir). Дляdotnet-dumpпотрібна можливістьptrace(SYS_PTRACE). dotnet-monitor— готовий sidecar з HTTP API:/dump,/gcdump,/trace,/metrics. Його collection rules з тригерами на кшталтAspNetRequestDurationсамі знімають артефакт у момент проблеми й вивантажують у сховище.- Для падінь рантайм пише дамп сам за
DOTNET_DbgEnableMiniDump=1.
Ціна
- Поки пишеться дамп, процес стоїть: на великій купі це секунди й більше.
- У дампі вся пам’ять: рядки підключення, токени, персональні дані. Його зберігають і передають як секрет і видаляють після розбору.
Підсумок: у продакшені спочатку рятують користувачів, потім знімають мінімально достатні докази, а важкі знімки роблять на виведеному з балансування екземплярі.
# 1. Які .NET-процеси видно (той самий користувач або root)dotnet-counters ps
# 2. Живі метрики: CPU, GC, пул потоків, запитиdotnet-counters monitor -p 1234 --counters System.Runtime,Microsoft.AspNetCore.Hosting
# 3. Високий CPU: 30 секунд семплів стеків, файл для speedscope або PerfViewdotnet-trace collect -p 1234 --duration 00:00:00:30 --format Speedscope
# 4. Зростання пам'яті: граф об'єктів (викликає повне збирання сміття)dotnet-gcdump collect -p 1234 -o before.gcdump
# 5. Зависання або голодування пулу: повний дамп і стеки всіх потоківdotnet-dump collect -p 1234 -o /diag/app.dmpdotnet-dump analyze /diag/app.dmp -c "threadpool" -c "parallelstacks" -c "exit"{ "CollectionRules": { "SlowRequests": { "Trigger": { "Type": "AspNetRequestDuration", "Settings": { "RequestCount": 10, "RequestDuration": "00:00:03", "SlidingWindowDuration": "00:01:00" } }, "Actions": [ { "Type": "CollectTrace", "Settings": { "Profile": "Cpu", "Duration": "00:00:30", "Egress": "TmpDir" } } ], "Limits": { "ActionCount": 1, "ActionCountSlidingWindowDuration": "01:00:00" } } }}Десять запитів довших за три секунди за хвилину — і dotnet-monitor сам пише 30-секундний трейс, не частіше ніж раз на годину.
- Чим
dotnet-dumpвідрізняється відdotnet-gcdump? — Дамп — уся пам’ять процесу зі значеннями полів, gcdump — лише граф об’єктів, легкий, але з повним збиранням сміття. - Чому
dotnet-traceне бачить процес у сусідньому контейнері? — Немає спільного/tmpз diagnostic port або спільного простору процесів. - Як зрозуміти, що повільно через пул потоків, а не через CPU? — CPU низький, черга пулу довга, потоків дедалі більше; стеки показують блокувальні виклики.
- Як упіймати проблему, що триває хвилину вночі? — Правила
dotnet-monitorз тригером або--stopping-eventуdotnet-trace. - Чим небезпечний дамп у продакшені? — Пауза процесу, зростання пам’яті до OOM kill, персональні дані у файлі.
Що таке SLI, SLO та error budget і як налаштувати алерти, що не будять даремно?
Section titled “Що таке SLI, SLO та error budget і як налаштувати алерти, що не будять даремно?”Що це
- SLI (service level indicator) — що вимірюємо: частка успішних запитів, частка запитів, швидших за 300 мс.
- SLO (service level objective) — ціль: 99,9% за 30 днів.
- SLA — договір із клієнтом із наслідками, зазвичай м’якший за внутрішній SLO.
- Error budget — 100% − SLO: скільки невдач можна собі дозволити.
Навіщо
SLO відповідає на питання «чи достатньо ми надійні?». 100% недосяжно й не потрібно: користувач не відрізнить 99,99% від 100%.
Як користуватися
- Бюджет є — викочуємо фічі. Бюджет скінчився — займаємося надійністю.
- Алерт спрацьовує, коли страждають користувачі, а не коли «щось виглядає дивно».
Типова помилка
Алерт на кожен сплеск CPU або на одну помилку 500. Черговий звикає ігнорувати сповіщення й пропускає справжню відмову.
Як влаштовано
- SLI = добрі події / усі події. Його рахують із метрик, наприклад з
http.server.request.durationз кодом відповіді. - SLO задають на вікно, зазвичай ковзні 28 або 30 днів.
- Бюджет у хвилинах: 99,9% за 30 днів — 43,2 хвилини повного простою, 99,99% — 4,3 хвилини.
Коли що обирати
| Алерт | Приклад | Куди |
|---|---|---|
| Симптомний | частка 5xx, p99 вище за ціль SLO | будить чергового |
| Причинний | CPU 90%, диск заповнений на 80% | дашборд або тікет |
| Причинний, але неминучий | диск заповниться за 4 години | будить: відмова ось-ось станеться |
Пастки
- Мало трафіку. Поріг «понад 1% помилок за 5 хвилин» уночі спрацьовує від однієї помилки на 50 запитів.
- Алерт без дії. Якщо на сповіщення нічого зробити, це рядок на дашборді, а не алерт.
- Лише доступність. SLO за помилками не помітить, що все стало повільним; потрібен ще SLI за затримкою.
Правило
Будить лише те, що потребує дії людини просто зараз, і в кожного такого алерту є runbook.
Під капотом: burn rate
Burn rate = частка помилок / (1 − SLO). За SLO 99,9% і 1,44% помилок burn rate дорівнює 14,4: місячний бюджет згорить за 50 годин. Рекомендація SRE Workbook для 99,9%:
| Витрата бюджету | Вікна: довге / коротке | Burn rate і реакція |
|---|---|---|
| 2% | 1 год / 5 хв | 14,4 — будити |
| 5% | 6 год / 30 хв | 6 — будити |
| 10% | 3 дні / 6 год | 1 — тікет |
Алерт спрацьовує, коли перевищено обидва вікна: довге дає точність, коротке — швидке скидання після відновлення.
Ціна
- Нічне пробудження — ціна в людях. Google SRE розраховує максимум на два інциденти за 12-годинну зміну: більше — і розбір перетворюється на гасіння пожеж.
- Надто жорсткий SLO гальмує викочування, надто м’який не захищає користувачів. Ціль беруть від того, що помічає користувач, а не від поточних графіків.
Компроміси
- Error budget policy заздалегідь домовляється про наслідки: бюджет вичерпано — фічі чекають, команда лагодить надійність. Без політики SLO лишається цифрою на дашборді.
- Runbook — кроки діагностики й безпечні дії для конкретного алерту. Runbook, що повторюється, — кандидат на автоматизацію.
Розбір інциденту
Blameless postmortem фіксує хронологію, вплив на користувачів, причини й дії з власниками та строками. Питання — «чому система дозволила цьому статися», а не «хто винен»: інакше люди починають приховувати помилки.
Підсумок: алерт за швидкістю витрати error budget будить лише тоді, коли користувачі справді втрачають обіцяну якість.
groups: - name: orders-api-slo rules: # SLI: частка відповідей 5xx за вікно - record: slo:error_ratio:rate5m expr: | sum(rate(http_server_request_duration_seconds_count{job="orders-api",http_response_status_code=~"5.."}[5m])) / sum(rate(http_server_request_duration_seconds_count{job="orders-api"}[5m])) - record: slo:error_ratio:rate1h expr: | sum(rate(http_server_request_duration_seconds_count{job="orders-api",http_response_status_code=~"5.."}[1h])) / sum(rate(http_server_request_duration_seconds_count{job="orders-api"}[1h])) # SLO 99,9% → бюджет 0,001; burn rate 14,4 = 2% місячного бюджету за годину - alert: OrdersApiErrorBudgetBurn expr: | slo:error_ratio:rate1h > (14.4 * 0.001) and slo:error_ratio:rate5m > (14.4 * 0.001) labels: severity: page annotations: summary: "orders-api is burning its error budget 14x faster than allowed" runbook_url: "https://wiki.example.com/runbooks/orders-api-errors"Правило Prometheus будить чергового, лише якщо помилок багато і за останню годину, і за останні 5 хвилин.
- Чим SLO відрізняється від SLA? — SLO — внутрішня ціль, SLA — договір із наслідками, зазвичай м’якший за SLO.
- Скільки простою дає 99,9% на місяць? — Близько 43 хвилин.
- Що таке burn rate 1? — Бюджет закінчиться рівно до кінця вікна SLO.
- Навіщо коротке вікно в алерті за burn rate? — Щоб алерт швидко згас після відновлення, а не висів годину.
- Що має бути в postmortem? — Хронологія, вплив, причини, дії з власниками; без пошуку винних.