T-CREATOR

Convex 運用監視ダッシュボード構築:Datadog/Grafana 連携と SLO 設計

Convex 運用監視ダッシュボード構築:Datadog/Grafana 連携と SLO 設計

Convex はリアルタイムデータベースと API を提供する BaaS(Backend as a Service)として注目を集めています。しかし、本番環境で安定運用するには、適切な監視体制の構築が欠かせません。本記事では、Convex の運用監視ダッシュボードを Datadog や Grafana と連携させ、SLO(Service Level Objective)を設計する方法を詳しく解説していきます。

実際の運用現場で使える具体的な設定方法と、信頼性の高いサービスを維持するためのベストプラクティスをお伝えしますので、ぜひ最後までご覧ください。

背景

Convex の運用監視が重要な理由

Convex は WebSocket を使ったリアルタイム通信を基盤としており、従来の REST API とは異なる監視アプローチが必要です。リアルタイム性を活かしたアプリケーションでは、わずかな遅延やエラーがユーザー体験に直結するため、継続的な監視が不可欠になります。

また、Convex は BaaS として様々な処理を担当するため、データベースクエリ、関数実行、認証処理など、複数のレイヤーでの監視が求められます。これらの処理をまとめて可視化し、問題を早期発見できる体制を整えることが、サービスの信頼性向上につながるのです。

以下の図は、Convex アプリケーションにおける監視対象の全体像を示しています。

mermaidflowchart TB
  client["クライアント<br/>アプリケーション"]
  convex["Convex<br/>Backend"]
  monitoring["監視システム<br/>(Datadog/Grafana)"]

  subgraph "監視対象"
    queries["クエリ実行時間"]
    mutations["ミューテーション<br/>成功率"]
    connections["WebSocket<br/>接続数"]
    errors["エラー発生率"]
  end

  client -->|WebSocket| convex
  convex -->|メトリクス| queries
  convex -->|メトリクス| mutations
  convex -->|メトリクス| connections
  convex -->|メトリクス| errors
  queries --> monitoring
  mutations --> monitoring
  connections --> monitoring
  errors --> monitoring

この図からわかるように、クライアントからの WebSocket 接続を起点に、複数の監視ポイントが存在します。これらのメトリクスを適切に収集し、分析することで、サービスの健全性を保つことができます。

監視ツールの選択肢

運用監視ツールとして、Datadog と Grafana は異なる特徴を持っています。

#ツール特徴適したケース
1Datadogフルマネージドで統合監視に強いSaaS 全体の監視、アラート自動化
2Grafanaオープンソースでカスタマイズ性が高いコスト重視、柔軟なダッシュボード
3両方併用異なる強みを活かせる大規模運用、複数チーム体制

Datadog は APM(Application Performance Monitoring)機能が充実しており、ログ、メトリクス、トレースを一元管理できます。一方、Grafana は Prometheus などのデータソースと組み合わせることで、コストを抑えながら柔軟な可視化が可能です。

課題

Convex 固有の監視課題

Convex を運用する際、いくつかの特有の課題に直面します。

まず、リアルタイム性の担保が最大の課題です。WebSocket 接続が切断されると、リアルタイム更新が停止し、ユーザー体験が大きく損なわれます。接続状態を常時監視し、異常を即座に検知する必要があります。

次に、関数実行のパフォーマンス監視が重要です。Convex の関数は TypeScript で記述されますが、実行時間が長くなると、アプリケーション全体のレスポンスに影響します。どの関数がボトルネックになっているかを特定できる仕組みが必要でしょう。

さらに、エラー率の可視化も課題となります。Convex では、クエリエラー、ミューテーションエラー、認証エラーなど、様々なエラーが発生する可能性があります。これらを分類し、優先度をつけて対応する体制が求められます。

以下の図は、これらの課題がどのように関連しているかを示しています。

