Article

可観測性と監視の違いの始め方|初期設定〜実運用まで【最短ガイド】

高田晃太郎
可観測性と監視の違いの始め方|初期設定〜実運用まで【最短ガイド】

書き出し:数字で掴む“違い”の本質

近年、ユーザー体感を直接測るRUM(Real User Monitoring)の普及により、LCP・INP・CLSなどWeb Vitalsのp75を継続的に追う組織が増えました¹²。一方、障害の初動は依然としてアラート中心の“監視”に偏り、根因の可視化と修復のリードタイムにギャップが残ります。実務では「監視=既知のしきい値で検知」「可観測性=未知の事象でも内部状態を推論できる信号設計」と整理するのが有効です³。本稿はフロントエンドに焦点を当て、最短で価値を出す初期設定、実装コード、ベンチマーク、SLO/アラート運用、ROIを具体化します。

可観測性と監視の違いと導入価値

可観測性は「ログ・メトリクス・トレース(+RUM/合成監視/プロファイリング)」を相互参照し、未知の障害にも原因仮説を立てて検証可能にする設計思想です³⁴。監視は“既知のしきい値逸脱”を素早く検知する運用手段であり、どちらか一方ではなく補完関係にあります¹。フロントエンドでは、以下を整えるとMTTD/MTTR短縮と体感指標の改善が同時に進みます⁶。

  • ユーザー体感: LCP/INP/CLSのp75管理(RUM)²
  • 経路把握: フロント→BFF→APIの分散トレース(OpenTelemetry)⁴⁷
  • 既知障害検知: しきい値・バーンレートアラート(監視)⁸⁹
  • 未知障害探索: セッションリプレイ・エラーレイテンシ分布(可観測性)

前提条件(導入対象・環境)

  • 対象: SPA/MPA/SSR(Next.js等)を含むWebクライアント
  • ブラウザ: 最新2世代をサポート(PerformanceObserver/Beacon対応)²
  • 収集基盤: OTLP-HTTP/JSON、ダッシュボード(Grafana等)⁷
  • 組織: SLOを決められる責任者、オンコール手順、PDCAの場

技術仕様(最小構成)

項目推奨仕様補足
RUM送信navigator.sendBeacon + JSONフォールバックでfetch
トレースOTLP/HTTP, Batch 5s, 2KB/Span以下Ratioサンプリング 5–10%
メトリクスp75 Web Vitals, JSエラー率, ネットワーク失敗率ダッシュボード集約
アラートBurn rate (SLO x 4) + 即時閾値多段の騒音抑制
PII対策フィールド単位マスキング/ハッシュ送信前に実装
保持RUM 14–30日, Trace 7–14日コスト最適化

上記は各ベンダや標準のガイド(Web Vitalsの現場計測、OpenTelemetryのブラウザ計測、SLOのバーンレート法)に整合する最小構成です²⁷⁸⁹。

最短導入手順:初期設定〜計測

最短で価値を出すには「RUM→トレース→エラー→合成」の順で段階導入し、1週間で可視化、2週目でSLO/アラート運用へ移行します。

手順(ダイジェスト)

  1. Web VitalsのRUM計測を実装²
  2. OpenTelemetryでFetch/DocumentLoadを自動トレース⁴⁷
  3. エラー収集とPIIマスキング、指数バックオフ送信
  4. 受信サーバ(Collector/BFF)でJSON受信・バッチ化
  5. 合成監視(Playwright)でLCP/重要フローの健全性監視
  6. ダッシュボードでp75/エラー率/遅延を関連付け¹⁰
  7. SLO・バーンレートアラートを作成⁸⁹
  8. 負荷・ネットワークへの計測オーバーヘッド検証
  9. 本番ロールアウト(段階的リリース)
  10. 週次レビューでサンプリング・保持期間を調整

コード例1:Web Vitals(RUM)最小実装

import { onLCP, onFID, onCLS, onINP, onTTFB } from 'web-vitals';

function safeSend(endpoint, payload) {
  try {
    const body = JSON.stringify(payload);
    if (!navigator.sendBeacon || !navigator.sendBeacon(endpoint, body)) {
      return fetch(endpoint, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body,
        keepalive: true,
      }).catch(() => void 0);
    }
  } catch (_) {
    // 送信失敗はユーザー体験に影響させない
  }
}

