Як називати ваші метрики

Метрики — кількісна основа спостережуваності, числа, що показують, як працюють наші системи. Це третя публікація в нашій серії про іменування в OpenTelemetry; раніше ми вже розглядали як називати відрізки та як збагачувати їх корисними атрибутами. Тепер розглянемо мистецтво називати вимірювання, які мають значення.

На відміну від відрізків, що розповідають історії про те, що сталося, метрики говорять про кількість: скільки, як швидко, наскільки. Але правильно їх називати так само важливо, як і відрізки, і принципи, які ми вивчили, також застосовні тут. “Хто” має знаходитись в атрибутах, а не в назвах.

Приклад традиційних систем

Перш ніж заглиблюватись у практики OpenTelemetry, погляньмо, як традиційні системи обробляють іменування метрик. Наприклад, Kubernetes використовує патерни на кшталт:

  • apiserver_request_total
  • scheduler_schedule_attempts_total
  • container_cpu_usage_seconds_total
  • kubelet_volume_stats_used_bytes

Помітили патерн? Компонент + ресурс + дія + одиниця виміру. Назва сервісу знаходиться прямо в імені метрики. Такий підхід мав сенс у простіших моделях даних, де контекст зберігався обмежено.

Але це створює кілька проблем:

  • Захаращення бекенду спостережуваності: кожен компонент отримує власний простір імен метрик, що ускладнює пошук потрібної метрики серед десятків або сотень метрик із подібними назвами.
  • Негнучка агрегація: важко підсумовувати метрики для різних компонентів.
  • Привʼязування до постачальника: імена метрик привʼязані до конкретної реалізації.
  • Витрати на підтримку: додавання нових сервісів вимагає нових імен метрик.

Основний антипатерн: імена сервісів у назвах метрик

Найважливіший принцип для метрик OpenTelemetry: не додавайте імʼя сервісу в назву метрики.

Припустимо, у вас є сервіс платежів. Можна спокуситися створити метрики:

  • payment.transaction.count
  • payment.latency.p95
  • payment.error.rate

Не робіть цього. Імʼя сервісу вже є в ресурсному атрибуті service.name. Натомість використовуйте:

  • transaction.count з service.name=payment
  • http.server.request.duration з service.name=payment
  • error.rate з service.name=payment

Чому це краще? Тому що тепер легко виконувати агрегацію по всіх сервісах:

sum(transaction.count)  // Всі транзакції по всіх сервісах
sum(transaction.count{service.name="payment"})  // Тільки транзакції сервісу payment

Якщо б кожен сервіс мав власне імʼя метрики, довелося б знати всі імена сервісів, щоб будувати корисні дашборди. З чистими іменами один запит працює для всього.

Повноконтекстна модель OpenTelemetry

Метрики OpenTelemetry виграють від тієї самої повноконтекстної моделі, про яку ми говорили в статті про атрибути відрізків. Замість того, щоб упаковувати все в імʼя метрики, у нас є кілька шарів, де може існувати контекст:

Традиційний підхід (стиль Prometheus):

payment_service_transaction_total{method="credit_card",status="success"}
user_service_auth_latency_milliseconds{endpoint="/login",region="us-east"}
inventory_service_db_query_seconds{table="products",operation="select"}

Підхід OpenTelemetry:

transaction.count
- Resource: service.name=payment, service.version=1.2.3, deployment.environment.name=prod
- Scope: instrumentation.library.name=com.acme.payment, instrumentation.library.version=2.1.0
- Attributes: method=credit_card, status=success

auth.duration
- Resource: service.name=user, service.version=2.0.1, deployment.environment.name=prod
- Scope: instrumentation.library.name=express.middleware
- Attributes: endpoint=/login, region=us-east
- Unit: ms

db.client.operation.duration
- Resource: service.name=inventory, service.version=1.5.2
- Scope: instrumentation.library.name=postgres.client
- Attributes: db.sql.table=products, db.operation=select
- Unit: s

Ця трирівнева сепарація відповідає специфікації OpenTelemetry — модель Events → Metric Streams → Timeseries, де контекст проходить через кілька ієрархічних рівнів, замість того щоб бути запханим у назви.

Одиниці виміру: теж не місце в іменах

Як ми вже зʼясували з імен відрізків, назва сервісу не має бути в імені метрики, так само й одиниці виміру не повинні бути в імені.