mermaidflowchart LR
  realtime["リアルタイム性の低下"]
  performance["関数実行の遅延"]
  errors["エラー発生"]
  user["ユーザー体験の悪化"]

  realtime -->|接続断| user
  performance -->|レスポンス遅延| user
  errors -->|機能停止| user

  realtime -.->|連鎖的影響| performance
  performance -.->|タイムアウト| errors

このように、各課題は独立しているように見えて、実は連鎖的に影響し合います。そのため、包括的な監視アプローチが不可欠なのです。

既存の監視ツールとの連携の難しさ

Convex は比較的新しいプラットフォームであり、既存の監視ツールとのネイティブ連携が限られています。Datadog や Grafana に直接メトリクスを送信する標準機能がないため、独自の連携方法を構築する必要があります。

また、Convex のログフォーマットが独自であるため、ログ解析にもひと工夫が必要です。標準的なログパーサーでは解釈できない情報が含まれており、カスタムパーサーの実装が求められるケースもあるでしょう。

解決策

監視アーキテクチャの設計

Convex の監視を効果的に行うには、以下のアーキテクチャを採用することをお勧めします。

まず、Convex の内部メトリクスを取得する仕組みを構築します。Convex Dashboard から提供される情報を API 経由で取得し、外部監視ツールに転送する橋渡し役を作ります。

次に、カスタムメトリクスの収集を行います。アプリケーション側から独自のメトリクスを Convex の関数経由で記録し、それを監視ツールに送信します。

最後に、ダッシュボードの統合を実現します。Datadog や Grafana で Convex 専用のダッシュボードを作成し、重要な指標を一目で把握できるようにします。

以下の図は、推奨する監視アーキテクチャの全体像です。

mermaidflowchart TB
  convex_app["Convex アプリケーション"]
  convex_api["Convex API"]
  collector["メトリクス収集<br/>サービス"]

  subgraph "監視基盤"
    datadog["Datadog"]
    prometheus["Prometheus"]
    grafana["Grafana"]
  end

  subgraph "アラート"
    slack["Slack 通知"]
    pagerduty["PagerDuty"]
  end

  convex_app -->|カスタムメトリクス| collector
  convex_api -->|システムメトリクス| collector
  collector -->|転送| datadog
  collector -->|転送| prometheus
  prometheus --> grafana
  datadog --> slack
  datadog --> pagerduty
  grafana --> slack

この構成により、Convex から得られる様々なメトリクスを一元的に収集し、適切な監視ツールに振り分けることができます。

Datadog 連携の実装

Datadog と Convex を連携させるには、メトリクス収集サービスを経由する方法が最も実用的です。

メトリクス収集サービスの構築

まず、Convex のメトリクスを収集する専用サービスを Node.js で実装します。

typescript// types/metrics.ts

export interface ConvexMetrics {
  functionName: string;
  executionTime: number;
  status: 'success' | 'error';
  timestamp: number;
  errorType?: string;
}

export interface DatadogConfig {
  apiKey: string;
  appKey: string;
  site: string;
}

メトリクスの型定義を行い、Convex 関数の実行情報と Datadog の設定情報を明確にします。

次に、Datadog にメトリクスを送信するクライアントを実装します。

typescript// lib/datadog-client.ts

import { v2 } from '@datadog/datadog-api-client';
import type {
  ConvexMetrics,
  DatadogConfig,
} from '../types/metrics';

export class DatadogClient {
  private metricsApi: v2.MetricsApi;

  constructor(config: DatadogConfig) {
    const configuration = v2.createConfiguration({
      authMethods: {
        apiKeyAuth: config.apiKey,
        appKeyAuth: config.appKey,
      },
    });

    // Datadog のサイト設定(例: datadoghq.com)
    configuration.setServerVariables({
      site: config.site,
    });

    this.metricsApi = new v2.MetricsApi(configuration);
  }

