Real User Monitoring (RUM)

RUM (Real User Monitoring) — сбор и анализ метрик производительности от реальных пользователей в реальных условиях (разные устройства, браузеры, сети), в отличие от синтетических тестов в лабораторных условиях.

Зачем нужно

Lighthouse показывает производительность на одном устройстве в контролируемой среде. RUM отражает реальный опыт: пользователи из разных регионов, на медленных устройствах, с мобильным интернетом. CrUX данные Google основаны на RUM Chrome-браузера.

Где используется

  • Мониторинг Core Web Vitals на production для реальных пользователей
  • Сравнение производительности до и после деплоя оптимизаций
  • Обнаружение деградации в конкретных регионах или на конкретных устройствах
  • Data-driven принятие решений о приоритетах оптимизации

Основной контент

Самостоятельный сбор (web-vitals + endpoint)

// public/rum.js
import { onLCP, onINP, onCLS, onFCP, onTTFB } from 'web-vitals/attribution';

const sessionId = crypto.randomUUID;

function sendMetric(metric) {
  const body = JSON.stringify({
    name: metric.name,
    value: metric.value,
    rating: metric.rating, // 'good' | 'needs-improvement' | 'poor'
    delta: metric.delta,
    id: metric.id,
    attribution: metric.attribution,
    // Контекст
    sessionId,
    url: location.href,
    userAgent: navigator.userAgent,
    connectionType: navigator.connection?.effectiveType,
    deviceMemory: navigator.deviceMemory,
    timestamp: Date.now(),
  });

  // sendBeacon не блокирует unload и гарантирует отправку
  navigator.sendBeacon('/api/metrics', body);
}

// Регистрируем все метрики
[onLCP, onINP, onCLS, onFCP, onTTFB].forEach(fn => fn(sendMetric));

Endpoint для сбора метрик (Express)

app.post('/api/metrics', express.json({ limit: '10kb' }), async (req, res) => {
  const metric = req.body;

  // Валидация
  if (!['LCP', 'INP', 'CLS', 'FCP', 'TTFB'].includes(metric.name)) {
    return res.status(400).end();
  }

  // Сохранение в ClickHouse/InfluxDB/BigQuery
  await metricsDb.insert('web_vitals', {
    name: metric.name,
    value: metric.value,
    rating: metric.rating,
    url: new URL(metric.url).pathname,
    connection: metric.connectionType,
    timestamp: new Date,
  });

  res.status(204).end();
});

Готовые RUM-решения

Коммерческие:
  DataDog RUM      — полный observability + session replay
  New Relic Browser — трассировка + метрики
  Sentry Performance — интеграция с error tracking

Open Source / Бесплатные:
  Grafana Faro     — self-hosted RUM
  web-vitals.js    — сбор, Beacon API, обработка на вашем бэкенде
  Google Analytics 4 — Core Web Vitals в отчёте

Анализ данных — SQL запрос к RUM-данным

-- p75 LCP по устройству за последние 7 дней
SELECT
  DATE(timestamp) as date,
  connection_type,
  PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY value) as p75_lcp,
  COUNT(*) as sessions,
  COUNT(CASE WHEN rating = 'good' THEN 1 END) * 100.0 / COUNT(*) as good_pct
FROM web_vitals
WHERE name = 'LCP'
  AND timestamp > NOW - INTERVAL '7 days'
GROUP BY date, connection_type
ORDER BY date DESC;

Частые ошибки

  • Сэмплинг 100% пользователей с тяжёлым RUM-скриптом — влияние на производительность
  • Не фильтровать боты и crawlers — искажают метрики
  • Только средние значения вместо перцентилей (p75, p95) — выбросы скрываются
  • Хранение полных URL с query string — утечка PII (email в URL)

Связанные темы

Ресурсы