Традиційні системи часто додають одиниці в назву через відсутність метаданих щодо одиниць виміру:

  • response_time_milliseconds
  • memory_usage_bytes
  • throughput_requests_per_second

OpenTelemetry трактує одиниці виміру як метадані, окремі від назви:

  • http.server.request.duration з одиницею виміру ms
  • system.memory.usage з одиницею виміруBy
  • http.server.request.rate з одиницею виміру {request}/s

Цей підхід має кілька переваг:

  1. Чисті імена: немає неприємних суфіксів у назвах.
  2. Стандартизовані одиниці виміру: дотримуйтеся Unified Code for Units of Measure (UCUM).
  3. Гнучкість бекенду: системи можуть автоматично конвертувати одиниці.
  4. Узгоджені домовленості: відповідає семантичним домовленостям OpenTelemetry.

Специфікація рекомендує використовувати одиниці виміру без префіксів, такі як By (байти), замість MiBy (мебібайти), якщо немає технічних причин робити інакше.

Практичні поради з іменування

При створенні імен метрик застосовуйте принцип {дія} {обʼєкт}, схожий на той, що ми використовували для відрізків, де це доречно:

  1. Зосередьтеся на операції: що вимірюється?
  2. Не на тому, хто це робить: хто виконує вимірювання — вже є в атрибутах.
  3. Дотримуйтесь семантичних домовленостей: використовуйте встановлені патерни, коли вони доступні.
  4. Тримайте одиниці як метадані: не додавайте суфікси з одиницями.

Ось приклади згідно з семантичними домовленостями OpenTelemetry:

  • http.server.request.duration (не payment_http_requests_ms)
  • db.client.operation.duration (не user_service_db_queries_seconds)
  • messaging.client.sent.messages (не order_service_messages_sent_total)
  • transaction.count (не payment_transaction_total)

Приклади міграції в реальному світі

Традиційно (контекст + одиниці в імені)OpenTelemetry (чисте розділення)Чому це краще
payment_transaction_totaltransaction.count + service.name=payment + unit 1Агрегується між сервісами
user_service_auth_latency_msauth.duration + service.name=user + unit msСтандартна назва операції, правильно вказана одиниця
inventory_db_query_secondsdb.client.operation.duration + service.name=inventory + unit sВідповідає семантичним домовленостям
api_gateway_requests_per_secondhttp.server.request.rate + service.name=api-gateway + unit {request}/sЧиста назва, правильна одиниця для rate
redis_cache_hit_ratio_percentcache.hit_ratio + service.name=redis + unit 1Співвідношення без одиниць виміру

Переваги чистого іменування

Розділення контексту та назв метрик дає конкретні технічні переваги, що покращують як продуктивність запитів, так і операційні процеси. Перша перевага — агрегація між сервісами. Запит на кшталт sum(transaction.count) повертає дані зі всіх сервісів без потреби знати чи підтримувати список імен сервісів. У системі з 50 мікросервісами це означає один запит замість 50, і цей запит не зламається, коли зʼявиться 51-й сервіс.

Така послідовність дозволяє використовувати дашборди повторно для різних сервісів. Дашборд для моніторингу HTTP-запитів у службі автентифікації працюватиме без змін для сервісу платежів, інвентаризації чи будь-якого іншого компонента, що обслуговує HTTP. Ви пишете запит один раз, http.server.request.duration, фільтруєте за service.name, і застосовуєте його скрізь. Більше не потрібно підтримувати десятки майже однакових дашбордів. Деякі платформи спостережуваності йдуть ще далі й генерують дашборди автоматично на основі семантичних імен метрик, коли ваші сервіси надсилають http.server.request.duration, платформа знає, які візуалізації й агрегування мають сенс.

Чисте іменування також зменшує захаращення простору імен метрик. Уявіть платформу з десятками сервісів, кожен визначає власні метрики. З традиційним іменуванням в оглядачі метрик ви побачите сотні варіацій: apiserver_request_total, payment_service_request_total, user_service_request_total, inventory_service_request_total тощо. З чистими іменами у вас одна назва (request.count) із атрибутами, що описують контекст. Це спрощує пошук — знаходите вимірювання, а потім фільтруєте за сервісом.