  // メトリクスを送信するメソッド
  async sendMetrics(
    metrics: ConvexMetrics[]
  ): Promise<void> {
    // 次のコードブロックで実装
  }
}

Datadog の公式クライアントライブラリを使用し、認証情報を設定します。この構成により、安全にメトリクスを送信できます。

メトリクス送信の実装

実際にメトリクスを Datadog に送信する処理を実装します。

typescript// lib/datadog-client.ts(続き)

async sendMetrics(metrics: ConvexMetrics[]): Promise<void> {
  try {
    const series = metrics.map((metric) => ({
      metric: `convex.function.${metric.functionName}`,
      type: 1, // gauge type
      points: [
        {
          timestamp: Math.floor(metric.timestamp / 1000),
          value: metric.executionTime,
        },
      ],
      tags: [
        `status:${metric.status}`,
        metric.errorType ? `error_type:${metric.errorType}` : '',
      ].filter(Boolean),
    }));

    const params: v2.MetricsApiSubmitMetricsRequest = {
      body: { series },
    };

    await this.metricsApi.submitMetrics(params);
    console.log(`Successfully sent ${metrics.length} metrics to Datadog`);
  } catch (error) {
    console.error('Failed to send metrics to Datadog:', error);
    throw error;
  }
}

メトリクスを Datadog のフォーマットに変換し、バッチで送信します。エラーハンドリングも適切に行い、送信失敗時にログを出力します。

Convex 関数からのメトリクス記録

Convex の関数内でメトリクスを記録する仕組みを実装します。

typescript// convex/lib/metrics.ts

import { v } from 'convex/values';
import { mutation } from '../_generated/server';

export const recordMetric = mutation({
  args: {
    functionName: v.string(),
    executionTime: v.number(),
    status: v.union(
      v.literal('success'),
      v.literal('error')
    ),
    errorType: v.optional(v.string()),
  },
  handler: async (ctx, args) => {
    // メトリクスをデータベースに保存
    await ctx.db.insert('metrics', {
      functionName: args.functionName,
      executionTime: args.executionTime,
      status: args.status,
      errorType: args.errorType,
      timestamp: Date.now(),
    });
  },
});

この mutation を使用することで、任意の Convex 関数からメトリクスを記録できます。

メトリクス収集ワーカーの実装

定期的にメトリクスを収集し、Datadog に送信するワーカーを実装します。

typescript// workers/metrics-collector.ts

import { ConvexHttpClient } from 'convex/browser';
import { DatadogClient } from '../lib/datadog-client';
import { api } from '../convex/_generated/api';

const convexClient = new ConvexHttpClient(
  process.env.CONVEX_URL!
);
const datadogClient = new DatadogClient({
  apiKey: process.env.DATADOG_API_KEY!,
  appKey: process.env.DATADOG_APP_KEY!,
  site: process.env.DATADOG_SITE || 'datadoghq.com',
});

async function collectAndSendMetrics() {
  try {
    // Convex から最新のメトリクスを取得
    const metrics = await convexClient.query(
      api.metrics.getRecent,
      {
        limit: 100,
      }
    );

    if (metrics.length > 0) {
      await datadogClient.sendMetrics(metrics);
      console.log(
        `Collected and sent ${metrics.length} metrics`
      );
    }
  } catch (error) {
    console.error('Error in metrics collection:', error);
  }
}

// 1分ごとに実行
setInterval(collectAndSendMetrics, 60000);

この処理により、Convex に蓄積されたメトリクスを定期的に Datadog に転送できます。

Grafana 連携の実装

Grafana との連携では、Prometheus をデータソースとして使用する方法が一般的です。

Prometheus Exporter の実装

Convex のメトリクスを Prometheus 形式で公開する Exporter を作成します。

typescript// exporters/prometheus-exporter.ts

import express from 'express';
import { register, Counter, Histogram } from 'prom-client';
import { ConvexHttpClient } from 'convex/browser';
import { api } from '../convex/_generated/api';

