Skip to main content
Back to course
Распределенная трассировка: от основ до production
15 / 1883%

Performance Tuning: минимизация overhead

50 минут

Performance Tuning: минимизация overhead

Проблема: overhead от трассировки

Реальный кейс:

Service: 10,000 req/sec
Без трассировки:
├─ CPU: 60%
├─ Memory: 512MB
└─ P95 latency: 50ms
 
С трассировкой (плохая конфигурация):
├─ CPU: 85% (+42%)
├─ Memory: 1.2GB (+135%)
└─ P95 latency: 65ms (+30%)

Вопрос: Как снизить overhead до приемлемого уровня?

Цель: 2-5% CPU overhead, <1ms latency impact.


Измерение overhead

Метрики для мониторинга

1. CPU overhead:

# До включения трассировки
top -p $(pgrep -f node)  # CPU: 60%
 
# После включения
top -p $(pgrep -f node)  # CPU: 63% → overhead = 3% ✅

2. Memory footprint:

// Мониторинг memory в приложении
const used = process.memoryUsage();
console.log({
  heapUsed: Math.round(used.heapUsed / 1024 / 1024) + " MB",
  heapTotal: Math.round(used.heapTotal / 1024 / 1024) + " MB",
});

3. Latency impact:

P50 latency:
  До: 25ms
  После: 25ms (+0ms) ✅
 
P95 latency:
  До: 50ms
  После: 51ms (+1ms) ✅
 
P99 latency:
  До: 100ms
  После: 103ms (+3ms) ⚠️ Приемлемо

Батчинг spans (критически важно!)

Проблема: Синхронная отправка каждого span блокирует thread.

// ❌ Плохо: каждый span → HTTP request
span.end();
HTTP POST to Jaeger (5ms network latency)

Решение: BatchSpanProcessor — отправка пакетами.

const { BatchSpanProcessor } = require("@opentelemetry/sdk-trace-base");
const {
  OTLPTraceExporter,
} = require("@opentelemetry/exporter-trace-otlp-http");
 
const exporter = new OTLPTraceExporter({
  url: "http://otel-collector:4318/v1/traces",
});
 
const batchProcessor = new BatchSpanProcessor(exporter, {
  maxQueueSize: 2048,
  maxExportBatchSize: 512,
  scheduledDelayMillis: 5000,
  exportTimeoutMillis: 30000,
});
 
provider.addSpanProcessor(batchProcessor);

Результат:

До: 1000 spans → 1000 HTTP requests (5s network time)
После: 1000 spans → 2 HTTP requests по 500 spans (10ms)

Экономия: 99.8% сокращение network overhead!


Оптимальная production-конфигурация

const { NodeSDK } = require("@opentelemetry/sdk-node");
const {
  getNodeAutoInstrumentations,
} = require("@opentelemetry/auto-instrumentations-node");
const {
  OTLPTraceExporter,
} = require("@opentelemetry/exporter-trace-otlp-http");
const {
  BatchSpanProcessor,
  ParentBasedSampler,
  TraceIdRatioBasedSampler,
} = require("@opentelemetry/sdk-trace-base");
const { Resource } = require("@opentelemetry/resources");
const {
  SemanticResourceAttributes,
} = require("@opentelemetry/semantic-conventions");
 
const resource = new Resource({
  [SemanticResourceAttributes.SERVICE_NAME]: "api-gateway",
  [SemanticResourceAttributes.SERVICE_VERSION]: "1.0.0",
});
 
const exporter = new OTLPTraceExporter({
  url: "http://otel-collector:4318/v1/traces",
  headers: {
    "Content-Encoding": "gzip",
  },
});
 
const batchProcessor = new BatchSpanProcessor(exporter, {
  maxQueueSize: 2048,
  maxExportBatchSize: 512,
  scheduledDelayMillis: 5000,
  exportTimeoutMillis: 30000,
});
 
const sdk = new NodeSDK({
  resource: resource,
  spanProcessor: batchProcessor,
  sampler: new ParentBasedSampler({
    root: new TraceIdRatioBasedSampler(0.01),
  }),
  instrumentations: [
    getNodeAutoInstrumentations({
      "@opentelemetry/instrumentation-http": {
        ignoreIncomingPaths: ["/health", "/metrics"],
      },
      "@opentelemetry/instrumentation-fs": {
        enabled: false,
      },
    }),
  ],
  spanLimits: {
    attributeValueLengthLimit: 256,
    attributeCountLimit: 128,
  },
});
 
sdk.start();

Best Practices Summary

1. Всегда используйте BatchSpanProcessor

2. Начните с 1-5% sampling

3. Включите GZIP compression

4. Фильтруйте health checks

5. Мониторьте overhead

6. Используйте OTel Collector для offloading


Следующий урок

В следующем уроке мы изучим Security & Privacy — защита чувствительных данных в traces.

Теперь вы можете минимизировать overhead до 2-3% CPU!

Performance Tuning: минимизация overhead — Распределенная трассировка: от основ до production — Potapov.me