OpenTelemetry и Grafana stack: production observability
Зачем знать: Observability — это не “просто логи и метрики”. Это способность отвечать на произвольные вопросы о работе системы в production. Middle 2 должен уметь развернуть полноценный observability-стек: traces + metrics + logs + profiling, объединить их через trace_id, настроить sampling, контролировать стоимость и overhead. OpenTelemetry стал de-facto стандартом в 2023-2026 — заменил OpenTracing, OpenCensus, Jaeger Client. Без знания OTel и Grafana stack ты не пройдёшь собеседование на middle 2 в любую современную инженерную команду.
Содержание
Заголовок раздела «Содержание»- Концепция: три столпа observability и модель OpenTelemetry
- Production-практики: SDK, Collector, Grafana stack
- Gotchas: cardinality, sampling, overhead
- Real cases: Datadog migration, Cloudflare, Uber
- Вопросы для собеседования
- Practice
- Источники
1. Концепция: три столпа observability и модель OpenTelemetry
Заголовок раздела «1. Концепция: три столпа observability и модель OpenTelemetry»1.1 Три столпа (Three Pillars)
Заголовок раздела «1.1 Три столпа (Three Pillars)»Observability традиционно строится на трёх типах сигналов:
Metrics — числовые ряды, агрегированные за временные окна.
- Низкая стоимость (сжимаются хорошо).
- Высокая cardinality убивает (про это ниже).
- Используются для: алертов, дашбордов, capacity planning, SLO.
- Примеры:
http_requests_total,request_duration_seconds,db_pool_active.
Logs — структурированные события с timestamp.
- Высокая детализация, дорогие в хранении.
- Идеальны для debugging конкретного инцидента.
- Структурированный (JSON) намного лучше plain text.
Traces — распределённые цепочки вызовов через сервисы.
- Показывают, где время теряется в request lifecycle.
- Дорогие в полном объёме → нужен sampling.
- Помогают понять межсервисные dependencies.
Дополнительные сигналы (2024-2026):
- Continuous profiling (Pyroscope, Parca) — непрерывные CPU/memory профили.
- eBPF events — низкоуровневые сигналы из ядра.
1.2 OpenTelemetry: единая модель
Заголовок раздела «1.2 OpenTelemetry: единая модель»OpenTelemetry (OTel) — это open-source observability framework от CNCF. Включает:
- Specification — единая модель данных, семантические конвенции.
- SDK — реализации для языков (Go, Java, Python, JS и др.).
- Collector — универсальный агент для приёма/обработки/отправки.
- Protocol (OTLP) — gRPC/HTTP формат для передачи телеметрии.
Цель OTel: vendor-neutral сбор телеметрии. Меняешь backend (Jaeger → Tempo → Datadog) без переписывания кода приложения.
1.3 Traces: anatomy
Заголовок раздела «1.3 Traces: anatomy»Trace — корневая сущность, представляющая запрос через систему.
trace_id: 128-bit уникальный идентификатор.- Состоит из spans.
Span — единица работы (HTTP вызов, DB запрос, обработка сообщения).
span_id: 64-bit.parent_span_id: ссылка на родителя.start_time,end_time.attributes: key-value (метаданные).events: timestamped logs внутри span.links: ссылки на другие spans (для async/fan-in).status: OK / ERROR / UNSET.
trace_id: abc123...└─ span: HTTP GET /api/order (root, span_id=001) ├─ span: SELECT FROM orders (span_id=002, parent=001) ├─ span: HTTP GET /api/user (span_id=003, parent=001) │ └─ span: SELECT FROM users (span_id=004, parent=003) └─ span: publish to Kafka (span_id=005, parent=001)1.4 Metrics: instrument types
Заголовок раздела «1.4 Metrics: instrument types»OTel определяет несколько типов instruments:
Synchronous (вызываются в hot path):
Counter— монотонно растущее значение (requests, errors).UpDownCounter— может расти/уменьшаться (active_connections).Histogram— распределение значений (latency).
Asynchronous (callback’и, читаются периодически):
ObservableCounter— снимок counter’а (CPU time used).ObservableUpDownCounter— снимок (memory used).ObservableGauge— мгновенное значение (current temperature).
Histogram заслуживает отдельного внимания:
- Конфигурируемые buckets (default OTel: 0, 5ms, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000).
- Exponential histograms (новый стандарт) — auto-scaling, точные перцентили.
1.5 Logs: structured logging
Заголовок раздела «1.5 Logs: structured logging»В Go 1.21+ появился log/slog — official structured logger.
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))logger.Info("order created", "order_id", orderID, "user_id", userID, "amount", amount,)OTel предоставляет logs bridge — slog handler, который превращает каждый лог в OTel LogRecord и отправляет в Collector. Logs автоматически коррелируются с активным span через trace_id.
1.6 Semantic Conventions
Заголовок раздела «1.6 Semantic Conventions»OTel определяет semantic conventions — стандартные имена атрибутов:
service.name,service.version,service.namespace— обязательные resource attributes.http.request.method,http.response.status_code,url.path,url.full.db.system,db.statement,db.name.messaging.system,messaging.destination.name,messaging.operation.rpc.system,rpc.service,rpc.method.
Использование semantic conventions критично — иначе дашборды и алерты не работают одинаково между сервисами.
1.7 Context propagation: W3C TraceContext + Baggage
Заголовок раздела «1.7 Context propagation: W3C TraceContext + Baggage»Между сервисами нужно пробросить trace_id и parent_span_id. Это делается через HTTP headers по W3C TraceContext стандарту:
traceparent: 00-<trace_id>-<span_id>-<flags>tracestate: vendor1=value1,vendor2=value2baggage: user_id=42,tenant=acmeBaggage — это произвольный key-value контекст, который пробрасывается через все hop’ы. Используется для:
- Tenant ID, feature flags, A/B test variant.
- НЕ для секретов — baggage видим в трафике.
ctx = baggage.ContextWithValues(ctx, baggage.String("tenant", "acme"))// В downstream сервисе:tenant := baggage.FromContext(ctx).Member("tenant").Value()1.8 Sampling: head-based vs tail-based
Заголовок раздела «1.8 Sampling: head-based vs tail-based»Head-based sampling — решение в начале трейса (на корневом сервисе).
TraceIDRatioBased(0.1)— 10% всех трейсов.ParentBased(...)— следовать решению родителя (если родитель сэмплирован — мы тоже).- Плюсы: дешёво, простой алгоритм.
- Минусы: можно пропустить редкие важные трейсы (ошибки, slow).
Tail-based sampling — решение в конце трейса, в Collector’е.
- Видит все spans трейса перед решением.
- Policies: keep error traces, keep slow traces (latency > 1s), keep N% normal.
- Плюсы: гарантированно ловит проблемы.
- Минусы: Collector должен буферизовать ВСЕ spans до конца трейса → память, задержка.
Production стратегия (2026):
- App: head sampling 100% (всё в Collector).
- Collector: tail sampling — keep 100% errors/slow, 1-5% normal.
1.9 Exemplars: bridge metrics ↔ traces
Заголовок раздела «1.9 Exemplars: bridge metrics ↔ traces»Exemplar — это запись внутри метрики, содержащая trace_id примера. Когда видишь spike в histogram’е latency, можешь кликнуть на exemplar и перейти в trace, который вызвал этот spike.
В Prometheus exemplars поддерживаются с 2.26+ через OpenMetrics format. В Go SDK включаются автоматически, если есть активный span при записи метрики.
2. Production-практики: SDK, Collector, Grafana stack
Заголовок раздела «2. Production-практики: SDK, Collector, Grafana stack»2.1 Go SDK setup (production)
Заголовок раздела «2.1 Go SDK setup (production)»package observability
import ( "context"
"go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" "go.opentelemetry.io/otel/propagation" sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.26.0")
func Setup(ctx context.Context, serviceName, version string) (func(context.Context) error, error) { res, err := resource.New(ctx, resource.WithAttributes( semconv.ServiceName(serviceName), semconv.ServiceVersion(version), semconv.DeploymentEnvironment("production"), ), resource.WithFromEnv(), resource.WithHost(), resource.WithProcess(), resource.WithContainer(), ) if err != nil { return nil, err }
// Traces traceExporter, err := otlptracegrpc.New(ctx, otlptracegrpc.WithEndpoint("otel-collector:4317"), otlptracegrpc.WithInsecure(), ) if err != nil { return nil, err } tp := sdktrace.NewTracerProvider( sdktrace.WithBatcher(traceExporter, sdktrace.WithMaxQueueSize(2048), sdktrace.WithMaxExportBatchSize(512), ), sdktrace.WithResource(res), sdktrace.WithSampler(sdktrace.ParentBased( sdktrace.TraceIDRatioBased(1.0), // 100% — Collector сделает tail sampling )), ) otel.SetTracerProvider(tp)
// Metrics metricExporter, err := otlpmetricgrpc.New(ctx, otlpmetricgrpc.WithEndpoint("otel-collector:4317"), otlpmetricgrpc.WithInsecure(), ) if err != nil { return nil, err } mp := sdkmetric.NewMeterProvider( sdkmetric.WithReader(sdkmetric.NewPeriodicReader(metricExporter, sdkmetric.WithInterval(15*time.Second), )), sdkmetric.WithResource(res), ) otel.SetMeterProvider(mp)
// Propagators otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator( propagation.TraceContext{}, propagation.Baggage{}, ))
return func(ctx context.Context) error { if err := tp.Shutdown(ctx); err != nil { return err } return mp.Shutdown(ctx) }, nil}2.2 Manual instrumentation
Заголовок раздела «2.2 Manual instrumentation»var tracer = otel.Tracer("order-service")
func (s *Service) CreateOrder(ctx context.Context, req CreateOrderReq) (*Order, error) { ctx, span := tracer.Start(ctx, "Service.CreateOrder", trace.WithAttributes( attribute.String("order.user_id", req.UserID), attribute.Int("order.items_count", len(req.Items)), ), ) defer span.End()
order, err := s.repo.Create(ctx, req) if err != nil { span.RecordError(err) span.SetStatus(codes.Error, "create failed") return nil, err } span.SetAttributes(attribute.String("order.id", order.ID)) return order, nil}2.3 Auto-instrumentation
Заголовок раздела «2.3 Auto-instrumentation»Готовые библиотеки покрывают 80% инструментации:
otelhttp— net/http (server + client).otelgrpc— gRPC interceptors.otelsql— database/sql wrapper.otelpgx— pgx native instrumentation.otelmongo— MongoDB.otelredis— go-redis hook.otelkafka— Kafka producer/consumer.
// HTTP serverhandler := otelhttp.NewHandler(myHandler, "api")
// HTTP clientclient := http.Client{ Transport: otelhttp.NewTransport(http.DefaultTransport),}
// gRPC servergrpc.NewServer(grpc.UnaryInterceptor(otelgrpc.UnaryServerInterceptor()))
// SQLdb, _ := otelsql.Open("postgres", dsn, otelsql.WithAttributes(semconv.DBSystemPostgreSQL))2.4 Logs bridge: slog → OTel
Заголовок раздела «2.4 Logs bridge: slog → OTel»import ( "log/slog" "go.opentelemetry.io/contrib/bridges/otelslog")
logger := otelslog.NewLogger("order-service")slog.SetDefault(logger)
// Теперь все slog.Info/Error попадают в OTelslog.InfoContext(ctx, "order created", "id", order.ID)// trace_id и span_id автоматически прикрепляются2.5 OTel Collector
Заголовок раздела «2.5 OTel Collector»Collector — это сердце production setup’а. Архитектура:
[App SDK] --OTLP--> [Collector Agent (daemonset)] --OTLP--> [Collector Gateway (deployment)] --> [Backends]Pipeline: receivers → processors → exporters.
receivers: otlp: protocols: grpc: endpoint: 0.0.0.0:4317 http: endpoint: 0.0.0.0:4318 prometheus: config: scrape_configs: - job_name: 'apps' kubernetes_sd_configs: - role: pod
processors: memory_limiter: check_interval: 1s limit_mib: 1500 spike_limit_mib: 300 batch: timeout: 5s send_batch_size: 1024 resource: attributes: - key: cluster value: prod-eu-1 action: insert tail_sampling: decision_wait: 30s num_traces: 100000 policies: - name: errors type: status_code status_code: { status_codes: [ERROR] } - name: slow type: latency latency: { threshold_ms: 1000 } - name: random type: probabilistic probabilistic: { sampling_percentage: 5 }
exporters: otlp/tempo: endpoint: tempo:4317 tls: { insecure: true } prometheusremotewrite: endpoint: http://mimir:9009/api/v1/push loki: endpoint: http://loki:3100/loki/api/v1/push
service: pipelines: traces: receivers: [otlp] processors: [memory_limiter, tail_sampling, batch] exporters: [otlp/tempo] metrics: receivers: [otlp, prometheus] processors: [memory_limiter, batch] exporters: [prometheusremotewrite] logs: receivers: [otlp] processors: [memory_limiter, batch] exporters: [loki]Ключевые processors:
memory_limiter— должен быть ПЕРВЫМ, чтобы не уронить Collector.batch— последний или предпоследний, объединяет данные перед отправкой.tail_sampling— должен быть до batch для traces.attributes/resource— нормализация атрибутов.filter— отбрасывание ненужных метрик/spans (cardinality control).transform— мощный язык OTTL для трансформаций.
2.6 Grafana stack компоненты
Заголовок раздела «2.6 Grafana stack компоненты»Tempo — backend для traces.
- Object storage (S3, GCS, Azure Blob) — дешёвое хранение.
- Не индексирует по атрибутам (только по trace_id, time range).
- TraceQL — query language для поиска по trace атрибутам (с 2023).
- Hot/warm tiers.
Loki — backend для logs.
- Индексирует только метки (labels), не текст.
- Логи хранятся в object storage.
- LogQL — язык запросов (похож на PromQL).
- Дешевле Elasticsearch в 5-10 раз.
Mimir — distributed Prometheus (форк Cortex).
- Горизонтально масштабируемый long-term storage.
- Tenant isolation.
- Совместимый с Prometheus API.
Pyroscope — continuous profiling.
- pprof formats (CPU, heap, goroutine, mutex).
- В Go SDK:
pyroscope-go. - Flame graphs с поиском по времени и labels.
Grafana — UI dashboards.
- Data sources: Prometheus, Tempo, Loki, Pyroscope, ClickHouse, MySQL, etc.
- Correlations: jump from metric exemplar → trace → log.
2.7 Alloy (бывший Grafana Agent)
Заголовок раздела «2.7 Alloy (бывший Grafana Agent)»Alloy — universal telemetry collector от Grafana Labs (2024+). Конкурирует с OTel Collector.
- Поддерживает OTel pipeline.
- Lightweight (Go).
- Декларативный config (HCL-подобный).
- Используется на edge / k8s nodes.
2.8 VictoriaMetrics (популярно в РФ)
Заголовок раздела «2.8 VictoriaMetrics (популярно в РФ)»VictoriaMetrics — альтернатива Prometheus, написана в Москве (изначально):
- В 5-10 раз меньше disk usage.
- Быстрее в чтении (PromQL совместимый).
- Cluster version для HA.
- MetricsQL — расширения PromQL.
- VictoriaLogs (2023+) — конкурент Loki.
В компаниях типа Авито, Тинькофф, ВК — VictoriaMetrics стандарт.
2.9 ClickHouse как unified backend
Заголовок раздела «2.9 ClickHouse как unified backend»Uptrace, SigNoz — open-source observability платформы на ClickHouse.
- Один backend для traces + logs + metrics.
- Дешевле фрагментированного стека.
- ClickHouse отлично сжимает observability data.
- SQL-based queries.
В России ClickHouse-based observability особенно популярен — снимает зависимость от GitHub-disabled tooling.
2.10 Sampling strategies в production
Заголовок раздела «2.10 Sampling strategies в production»| Сценарий | Strategy | Rationale |
|---|---|---|
| Dev / staging | 100% head-based | Видеть всё |
| Низкий трафик (< 100 RPS) | 100% head-based | Cost тривиален |
| Средний (100-10k RPS) | Head 100% + tail в Collector | Гарантия ошибок |
| Высокий (> 10k RPS) | Head 10-50%, tail приоритезирует | Cost optimization |
| Очень высокий (> 100k RPS) | Adaptive sampling (rate-limiting) | Hard cost ceiling |
2.11 Cost considerations
Заголовок раздела «2.11 Cost considerations»Стоимость observability часто превышает стоимость самой инфраструктуры:
- Datadog: $15-23/host + $1.27/M spans (быстро растёт).
- New Relic: похоже.
- Self-hosted Grafana stack: дешевле в 5-10 раз, но нужны люди.
Стратегии экономии:
- Aggressive tail sampling.
- Drop low-value metrics (тегированные по user_id — катастрофа).
- Short retention для debug data (logs — 7 дней, traces — 3 дня).
- Long retention только для metrics (1 год для capacity planning).
- Cold storage для archive.
2.12 Performance overhead
Заголовок раздела «2.12 Performance overhead»OTel overhead обычно < 5% CPU при нормальной инструментации:
- BatchSpanProcessor (не SimpleSpanProcessor!).
- Async export.
- Reasonable batch size (512-1024).
Что НЕ делать:
SimpleSpanProcessorв production (blocking export).- Слишком много attributes (cardinality + сериализация).
- Trace на каждый log call (раздувает).
3. Gotchas
Заголовок раздела «3. Gotchas»3.1 ⚠️ High cardinality в метриках
Заголовок раздела «3.1 ⚠️ High cardinality в метриках»Проблема: добавил user_id как label → 1M активных users × 100 метрик = 100M time series → Prometheus OOM.
Правило: cardinality для одной метрики не должна превышать 10k-100k.
Что НЕ должно быть в labels:
- user_id, request_id, trace_id, session_id.
- URL paths с динамическими ID (
/users/123/orders/456).
Что МОЖНО:
- HTTP method, endpoint pattern (
/users/:id/orders/:id), status code class (2xx/4xx/5xx). - Service version, environment, region.
3.2 ⚠️ SimpleSpanProcessor vs BatchSpanProcessor
Заголовок раздела «3.2 ⚠️ SimpleSpanProcessor vs BatchSpanProcessor»SimpleSpanProcessor экспортирует span синхронно при End() → блокирует request thread.
Всегда используй BatchSpanProcessor в production.
3.3 ⚠️ Context propagation в worker pools
Заголовок раздела «3.3 ⚠️ Context propagation в worker pools»// ПЛОХО: контекст пропадает в горутинеgo func() { process(item) // нет ctx}()
// ХОРОШО: пробрасываем ctxgo func(ctx context.Context) { process(ctx, item)}(ctx)Для async задач из Kafka/queue: восстанавливай trace context из message headers через propagation.Extract.
3.4 ⚠️ Trace context потеря через HTTP клиенты
Заголовок раздела «3.4 ⚠️ Trace context потеря через HTTP клиенты»Если используешь http.Client{} без otelhttp.NewTransport, trace_id не пропагируется → traces “ломаются” между сервисами.
3.5 ⚠️ Tail sampling требует sticky routing
Заголовок раздела «3.5 ⚠️ Tail sampling требует sticky routing»Если у тебя несколько Collector replicas, все spans одного trace должны попадать в одну реплику для tail sampling. Решение: loadbalancing exporter перед tail_sampling Collector’ом.
exporters: loadbalancing: routing_key: traceID resolver: dns: hostname: tail-sampling-collector protocol: otlp: { tls: { insecure: true } }3.6 ⚠️ Span limits
Заголовок раздела «3.6 ⚠️ Span limits»OTel SDK имеет лимиты:
- 128 attributes per span.
- 128 events per span.
- 128 links per span.
При превышении — отбрасывается. Если bulk-обработка → не пиши event на каждую запись.
3.7 ⚠️ Slog handler и stdlib log
Заголовок раздела «3.7 ⚠️ Slog handler и stdlib log»Если в коде есть legacy log.Printf(...), OTel slog handler их не поймает. Нужно глобально переопределить:
slog.SetDefault(logger)log.SetOutput(slog.NewLogLogger(logger.Handler(), slog.LevelInfo).Writer())3.8 ⚠️ Метрики с histogram + exemplars overhead
Заголовок раздела «3.8 ⚠️ Метрики с histogram + exemplars overhead»Exemplars увеличивают размер метрик ~20%. На очень высоких QPS (>50k) это заметно. Альтернатива — sample exemplars (записывать только 1 из N).
3.9 ⚠️ Collector OOM при tail_sampling
Заголовок раздела «3.9 ⚠️ Collector OOM при tail_sampling»Tail sampling буферизует все spans трейса (default 30 сек). Если трафик 50k spans/sec и avg trace 50 spans, нужно ~75M spans в памяти. Конфигурируй num_traces и decision_wait аккуратно.
3.10 ⚠️ Не путать service.name и hostname
Заголовок раздела «3.10 ⚠️ Не путать service.name и hostname»service.name — это логическое имя приложения (order-service), а не hostname. Hostname в host.name. Иначе дашборды разваливаются при autoscaling.
3.11 ⚠️ Sensitive data в attributes
Заголовок раздела «3.11 ⚠️ Sensitive data в attributes»PII (email, phone, password) НЕ должны попадать в traces/logs. OTel Collector умеет redact’ить через transform processor:
processors: transform: trace_statements: - context: span statements: - delete_key(attributes, "user.email") - replace_pattern(attributes["http.url"], "token=[^&]+", "token=REDACTED")3.12 ⚠️ Sampling и SLO
Заголовок раздела «3.12 ⚠️ Sampling и SLO»Если sample 1% — то и метрики error_rate из traces неточные. Используй raw metrics (Counter в SDK) для SLO, traces только для root cause.
4. Real cases
Заголовок раздела «4. Real cases»4.1 Datadog → Grafana migration (типичная история)
Заголовок раздела «4.1 Datadog → Grafana migration (типичная история)»Компания X росла, Datadog bill достиг $500k/month. Миграция:
- Сначала replace logs: Fluent Bit → Loki. Экономия $200k.
- Затем metrics: Prometheus Operator → Mimir. Экономия $150k.
- Traces: OTel SDK + Tempo. Экономия $100k.
- Профайлинг: pprof + Pyroscope. Экономия $30k.
Итог: $500k → $50k self-hosted (infra). Команда 2 человека support.
4.2 Cloudflare observability
Заголовок раздела «4.2 Cloudflare observability»Cloudflare обрабатывает миллионы RPS, и их observability stack:
- ClickHouse как backend для traces/logs/metrics.
- Sampling: 0.01% обычных, 100% errors.
- Custom OTel Collector с heavy filtering.
- Hot path с минимальной instrumentation.
4.3 Uber & automaxprocs
Заголовок раздела «4.3 Uber & automaxprocs»Uber открыто публикуют свои observability практики:
- M3 — собственный TSDB (open-source).
- Jaeger (создан в Uber).
- automaxprocs (для CPU limits в k8s).
Их stack обрабатывает > 100M метрик/sec.
4.4 Авито (РФ)
Заголовок раздела «4.4 Авито (РФ)»Авито использует:
- VictoriaMetrics для metrics.
- ClickHouse для logs.
- Tempo для traces.
- OTel SDK во всех Go-сервисах.
Их CTO рассказывал на конференциях про переход с Prometheus → VictoriaMetrics из-за storage costs.
4.5 Тинькофф / T-Bank
Заголовок раздела «4.5 Тинькофф / T-Bank»- ClickHouse-based observability.
- Свой fork Pyroscope.
- В Go-сервисах OTel SDK.
4.6 Корпоративный bench: 5-7% overhead
Заголовок раздела «4.6 Корпоративный bench: 5-7% overhead»В реальных Go-сервисах (Авито, Cloudflare, корпоративные тесты) typical OTel overhead:
- CPU: +3-5%.
- Memory: +50-100 MB на типичный pod.
- Latency: +0.2-1ms на span (в основном sampling decision + serialization).
При SimpleSpanProcessor можно увидеть +50ms на запрос — это ловушка.
5. Вопросы для собеседования
Заголовок раздела «5. Вопросы для собеседования»Q1: Что такое три столпа observability и в чём их различия? A: Metrics — агрегированные числа (счётчики, histograms), дёшевы, для алертов/SLO. Logs — детальные события, дороги. Traces — цепочки межсервисных вызовов, помогают понять request lifecycle. Плюс continuous profiling и eBPF events в современных стеках.
Q2: Что такое OpenTelemetry и зачем он нужен? A: CNCF-стандарт для observability. Включает спецификацию, SDK для языков, Collector и OTLP протокол. Vendor-neutral — можно менять backend без переписывания кода.
Q3: Чем отличаются head-based и tail-based sampling? A: Head — решение в начале трейса на корневом сервисе (быстро, дёшево, но можно пропустить редкие ошибки). Tail — решение в Collector’е, видя все spans (гарантия ловить ошибки, но дороже по памяти и задержке).
Q4: Что такое exemplars и зачем нужны? A: Это записи в метрике, содержащие trace_id примера. Позволяют из дашборда (например, spike в histogram latency) “прыгнуть” в конкретный trace, вызвавший этот spike.
Q5: Что такое semantic conventions в OTel? A: Стандартные имена атрибутов: service.name, http.request.method, db.system. Использование критично для cross-service дашбордов и алертов.
Q6: Как работает W3C TraceContext?
A: HTTP headers traceparent (содержит trace_id, parent span_id, flags) и tracestate (vendor-specific). Стандарт propagation для interoperability между tracing системами.
Q7: Зачем нужен OTel Collector если SDK может отправлять напрямую в backend? A: Collector предоставляет: централизованную обработку (sampling, фильтрация, обогащение), независимость от backend (поменял exporter — не трогая SDK), retry и буферизацию, агрегацию, и снижение нагрузки на бэкенд.
Q8: Расскажи про pipeline в Collector’е. A: Три этапа: receivers (принимают telemetry) → processors (обрабатывают: batch, memory_limiter, tail_sampling) → exporters (отправляют в backend). Поддерживает множество источников и целей.
Q9: Что произойдёт, если в метрику добавить label с trace_id? A: Cardinality explosion. Каждый уникальный trace_id создаёт новый time series. На миллионе trace’ов получим миллион series → Prometheus OOM. Категорически нельзя.
Q10: SimpleSpanProcessor vs BatchSpanProcessor? A: Simple отправляет span синхронно при End() — блокирует request, в production категорически нельзя. Batch буферизует и шлёт пачками асинхронно — стандарт для prod.
Q11: Что такое Tempo и чем отличается от Jaeger? A: Tempo — backend для traces от Grafana, использует object storage (S3) для удешевления. Изначально не имел индекса по атрибутам (только trace_id), сейчас TraceQL позволяет поиск. Jaeger использует Cassandra/Elasticsearch — дороже, но богаче в поиске.
Q12: Чем Loki отличается от Elasticsearch? A: Loki индексирует только метки (labels), не текст логов. Логи хранятся в object storage. В 5-10 раз дешевле ES при сопоставимой ценности. Минус — нельзя делать full-text поиск по миллиардам строк, но обычно фильтруют по labels и потом grep’ают в окне.
Q13: Что такое Mimir? A: Distributed Prometheus, форк Cortex. Горизонтально масштабируемое long-term storage с tenant isolation. Совместим с Prometheus API.
Q14: Что такое Pyroscope? A: Continuous profiling. Постоянно собирает pprof профили (CPU, heap, goroutine, mutex) и хранит их с labels и временем. Можно делать flame graphs за любой промежуток и сравнивать.
Q15: Как работает tail sampling в Collector? A: Collector буферизует все spans трейса. После decision_wait (30 сек по умолчанию) применяет policies: keep errors, keep slow (latency > X), keep random N%. Требует sticky routing если несколько replicas.
Q16: Что делать с context propagation в Kafka consumer? A: Producer сохраняет trace context в message headers (otelkafka делает автоматически). Consumer извлекает через propagation.Extract и стартует child span. Так trace продолжается через async границу.
Q17: Что такое Alloy? A: Grafana Alloy — universal telemetry collector, замена Grafana Agent. Поддерживает OTel pipeline + Prometheus scrape + Loki + другие. Лёгкий, декларативный config.
Q18: Как контролировать стоимость observability? A: Aggressive tail sampling (только errors и slow), drop low-value metrics, short retention для logs/traces (3-7 дней), long retention только для metrics, фильтрация в Collector до отправки в backend, self-hosted Grafana stack vs vendor.
Q19: Что такое VictoriaMetrics и почему популярен в РФ? A: Альтернатива Prometheus, написана в Москве. В 5-10 раз меньше disk usage, быстрее в чтении, PromQL-совместимый, cluster version для HA. В РФ ещё и независимость от санкций важна.
Q20: Какой overhead у OTel в production? A: При правильной настройке (BatchSpanProcessor, разумные attributes) — 3-5% CPU, 50-100MB памяти, +0.2-1ms latency. SimpleSpanProcessor и неразумная инструментация могут добавить десятки мс.
Q21: Что такое Baggage и чем отличается от обычных attributes? A: Baggage — key-value контекст, пробрасываемый между всеми сервисами автоматически через HTTP headers. Используется для cross-cutting concerns (tenant_id, feature flag). Attributes — это локальные данные span, не пропагируются.
Q22: Как объединить trace, log и metric в одном дашборде? A: 1) Использовать единый trace_id в логах (slog с OTel handler автоматически добавляет). 2) Exemplars в метриках (trace_id внутри метрики). 3) Grafana correlations: клик на exemplar → trace, клик на span → logs за то же окно.
Q23: Как работает span links? A: Link — это ссылка на другой span (не parent). Используется для async/batch обработки: один consumer обрабатывает batch из 100 сообщений из разных trace’ов, создаёт один span с 100 links на родительские.
Q24: Что такое ClickHouse-based observability (Uptrace, SigNoz)? A: Один backend (ClickHouse) для traces + logs + metrics. SQL для запросов, отличное сжатие, дешевле фрагментированного стека. В РФ популярно из-за независимости от санкций.
Q25: Если ты увидел spike в P99 latency, какой workflow найти причину? A: 1) Открыть метрику P99 в Grafana. 2) Найти exemplar в spike — кликнуть → перейти в trace. 3) Посмотреть, какой span доминирует. 4) Открыть логи этого span за окно — найти exception. 5) Если нужно — Pyroscope flame graph за окно — увидеть, что CPU делал.
6. Practice
Заголовок раздела «6. Practice»-
Запусти локальный stack (docker-compose): OTel Collector, Tempo, Loki, Prometheus, Grafana. Инструментируй простое Go API. Сделай 1000 запросов, открой в Grafana traces и метрики.
-
Tail sampling lab: настрой Collector с tail_sampling, который оставляет 100% errors, 100% slow > 500ms, 5% random. Запусти load test (vegeta, k6). Проверь, что в Tempo все ошибки сохранились.
-
High-cardinality drill: создай метрику с user_id как label на 100k уникальных users. Поломай Prometheus. Затем перепиши на histogram без user_id, добавь exemplar.
-
Slog → OTel bridge: интегрируй otelslog handler. Сделай запрос, увидь, что лог автоматически содержит trace_id и можно прыгнуть в trace из Loki.
-
Multi-window burn rate: реализуй Prometheus rule для burn rate alert (14x за 1h И 6x за 6h). Запусти chaos test — pod inject errors, проверь, что алерт сработал.
-
Profile flame graph: запусти Pyroscope, сделай нагрузку с CPU bottleneck (sleep tight loop), увидь flame graph. Сравни два периода.
-
Custom OTTL transform: напиши Collector processor, который redact’ит email в URL query parameter.
-
VictoriaMetrics swap: возьми существующий Prometheus setup, переключи на VictoriaMetrics, сравни диск и query speed.
7. Источники
Заголовок раздела «7. Источники»- OpenTelemetry Documentation — официальный стандарт.
- OpenTelemetry Go SDK — исходники и примеры.
- Grafana Tempo docs — backend для traces.
- Grafana Loki docs — backend для logs.
- Prometheus docs — metrics стандарт.
- “Distributed Tracing in Practice” — Austin Parker et al., O’Reilly.
- “Observability Engineering” — Charity Majors.
- W3C Trace Context spec.
- VictoriaMetrics docs — РФ-альтернатива.
- Uptrace docs — ClickHouse-based platform.