const app = express();
const convexClient = new ConvexHttpClient(
  process.env.CONVEX_URL!
);

// メトリクスの定義
const functionExecutionTime = new Histogram({
  name: 'convex_function_execution_seconds',
  help: 'Execution time of Convex functions in seconds',
  labelNames: ['function_name', 'status'],
  buckets: [0.1, 0.5, 1, 2, 5],
});

const functionExecutionCount = new Counter({
  name: 'convex_function_execution_total',
  help: 'Total number of Convex function executions',
  labelNames: ['function_name', 'status'],
});

Prometheus のクライアントライブラリを使用し、Convex 関数の実行時間と実行回数を測定するメトリクスを定義します。

メトリクス更新処理

定期的に Convex からメトリクスを取得し、Prometheus のメトリクスを更新します。

typescript// exporters/prometheus-exporter.ts(続き)

async function updateMetrics() {
  try {
    const metrics = await convexClient.query(
      api.metrics.getRecent,
      {
        limit: 1000,
      }
    );

    metrics.forEach((metric) => {
      // 実行時間をヒストグラムに記録
      functionExecutionTime
        .labels(metric.functionName, metric.status)
        .observe(metric.executionTime / 1000); // ミリ秒から秒に変換

      // 実行回数をカウンター に記録
      functionExecutionCount
        .labels(metric.functionName, metric.status)
        .inc();
    });

    console.log(
      `Updated metrics for ${metrics.length} executions`
    );
  } catch (error) {
    console.error('Failed to update metrics:', error);
  }
}

// 30秒ごとにメトリクスを更新
setInterval(updateMetrics, 30000);

この処理により、Prometheus が Exporter からメトリクスをスクレイプする際に、最新の情報を提供できます。

Exporter エンドポイントの公開

Prometheus がスクレイプするエンドポイントを公開します。

typescript// exporters/prometheus-exporter.ts(続き)

app.get('/metrics', async (req, res) => {
  try {
    res.set('Content-Type', register.contentType);
    res.end(await register.metrics());
  } catch (error) {
    res.status(500).end(error);
  }
});

const PORT = process.env.EXPORTER_PORT || 9090;
app.listen(PORT, () => {
  console.log(
    `Prometheus exporter listening on port ${PORT}`
  );
});

このエンドポイントを通じて、Prometheus は定期的にメトリクスを収集できます。

Prometheus 設定

Prometheus の設定ファイルに、Convex Exporter をスクレイプ対象として追加します。

yaml# prometheus.yml

global:
  scrape_interval: 30s
  evaluation_interval: 30s

scrape_configs:
  - job_name: 'convex-metrics'
    static_configs:
      - targets: ['localhost:9090']
    metric_relabel_configs:
      - source_labels: [__name__]
        regex: 'convex_.*'
        action: keep

この設定により、Prometheus は 30 秒ごとに Convex のメトリクスを収集します。

Grafana ダッシュボードの作成

Grafana でダッシュボードを作成するための JSON 定義を用意します。

json{
  "dashboard": {
    "title": "Convex Monitoring Dashboard",
    "panels": [
      {
        "id": 1,
        "title": "Function Execution Time (p95)",
        "type": "graph",
        "targets": [
          {
            "expr": "histogram_quantile(0.95, rate(convex_function_execution_seconds_bucket[5m]))",
            "legendFormat": "{{function_name}}"
          }
        ]
      },
      {
        "id": 2,
        "title": "Function Execution Rate",
        "type": "graph",
        "targets": [
          {
            "expr": "rate(convex_function_execution_total[5m])",
            "legendFormat": "{{function_name}} - {{status}}"
          }
        ]
      }
    ]
  }
}

このダッシュボード定義を Grafana にインポートすることで、すぐに使える監視画面が構築できます。

SLO 設計の実装

SLO(Service Level Objective)を設計し、サービスの信頼性目標を明確にします。