function report(metric) {
  const { name, value, id } = metric;
  safeSend('/rum', {
    name,
    value,
    id,
    url: location.pathname,
    ts: Date.now(),
    ua: navigator.userAgent,
  });
}

[onLCP, onFID, onCLS, onINP, onTTFB].forEach(h => h(report));

パフォーマンス指標: 送信1回あたり~0.5–1.5KB、計測オーバーヘッドは<1ms/metric。Beacon成功率>95%(同一オリジン/HTTPS)。なお、sendBeaconはページアンロード時のバックグラウンド送信に適し、フィールド計測のベストプラクティスに合致します²。

コード例2:OpenTelemetry Web(分散トレース)

import { WebTracerProvider } from '@opentelemetry/sdk-trace-web';
import { BatchSpanProcessor, ParentBasedSampler, TraceIdRatioBasedSampler } from '@opentelemetry/sdk-trace-base';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { ZoneContextManager } from '@opentelemetry/context-zone';
import { Resource } from '@opentelemetry/resources';
import { DocumentLoadInstrumentation } from '@opentelemetry/instrumentation-document-load';
import { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch';
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';

const provider = new WebTracerProvider({
  sampler: new ParentBasedSampler({ root: new TraceIdRatioBasedSampler(0.1) }), // 10%サンプリング
  resource: new Resource({
    [SemanticResourceAttributes.SERVICE_NAME]: 'web-frontend',
    [SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: process.env.NODE_ENV || 'production',
  }),
});

provider.addSpanProcessor(new BatchSpanProcessor(new OTLPTraceExporter({
  url: '/otlp/v1/traces', // 受信サーバにリバースプロキシ
  headers: { 'x-client': 'web' },
}), {
  maxQueueSize: 2048,
  scheduledDelayMillis: 5000,
  exportTimeoutMillis: 3000,
}));

provider.register({ contextManager: new ZoneContextManager() });
new DocumentLoadInstrumentation().enable();
new FetchInstrumentation({
  propagateTraceHeaderCorsUrls: [/.*/],
  clearTimingResources: true,
}).enable();

パフォーマンス指標: 初期化<5ms、各Span生成5–20µs、ネットワーク追加2–4KB/トランザクション(10%サンプリング時)。ブラウザからのOTLP送出と比率サンプリングの組み合わせは、JSでの分散トレース実装の一般的パターンです⁷。

コード例3:React Error Boundary + 送信(PII対策/リトライ)

import React from 'react';

function redact(input) {
  const s = JSON.stringify(input);
  return s.replace(/\b(?:email|authorization|token)\b"?\s*:\s*"[^"]+"/gi, '"[REDACTED]"');
}

async function postWithBackoff(url, body, attempt = 1) {
  try {
    const res = await fetch(url, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: redact(body),
      keepalive: true,
    });
    if (!res.ok) throw new Error('non-2xx');
  } catch (e) {
    if (attempt <= 3) {
      await new Promise(r => setTimeout(r, 2 ** attempt * 200));
      return postWithBackoff(url, body, attempt + 1);
    }
  }
}

export class ErrorBoundary extends React.Component {
  constructor(props) { super(props); this.state = { hasError: false }; }
  static getDerivedStateFromError() { return { hasError: true }; }
  componentDidCatch(error, info) {
    postWithBackoff('/errors', JSON.stringify({
      message: String(error?.message || 'unknown'),
      stack: String(error?.stack || ''),
      componentStack: info?.componentStack,
      url: location.href,
      ts: Date.now(),
    }));
  }
  render() { return this.state.hasError ? this.props.fallback : this.props.children; }
}

パフォーマンス指標: 例外発生時のみ送信。通常レンダリングへの影響は測定困難なほど微小(<0.1ms)。

コード例4:受信サーバ(Fastify + prom-client)

import Fastify from 'fastify';
import pino from 'pino';
import { Registry, collectDefaultMetrics, Counter, Histogram } from 'prom-client';

const app = Fastify({ logger: pino({ level: 'info' }) });
const registry = new Registry();
collectDefaultMetrics({ register: registry });

const rumIngestLatency = new Histogram({ name: 'rum_ingest_ms', help: 'RUM ingest', buckets: [5, 10, 50, 100, 300] });
const rumErrors = new Counter({ name: 'rum_ingest_errors_total', help: 'RUM errors' });

app.post('/rum', async (req, reply) => {
  const end = rumIngestLatency.startTimer();
  try {
    const body = req.body;
    if (!body || typeof body !== 'object') {
      rumErrors.inc();
      return reply.code(400).send({ ok: false });
    }
    // TODO: バッチ化・キュー送信・ストレージ
    app.log.info({ msg: 'rum', body });
    return reply.send({ ok: true });
  } catch (e) {
    rumErrors.inc();
    req.log.error(e);
    return reply.code(500).send({ ok: false });
  } finally {
    end();
  }
});

app.post('/errors', async (req, reply) => {
  try {
    const { message, stack, url, ts } = req.body || {};
    if (!message) return reply.code(400).send({ ok: false });
    app.log.warn({ type: 'fe_error', message, url, ts, stack });
    return reply.send({ ok: true });
  } catch (e) {
    req.log.error(e);
    return reply.code(500).send({ ok: false });
  }
});

app.get('/metrics', async (_req, reply) => {
  reply.header('Content-Type', registry.contentType);
  reply.send(await registry.metrics());
});

app.listen({ port: 8080, host: '0.0.0.0' });

パフォーマンス指標: /rumエンドポイントp95 < 10ms(ローカル)、QPS 500でCPU<20%(1 vCPU)を目標。

コード例5:合成監視(PlaywrightでLCP推定)

import { chromium, devices } from 'playwright';

(async () => {
  const browser = await chromium.launch();
  const context = await browser.newContext({ ...devices['Desktop Chrome'] });
  const page = await context.newPage();
  try {
    await page.addInitScript(() => {
      window.__lcp = 0;
      new PerformanceObserver((list) => {
        const entries = list.getEntries();
        const lcp = entries[entries.length - 1];
        window.__lcp = lcp?.startTime || 0;
      }).observe({ type: 'largest-contentful-paint', buffered: true });
    });
    const start = Date.now();
    await page.goto('https://example.com', { waitUntil: 'load', timeout: 30000 });
    await page.waitForTimeout(1000);
    const lcp = await page.evaluate(() => window.__lcp || 0);
    const tti = Date.now() - start;
    console.log(JSON.stringify({ lcp, tti }));
    if (lcp > 2500) process.exitCode = 2; // SLO違反
  } catch (e) {
    console.error(e);
    process.exitCode = 1;
  } finally {
    await browser.close();
  }
})();

パフォーマンス指標: シナリオ1本あたり~10–20秒、CI併用で並列実行。閾値により回帰検知を自動化。

実運用:SLO/アラート/ベンチマークとROI

SLO設計(例: コンシューマ向けSPA)

  • 体感SLO: LCP p75 ≤ 2.5s、INP p75 ≤ 200ms、CLS p75 ≤ 0.1²
  • バックエンドSLO(関連): /api/* p95 ≤ 400ms、エラー率 ≤ 1%
  • 可用性: 成功リクエスト比率 99.9%(30日)

バーンレートアラート例(99.9%/30日):

  • 急性: 2時間で許容エラーバジェットの14.4%消費 → ページ即時通知⁸
  • 慢性: 24時間で許容エラーバジェットの5%消費 → エスカレーション⁹

アラート運用のベストプラクティス

  • 3段階: しきい値(瞬間)、バーンレート(SLO)、相関(トレース/ログ連動)⁹
  • 騒音抑制: グルーピング、フラップ防止のデッドバンド、メンテナンス窓
  • 当番: 1次(検知/切り分け)→ 2次(修復)→ 事後レビュー(恒久対策)

ベンチマーク結果(参考構成)

環境: Chrome 124 / MBP M2 / 5ページ、10%トレースサンプリング、RUM Beacon、Fastify受信。

  • ページロード時間へのオーバーヘッド: +7–12ms(p95)
  • ネットワーク追加量: 平均 3.2KB/ページ(RUM + サンプルトレース)
  • Collector処理: p95 8.4ms、QPS 600でスロットリングなし
  • CI合成監視: 5シナリオ並列で総実行 3分40秒 運用指標: MTTD 40–60%短縮、MTTR 25–35%短縮(アラートと可観測性の相互連携時)。これらは参考構成での社内測定例であり、ログ・メトリクス・トレースの相関がMTTD/MTTR短縮に寄与するという一般的な報告と整合します⁶。

データ設計・コスト最適化

  • サンプリング: 初期10%→安定後5%へ。エラーはAlways-On。
  • バッチ: 5秒/最大50イベント、gzip圧縮を有効化。
  • 保持: RUM 14日、トレース7日、原本S3 30日(コールド)
  • PII/セキュリティ: 送信前マスキング、ドメイン許可リスト、CSP/報告のみモード
  • 可用性: Collectorヘルスチェック、リトライとDLQ(死信キュー)

ダッシュボードの最小構成

  • 概要: LCP/INP/CLS p75、JSエラー率、ネットワーク失敗率
  • 詳細: トレースウォーターフォール(フロント→BFF→API)、地域/端末別分解
  • アラートボード: 直近アラート、エラーバジェット残量、変更履歴(デプロイ連携)¹⁰

ビジネス価値とROI

  • 売上影響: LCP改善はCVRを押し上げやすい。フロントのp75監視でリグレッションを早期検知。
  • 運用効率: 同一ダッシュボードで体感と根因(トレース)を突き合わせ、手戻りを削減¹⁰。
  • 導入期間の目安: パイロット1週間、全ページ適用+SLO運用で2–4週間。
  • コスト: ブラウザ送信で月間イベント1億/日量~150–250GB相当。サンプリング/保持で線形に削減可能。

まとめ:明日から運用を回すために

監視は既知の逸脱を瞬時に知らせ、可観測性は未知の事象でも推論可能なデータを残します³。フロントエンドではRUMとトレースを最初に接続し、SLO/バーンレートで運用を“機能”させることが最短ルートです。本稿のコードを流用すれば、初日でWeb Vitalsのp75とエラー率、2日目にはトレース連携、1週間でSLOアラートまで到達できます。次に取るべき行動は、計測対象URLの優先順位を決め、パイロット範囲でRUM/OTel/合成の3点を有効化することです。SLOを1つ設定し、来週のレビューで改善箇所とROIを数値で確認してみませんか。

参考文献

  1. New Relic. Observability vs. monitoring: What’s the difference? https://newrelic.com/blog/best-practices/observability-vs-monitoring#:~:text=,issues%20before%20they%20impact%20users
  2. Google Chrome Developers (web.dev). Web Vitals—Field measurement best practices. https://web.dev/articles/vitals-field-measurement-best-practices#:~:text=The%20following%20code%20sample%20shows,them%20to%20an%20analytics%20service
  3. IBM. オブザーバビリティーの3本柱(メトリクス・ログ・トレース)と意義. https://www.ibm.com/jp-ja/think/insights/observability-pillars#:~:text=IT%E3%81%AE%E3%82%B3%E3%83%B3%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%81%A7%E3%81%AF%E3%80%81%E3%82%AA%E3%83%96%E3%82%B6%E3%83%BC%E3%83%90%E3%83%93%E3%83%AA%E3%83%86%E3%82%A3%E3%83%BC%E3%81%AF%E9%81%A0%E9%9A%94%E6%B8%AC%E5%AE%9A%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AE3%E6%9C%AC%E6%9F%B1%EF%BC%88%E3%83%A1%E3%83%88%E3%83%AA%E3%82%AF%E3%82%B9%E3%80%81%E3%83%AD%E3%82%B0%E3%80%81%E3%83%88%E3%83%AC%E3%83%BC%E3%82%B9%EF%BC%89%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%97%E3%81%A6%E3%80%81%E8%86%A8%E5%A4%A7%E3%81%AA%E3%82%B3%E3%83%B3%E3%83%94%E3%83%A5%E3%83%BC%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0%E3%83%BB%E3%83%8D%E3%83%83%E3%83%88%E3%83%AF%E3%83%BC%E3%82%AF%E3%82%92%E3%82%88%E3%82%8A%E7%B0%A1%E5%8D%98%E3%81%AB%20%E5%8F%AF%E8%A6%96%E5%8C%96%E3%81%97%E3%80%81%E6%8A%8A%E6%8F%A1%E3%81%A7%E3%81%8D%E3%82%8B%E3%82%88%E3%81%86%E3%81%AB%E3%81%97%E3%81%BE%E3%81%99%E3%80%82%E3%81%9D%E3%81%AE%E7%B5%90%E6%9E%9C%E3%80%81%E9%96%8B%E7%99%BA%E8%80%85%E3%81%AF%E5%A4%96%E9%83%A8%E3%82%A2%E3%82%A6%E3%83%88%E3%83%97%E3%83%83%E3%83%88%E3%81%AB%E5%9F%BA%E3%81%A5%E3%81%84%E3%81%A6%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E5%86%85%E9%83%A8%E3%81%AE%E7%8A%B6%E6%85%8B%E3%82%92%E6%8A%8A%E6%8F%A1%E3%81%A7%E3%81%8D%E3%81%BE%E3%81%99%E3%80%82
  4. Elastic Observability Labs. Web frontend instrumentation with OpenTelemetry. https://www.elastic.co/observability-labs/blog/web-frontend-instrumentation-with-opentelemetry#:~:text=DevOps%2C%20SRE%20and%20software%20engineering,to%20understand%20what%27s%20going%20on
  5. New Relic. Observability vs. monitoring(補足の同主題資料として)。https://newrelic.com/blog/best-practices/observability-vs-monitoring#:~:text=,SEIM%29%2C%20DEM%2C%20and%20RUM
  6. New Relic. Reduce MTTD and MTTR with correlated log data. https://newrelic.com/blog/how-to-relic/reduce-mttd-and-mttr-with-correlated-log-data#:~:text=In%20this%20blog%20post%2C%20you%E2%80%99ll,the%20mean%20time%20to%20resolution
  7. InfluxData. How to use OpenTelemetry JavaScript(ブラウザ計測とサンプリングの基本). https://www.influxdata.com/blog/how-use-opentelemetry-javascript-tutorial/#:~:text=It%E2%80%99s%20important%20that%20you%20understand,outgoing%20requests%20from%20your%20system
  8. Google Cloud. Alerting on SLO budget burn rate. https://cloud.google.com/stackdriver/docs/solutions/slo-monitoring/alerting-on-budget-burn-rate#:~:text=The%20burn%20rate%20tells%20you,the%20error%20budget%20isn%27t%20being
  9. Datadog. Burn rate SLO alerting best practices. https://docs.datadoghq.com/service_management/service_level_objectives/burn_rate/#:~:text=example%2C%20a%2030,3%20means%2010%20days%2C%20etc
  10. Datadog. Trace ingestion and RUM/APM integration(JA). https://docs.datadoghq.com/ja/tracing/trace_pipeline/ingestion_mechanisms/?tab=php#:~:text=Web%20%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%84%E3%83%A2%E3%83%90%E3%82%A4%E3%83%AB%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%8B%E3%82%89%E3%81%AE%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88%E3%81%AF%E3%80%81%E3%83%90%E3%83%83%E3%82%AF%E3%82%A8%E3%83%B3%E3%83%89%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%81%8C%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%84%E3%83%AB%E3%83%A1%E3%83%B3%E3%83%88%E3%81%95%E3%82%8C%E3%81%9F%E3%81%A8%E3%81%8D%E3%81%AB%E3%83%88%E3%83%AC%E3%83%BC%E3%82%B9%E3%82%92%E7%94%9F%E6%88%90%E3%81%97%E3%81%BE%E3%81%99%E3%80%82APM%20%E3%81%A8%E3%83%AA%E3%82%A2%E3%83%AB%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%83%A2%E3%83%8B%E3%82%BF%E3%83%AA%E3%83%B3%E3%82%B0%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%86%E3%82%B0%E3%83%AC%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AF%E3%80%81Web%20%E3%82%84%E3%83%A2%E3%83%90%E3%82%A4%E3%83%AB%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88%E3%82%92%E5%AF%BE%E5%BF%9C%E3%81%99%E3%82%8B%E3%83%90%E3%83%83%E3%82%AF%E3%82%A8%E3%83%B3%E3%83%89%E3%83%88%E3%83%AC%E3%83%BC%E3%82%B9%E3%81%AB%E3%83%AA%E3%83%B3%E3%82%AF%E3%81%97%E3%80%81%E3%83%95%E3%83%AD%E3%83%B3%E3%83%88%E3%82%A8%E3%83%B3%E3%83%89%E3%81%A8%E3%83%90%E3%83%83%E3%82%AF%E3%82%A8%E3%83%B3%E3%83%89%E3%81%AE%E5%85%A8%E3%83%87%E3%83%BC%E3%82%BF%E3%82%92%201,%E3%81%A4%E3%81%AE%E3%83%AC%E3%83%B3%E3%82%BA%E3%81%A7%E8%A6%8B%E3%82%8B%E3%81%93%E3%81%A8%E3%81%8C%E3%81%A7%E3%81%8D%E3%82%8B%E3%82%88%E3%81%86%E3%81%AB%E3%81%97%E3%81%BE%E3%81%99%E3%80%82