Обробка одиниць вимірювання стає систематичною, якщо одиниці — метадані, а не суфікси в назві. Платформи можуть виконувати конвертацію одиниць автоматично — відображати ту саму метрику тривалості як мілісекунди в одному графіку і як секунди в іншому, залежно від необхідності. Метрика залишається request.duration з одиницею ms, а не двома окремими метриками request_duration_ms і request_duration_seconds.

Такий підхід також забезпечує сумісність між ручною та автоматичною інструменталізацією. Коли ви дотримуєтесь семантичних домовленостей, наприклад http.server.request.duration, ваші власні метрики узгоджуються з тими, що генеруються автоінструментуванням. Це створює єдину модель даних, де запити працюють як для ручної, так і для автоматичної інструменталізації, і інженерам не потрібно памʼятати, звідки походить метрика.

Поширені помилки, яких слід уникати

Інженери часто включають у назви метрик розгортання чи інші специфічні дані, створюючи патерни на кшталт user_service_v2_latency. Це ламає все, коли зʼявляється версія 3 — доводиться змінювати кожен дашборд, алерт і запит, що посилаються на цю метрику. Та сама проблема з іменами на основі екземпляру, наприклад node_42_memory_usage. У кластері з динамічним масштабуванням ви отримаєте сотні різних імен метрик, що представляють одне й те саме вимірювання, і написати просту агрегацію стане неможливо.

Префікси, що вказують на середовище (prod_, staging_), створюють аналогічні проблеми. Метрики prod_payment_errors і staging_auth_count не дозволяють написати універсальний запит, що працює для всіх середовищ. Порівнювати метрики між середовищами, поширене завдання при налагодженні, і з такими назвами доведеться писати складні запити, що явно перелічують кожну назву метрики для кожного середовища.

Деталі стека технологій у назві метрики створюють проблеми при міграції. Метрика nodejs_payment_memory стає такою, що вводить в оману після переписування сервісу на Go. Аналогічно, postgres_user_queries потребуватиме перейменування при переході на іншу базу даних. Технологічні суфікси також перешкоджають написанню запитів, що працюють між сервісами, які виконують однакові бізнес-функції, але реалізовані різними технологіями.

Змішування бізнес-домену з інфраструктурними метриками порушує розділення обовʼязків. Метрика ecommerce_cpu_usage змішує бізнес-мету (електронна комерція) з технічним виміром (CPU). Це ускладнює повторне використання моніторингу інфраструктури між різними бізнес-напрямами і створює проблеми в багатокористувацьких середовищах.

Практика додавання одиниць вимірювання у назви метрик, latency_ms, memory_bytes, count_total, зараз є надлишковою, бо OpenTelemetry надає метадані одиниць виміру. Вона також заважає автоматичній конвертації одиниць вимірювання. Замість двох метрик request_duration_ms і request_duration_seconds краще мати одну request.duration з одиницею ms, а платформа вже покаже її як потрібно.

Патерн зрозумілий: контекст, що змінюється залежно від розгортання, екземпляру, середовища чи версії, має належати атрибутам, а не імені метрики. Назва метрики повинна ідентифікувати те, що вимірюється. Усе інше, хто вимірює, де, коли і як, живе в шарі атрибутів OpenTelemetry, де його можна фільтрувати, групувати та агрегувати.

Створення кращих метрик

Як і відрізки з нашої серії, правильно названі метрики — це подарунок вашому майбутньому «я» та команді. Вони дають зрозумілість під час інцидентів, дозволяють виконувати потужний крос-сервісний аналіз і роблять дані спостережуваності дійсно корисними, а не просто численними.

Ключова ідея та сама, що й для відрізків: розділення обовʼязків. Назва метрики описує, що ви вимірюєте. Контекст, хто вимірює, де, коли й як, знаходиться у повноцінному шарі атрибутів OpenTelemetry.

У наступному дописі ми детально розглянемо атрибути метрик — шар контексту, що робить метрики по-справжньому потужними. Ми поговоримо, як структурувати контекст, який не належить до імен, і як балансувати інформативність та проблеми кардинальності.

А поки памʼятайте: чисте імʼя метрики — як доглянута стежка в саду, веде вас саме туди, куди потрібно.

Востаннє змінено December 26, 2024: [uk] Ukrainian documentation for OpenTelemetry (2a3c5648)