SLO の定義

Convex アプリケーションにおける重要な SLO を定義します。

#SLO 項目目標値測定方法
1可用性99.9%成功したリクエスト数 / 全リクエスト数
2レイテンシ(p95)500ms 以下関数実行時間の 95 パーセンタイル
3エラー率0.1% 以下エラー数 / 全リクエスト数
4WebSocket 接続安定性99.5%接続維持時間 / 総接続時間

これらの SLO を基準に、サービスの健全性を評価します。

SLO 計算処理の実装

SLO を自動計算する処理を実装します。

typescript// lib/slo-calculator.ts

interface SLOMetrics {
  availability: number;
  latencyP95: number;
  errorRate: number;
  connectionStability: number;
}

export class SLOCalculator {
  // 可用性を計算
  calculateAvailability(
    successfulRequests: number,
    totalRequests: number
  ): number {
    if (totalRequests === 0) return 100;
    return (successfulRequests / totalRequests) * 100;
  }

  // p95レイテンシを計算
  calculateP95Latency(executionTimes: number[]): number {
    if (executionTimes.length === 0) return 0;

    const sorted = [...executionTimes].sort(
      (a, b) => a - b
    );
    const index = Math.floor(sorted.length * 0.95);
    return sorted[index];
  }

  // エラー率を計算
  calculateErrorRate(
    errors: number,
    totalRequests: number
  ): number {
    if (totalRequests === 0) return 0;
    return (errors / totalRequests) * 100;
  }
}

各 SLO 指標を計算するメソッドを実装し、数値として評価できるようにします。

SLO 監視の自動化

SLO の達成状況を定期的に確認し、違反時にアラートを発生させます。

typescript// workers/slo-monitor.ts

import { SLOCalculator } from '../lib/slo-calculator';
import { ConvexHttpClient } from 'convex/browser';
import { api } from '../convex/_generated/api';

const convexClient = new ConvexHttpClient(
  process.env.CONVEX_URL!
);
const sloCalculator = new SLOCalculator();

// SLOのしきい値
const SLO_THRESHOLDS = {
  availability: 99.9,
  latencyP95: 500,
  errorRate: 0.1,
  connectionStability: 99.5,
};

async function checkSLOs() {
  try {
    // 過去1時間のメトリクスを取得
    const metrics = await convexClient.query(
      api.metrics.getLastHour
    );

    const successfulRequests = metrics.filter(
      (m) => m.status === 'success'
    ).length;
    const totalRequests = metrics.length;
    const errors = metrics.filter(
      (m) => m.status === 'error'
    ).length;
    const executionTimes = metrics.map(
      (m) => m.executionTime
    );

    // SLOを計算
    const availability =
      sloCalculator.calculateAvailability(
        successfulRequests,
        totalRequests
      );
    const latencyP95 =
      sloCalculator.calculateP95Latency(executionTimes);
    const errorRate = sloCalculator.calculateErrorRate(
      errors,
      totalRequests
    );

    // しきい値チェック
    if (availability < SLO_THRESHOLDS.availability) {
      await sendAlert(
        'Availability SLO violation',
        availability
      );
    }
    if (latencyP95 > SLO_THRESHOLDS.latencyP95) {
      await sendAlert('Latency SLO violation', latencyP95);
    }
    if (errorRate > SLO_THRESHOLDS.errorRate) {
      await sendAlert(
        'Error rate SLO violation',
        errorRate
      );
    }

    console.log('SLO check completed:', {
      availability,
      latencyP95,
      errorRate,
    });
  } catch (error) {
    console.error('Failed to check SLOs:', error);
  }
}

// アラート送信処理
async function sendAlert(
  message: string,
  value: number
): Promise<void> {
  // Slack や PagerDuty にアラートを送信
  console.error(
    `ALERT: ${message} - Current value: ${value}`
  );
}

// 5分ごとにSLOをチェック
setInterval(checkSLOs, 300000);

この自動監視により、SLO 違反を即座に検知し、迅速な対応が可能になります。

具体例

実際のダッシュボード構築例

ここでは、実際に Datadog でダッシュボードを構築する手順を示します。

ダッシュボード定義の作成

Datadog のダッシュボード定義を Terraform で管理する例です。

hcl# terraform/datadog-dashboard.tf

resource "datadog_dashboard" "convex_monitoring" {
  title       = "Convex Monitoring Dashboard"
  description = "Convex アプリケーションの総合監視ダッシュボード"
  layout_type = "ordered"

  widget {
    timeseries_definition {
      title = "関数実行時間(p95)"
      request {
        q = "p95:convex.function.execution_time{*}"
        display_type = "line"
      }
      yaxis {
        label = "実行時間 (ms)"
        scale = "linear"
      }
    }
  }

  widget {
    query_value_definition {
      title = "現在のエラー率"
      request {
        q = "sum:convex.function.errors{*}.as_rate()"
        aggregator = "avg"
      }
      precision = 2
    }
  }
}

Infrastructure as Code により、ダッシュボードをバージョン管理し、再現可能な形で管理できます。

アラートルールの設定

SLO 違反時のアラートルールを定義します。

hcl# terraform/datadog-monitors.tf

resource "datadog_monitor" "high_error_rate" {
  name    = "Convex - High Error Rate"
  type    = "metric alert"
  message = <<-EOF
    Convex のエラー率がしきい値を超えています。

    現在のエラー率: {{value}}%
    しきい値: 0.1%

    @slack-convex-alerts
  EOF

  query = "avg(last_5m):( sum:convex.function.errors{*}.as_count() / sum:convex.function.total{*}.as_count() ) * 100 > 0.1"

  monitor_thresholds {
    critical = 0.1
    warning  = 0.05
  }

  notify_no_data    = false
  renotify_interval = 60

  tags = ["service:convex", "environment:production"]
}

このアラート設定により、エラー率が 0.1% を超えた場合に Slack へ通知が送信されます。

レイテンシ監視アラート

レイテンシが目標値を超えた場合のアラートも設定します。

hcl# terraform/datadog-monitors.tf(続き)

resource "datadog_monitor" "high_latency" {
  name    = "Convex - High Latency (p95)"
  type    = "metric alert"
  message = <<-EOF
    Convex の関数実行時間(p95)がしきい値を超えています。

    現在の実行時間: {{value}}ms
    しきい値: 500ms

    @slack-convex-alerts @pagerduty
  EOF

  query = "avg(last_10m):p95:convex.function.execution_time{*} > 500"

  monitor_thresholds {
    critical = 500
    warning  = 400
  }

  notify_no_data    = false
  renotify_interval = 30
  require_full_window = false

  tags = ["service:convex", "environment:production", "slo:latency"]
}

レイテンシの悪化は即座に対応が必要なため、PagerDuty にも通知を送信します。

Grafana ダッシュボードの具体例

Grafana で作成するダッシュボードの詳細な設定例を示します。

ダッシュボード全体の構成

Grafana ダッシュボードを JSON で定義します。

json{
  "dashboard": {
    "title": "Convex 運用監視ダッシュボード",
    "tags": ["convex", "monitoring"],
    "timezone": "Asia/Tokyo",
    "panels": [
      {
        "id": 1,
        "title": "可用性(直近24時間)",
        "type": "stat",
        "gridPos": { "h": 8, "w": 6, "x": 0, "y": 0 },
        "targets": [
          {
            "expr": "(sum(rate(convex_function_execution_total{status=\"success\"}[24h])) / sum(rate(convex_function_execution_total[24h]))) * 100",
            "legendFormat": "可用性"
          }
        ],
        "fieldConfig": {
          "defaults": {
            "unit": "percent",
            "thresholds": {
              "mode": "absolute",
              "steps": [
                { "value": 0, "color": "red" },
                { "value": 99, "color": "yellow" },
                { "value": 99.9, "color": "green" }
              ]
            }
          }
        }
      }
    ]
  }
}

この設定により、可用性を色分けして直感的に把握できるダッシュボードが作成されます。

関数別パフォーマンス監視パネル

各関数のパフォーマンスを個別に監視するパネルを追加します。

json{
  "id": 2,
  "title": "関数別実行時間(p95)",
  "type": "graph",
  "gridPos": { "h": 8, "w": 12, "x": 6, "y": 0 },
  "targets": [
    {
      "expr": "histogram_quantile(0.95, sum(rate(convex_function_execution_seconds_bucket[5m])) by (function_name, le))",
      "legendFormat": "{{function_name}}"
    }
  ],
  "fieldConfig": {
    "defaults": {
      "unit": "ms",
      "custom": {
        "drawStyle": "line",
        "lineInterpolation": "smooth",
        "fillOpacity": 10
      }
    }
  },
  "options": {
    "legend": {
      "displayMode": "table",
      "placement": "right",
      "calcs": ["lastNotNull", "max", "mean"]
    }
  }
}

このパネルでは、各関数の p95 レイテンシを時系列グラフで表示し、ボトルネックを特定しやすくします。

エラー率トレンドパネル

エラー率の推移を監視するパネルも重要です。

json{
  "id": 3,
  "title": "エラー率トレンド",
  "type": "graph",
  "gridPos": { "h": 8, "w": 6, "x": 18, "y": 0 },
  "targets": [
    {
      "expr": "(sum(rate(convex_function_execution_total{status=\"error\"}[5m])) / sum(rate(convex_function_execution_total[5m]))) * 100",
      "legendFormat": "エラー率"
    }
  ],
  "fieldConfig": {
    "defaults": {
      "unit": "percent",
      "custom": {
        "drawStyle": "line",
        "lineInterpolation": "smooth"
      }
    }
  },
  "alert": {
    "conditions": [
      {
        "evaluator": {
          "type": "gt",
          "params": [0.1]
        },
        "operator": {
          "type": "and"
        },
        "query": {
          "params": ["A", "5m", "now"]
        },
        "type": "query"
      }
    ],
    "executionErrorState": "alerting",
    "frequency": "1m",
    "handler": 1,
    "name": "エラー率アラート",
    "noDataState": "no_data",
    "notifications": []
  }
}

このパネルでは、エラー率が 0.1% を超えた場合にアラートが発生するよう設定されています。

トラブルシューティング事例

実際の運用で発生しやすい問題とその対処法を紹介します。

問題 1: メトリクス送信の遅延

エラーコード: Error 429: Too Many Requests

エラーメッセージ:

vbnetError: Failed to send metrics to Datadog
Status: 429
Message: API rate limit exceeded. Current rate: 150 req/s, Limit: 100 req/s

発生条件: メトリクス送信の頻度が高すぎる場合や、バッチサイズが小さすぎて API リクエスト数が増加した場合に発生します。

解決方法:

  1. バッチサイズを増やす
  2. 送信頻度を調整する
  3. バックオフ戦略を実装する
typescript// lib/datadog-client.ts(改善版)

export class DatadogClient {
  private queue: ConvexMetrics[] = [];
  private readonly BATCH_SIZE = 100;
  private readonly RETRY_DELAY = 5000;

  async sendMetrics(
    metrics: ConvexMetrics[]
  ): Promise<void> {
    this.queue.push(...metrics);

    // バッチサイズに達したら送信
    if (this.queue.length >= this.BATCH_SIZE) {
      await this.flush();
    }
  }

  private async flush(): Promise<void> {
    if (this.queue.length === 0) return;

    const batch = this.queue.splice(0, this.BATCH_SIZE);

    try {
      await this.sendBatch(batch);
    } catch (error: any) {
      if (error.statusCode === 429) {
        // レート制限エラーの場合、キューに戻して遅延
        this.queue.unshift(...batch);
        await new Promise((resolve) =>
          setTimeout(resolve, this.RETRY_DELAY)
        );
        await this.flush();
      } else {
        throw error;
      }
    }
  }
}

このバッチ処理とリトライ戦略により、API レート制限エラーを回避できます。

問題 2: Prometheus Exporter の停止

エラーコード: ECONNREFUSED

エラーメッセージ:

javascriptError: connect ECONNREFUSED 127.0.0.1:9090
    at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1148:16)

発生条件: Exporter プロセスがクラッシュした場合や、ネットワーク問題で接続できない場合に発生します。

解決方法:

  1. ヘルスチェックエンドポイントを追加
  2. プロセス監視を導入
  3. 自動再起動を設定
typescript// exporters/prometheus-exporter.ts(改善版)

import express from 'express';

const app = express();

// ヘルスチェックエンドポイント
app.get('/health', (req, res) => {
  res.status(200).json({
    status: 'healthy',
    uptime: process.uptime(),
    timestamp: Date.now(),
  });
});

// グレースフルシャットダウン
process.on('SIGTERM', () => {
  console.log(
    'SIGTERM received, closing server gracefully'
  );
  server.close(() => {
    console.log('Server closed');
    process.exit(0);
  });
});

const server = app.listen(PORT, () => {
  console.log(`Exporter running on port ${PORT}`);
});

プロセス管理ツール(PM2 や systemd)と組み合わせることで、安定した運用が可能になります。

問題 3: SLO 計算の不正確さ

エラーコード: なし(論理エラー)

発生条件: データの欠損やタイムスタンプのずれにより、SLO 計算が不正確になる場合があります。

解決方法:

  1. データ検証を追加
  2. 集計期間を明確にする
  3. 異常値を除外する
typescript// lib/slo-calculator.ts(改善版)

export class SLOCalculator {
  calculateP95Latency(executionTimes: number[]): number {
    // データ検証
    const validTimes = executionTimes.filter(
      (time) => time > 0 && time < 60000 // 60秒以上は異常値として除外
    );

    if (validTimes.length === 0) {
      console.warn(
        'No valid execution times for p95 calculation'
      );
      return 0;
    }

    // 最低10サンプル必要
    if (validTimes.length < 10) {
      console.warn(
        `Insufficient samples for accurate p95: ${validTimes.length}`
      );
    }

    const sorted = [...validTimes].sort((a, b) => a - b);
    const index = Math.floor(sorted.length * 0.95);

    return sorted[index];
  }
}

データ品質を向上させることで、より正確な SLO 評価が可能になります。

まとめ

本記事では、Convex の運用監視ダッシュボードを構築する方法について、Datadog と Grafana の連携、そして SLO 設計の観点から詳しく解説してきました。

Convex はリアルタイム性の高い BaaS であるため、従来の REST API とは異なる監視アプローチが必要です。メトリクス収集サービスを経由して Datadog や Prometheus にデータを送信し、Grafana で可視化する構成を採用することで、効果的な監視体制を構築できます。

SLO の設計では、可用性、レイテンシ、エラー率、接続安定性という 4 つの重要な指標を定義し、自動監視する仕組みを実装しました。これにより、サービスの信頼性を定量的に評価し、改善につなげることができるでしょう。

実際の運用では、API レート制限やプロセスの停止など、様々な問題が発生する可能性があります。本記事で紹介したトラブルシューティング事例を参考に、堅牢な監視システムを構築していただければと思います。

監視体制の整備は、サービスの成長とともに継続的に改善していくべき領域です。まずは基本的なメトリクスの収集から始め、運用の中で得られた知見をもとに、より高度な監視へと進化させていくことをお勧めします。

関連リンク