S
Solo Kit
DocumentationComponentsPricingChangelogRoadmapFAQContact
LoginGet Started
DocumentationComponentsPricing
LoginGet Started
Welcome to Solo Kit DocumentationIntroductionTech StackRoadmapFAQGetting Started
Database OverviewSchema DesignDatabase QueriesDatabase MigrationsDatabase SeedingDatabase PerformanceDatabase SecurityBackup & RestoreDatabase MonitoringAdvanced Database Features
Database

Database Monitoring

Database Monitoring

Learn how to monitor your Solo Kit database for optimal performance, health, and reliability. This guide covers health checks, performance metrics, alerting strategies, and comprehensive observability practices with Convex.

Monitoring Overview

Solo Kit Monitoring Architecture

Solo Kit implements comprehensive database observability with:

  • Real-time health checks: Continuous database connectivity monitoring
  • Performance metrics: Query response times and function execution
  • Function monitoring: Convex Dashboard function analytics
  • Error tracking: Automated error detection and alerting
  • Operational dashboards: Visual monitoring and trending

Monitoring Components

Health Check APIs (/api/health/database, /api/healthz)
    ↓
Performance Metrics (latency, function execution, query stats)
    ↓
Alerting & Notifications (Sentry, custom alerts)
    ↓
Dashboards & Visualization (Convex Dashboard)

Three-tier monitoring:

  1. Application-level: Health checks and performance metrics
  2. Function-level: Convex function execution and logs
  3. Platform-level: Convex Dashboard monitoring

Health Check Implementation

Built-in Health Check System

Solo Kit provides comprehensive health monitoring endpoints:

// convex/health.ts
import { query } from './_generated/server';

export const check = query({
  handler: async (ctx) => {
    const timestamp = Date.now();

    try {
      // Test database connectivity
      await ctx.db.query('users').take(1);

      return {
        status: 'healthy',
        timestamp,
        provider: 'convex',
      };
    } catch (error) {
      return {
        status: 'unhealthy',
        timestamp,
        error: error instanceof Error ? error.message : 'Unknown error',
        provider: 'convex',
      };
    }
  },
});

export const detailed = query({
  handler: async (ctx) => {
    const startTime = Date.now();

    try {
      // Test various operations
      const [users, sessions] = await Promise.all([
        ctx.db.query('users').take(1),
        ctx.db.query('sessions').take(1),
      ]);

      const latency = Date.now() - startTime;

      return {
        status: 'healthy',
        latency,
        timestamp: Date.now(),
        checks: {
          users: users.length >= 0,
          sessions: sessions.length >= 0,
        },
      };
    } catch (error) {
      return {
        status: 'unhealthy',
        latency: Date.now() - startTime,
        timestamp: Date.now(),
        error: error instanceof Error ? error.message : 'Unknown error',
      };
    }
  },
});

Health Check API Endpoints

Database-specific health check:

// apps/web/app/api/(dev)/health/database/route.ts
import { getConvexClient } from '@/lib/convex-server';
import { api } from '@/convex/_generated/api';

export async function GET() {
  try {
    const startTime = Date.now();
    const convex = getConvexClient();

    const health = await convex.query(api.health.check);
    const responseTime = Date.now() - startTime;

    const status = health.status === 'healthy' ? 200 : 503;

    return Response.json(
      {
        database: {
          status: health.status,
          responseTime: `${responseTime}ms`,
          timestamp: new Date().toISOString(),
          provider: 'convex',
        },
        environment: process.env.NODE_ENV,
        configured: true,
      },
      {
        status,
        headers: {
          'Cache-Control': 'no-cache, no-store, must-revalidate',
        },
      }
    );
  } catch (error) {
    return Response.json(
      {
        database: {
          status: 'unhealthy',
          error: error instanceof Error ? error.message : 'Health check failed',
          timestamp: new Date().toISOString(),
          provider: 'convex',
        },
        environment: process.env.NODE_ENV,
        configured: false,
      },
      { status: 503 }
    );
  }
}

Comprehensive application health check:

// apps/web/app/api/(dev)/healthz/route.ts
import { NextRequest } from 'next/server';
import { getConvexClient } from '@/lib/convex-server';
import { api } from '@/convex/_generated/api';

export async function GET(request: NextRequest) {
  const { searchParams } = new URL(request.url);
  const checkType = searchParams.get('type') || 'readiness';

  try {
    // Basic liveness check
    if (checkType === 'liveness') {
      return Response.json({
        status: 'healthy',
        timestamp: new Date().toISOString(),
        type: 'liveness',
      });
    }

    // Full readiness check
    const [databaseHealth, memoryHealth] = await Promise.all([
      checkDatabaseHealth(),
      Promise.resolve(getMemoryUsage()),
    ]);

    const overallStatus =
      databaseHealth.status === 'healthy' && memoryHealth.status === 'healthy'
        ? 'healthy'
        : 'unhealthy';

    return Response.json(
      {
        status: overallStatus,
        timestamp: new Date().toISOString(),
        checks: {
          database: databaseHealth,
          memory: memoryHealth,
        },
      },
      { status: overallStatus === 'healthy' ? 200 : 503 }
    );
  } catch (error) {
    return Response.json(
      {
        status: 'unhealthy',
        error: error instanceof Error ? error.message : 'Unknown error',
      },
      { status: 503 }
    );
  }
}

async function checkDatabaseHealth() {
  try {
    const convex = getConvexClient();
    const startTime = Date.now();

    const health = await convex.query(api.health.check);
    const responseTime = Date.now() - startTime;

    if (responseTime > 2000) {
      console.warn(`Slow database health check: ${responseTime}ms`);
    }

    return {
      status: health.status === 'healthy' ? 'healthy' : 'unhealthy',
      responseTime,
      provider: 'convex',
    };
  } catch (error) {
    return {
      status: 'unhealthy',
      error: error instanceof Error ? error.message : 'Health check failed',
      provider: 'convex',
    };
  }
}

function getMemoryUsage() {
  const usage = process.memoryUsage();
  const heapUsedMB = Math.round(usage.heapUsed / 1024 / 1024);
  const heapTotalMB = Math.round(usage.heapTotal / 1024 / 1024);

  return {
    status: heapUsedMB < heapTotalMB * 0.9 ? 'healthy' : 'warning',
    heapUsed: `${heapUsedMB}MB`,
    heapTotal: `${heapTotalMB}MB`,
    percentage: Math.round((heapUsedMB / heapTotalMB) * 100),
  };
}

Performance Monitoring

Response Time Tracking

Monitor query performance in Convex functions:

// convex/monitoring.ts
import { v } from 'convex/values';
import { query, internalMutation } from './_generated/server';

export const measureQueryPerformance = query({
  args: { queryName: v.string() },
  handler: async (ctx, args) => {
    const startTime = Date.now();

    // Execute the query being measured
    let result;
    switch (args.queryName) {
      case 'users':
        result = await ctx.db.query('users').take(10);
        break;
      case 'sessions':
        result = await ctx.db.query('sessions').take(10);
        break;
      default:
        result = [];
    }

    const duration = Date.now() - startTime;

    // Log slow queries
    if (duration > 1000) {
      console.warn(`Slow query detected: ${args.queryName} took ${duration}ms`);
    }

    return {
      queryName: args.queryName,
      duration,
      resultCount: result.length,
      timestamp: Date.now(),
    };
  },
});

// Store performance metrics
export const recordMetric = internalMutation({
  args: {
    name: v.string(),
    value: v.number(),
    tags: v.optional(v.any()),
  },
  handler: async (ctx, args) => {
    await ctx.db.insert('metrics', {
      name: args.name,
      value: args.value,
      tags: args.tags,
      timestamp: Date.now(),
    });
  },
});

// Get recent metrics
export const getRecentMetrics = query({
  args: {
    name: v.string(),
    limit: v.optional(v.number()),
  },
  handler: async (ctx, args) => {
    return await ctx.db
      .query('metrics')
      .filter((q) => q.eq(q.field('name'), args.name))
      .order('desc')
      .take(args.limit ?? 100);
  },
});

Function Performance Monitoring

Leverage Convex Dashboard for function metrics:

// convex/users.ts
import { query } from './_generated/server';

// Convex automatically tracks execution time
export const getUserWithTiming = query({
  args: { userId: v.id('users') },
  handler: async (ctx, args) => {
    console.time(`getUserWithTiming:${args.userId}`);

    const user = await ctx.db.get(args.userId);

    console.timeEnd(`getUserWithTiming:${args.userId}`);

    return user;
  },
});

Convex Dashboard Monitoring

Built-in Monitoring Features

Access Convex Dashboard:

# Open dashboard
npx convex dashboard

# View production dashboard
npx convex dashboard --prod

Dashboard provides:

  • Functions tab: Execution times, invocation counts, errors
  • Logs tab: Real-time function logs and errors
  • Data tab: Document counts and storage usage
  • Deployments: Deployment history and status

Metrics Available

Function Metrics:

  • Invocation count
  • Average execution time
  • P50, P95, P99 latency
  • Error rate
  • Cache hit rate

Data Metrics:

  • Document count per table
  • Storage usage
  • Index usage

Alerting & Notifications

Performance-Based Alerting

Create alerting functions:

// convex/alerts.ts
import { v } from 'convex/values';
import { internalMutation, query } from './_generated/server';

interface Alert {
  type: string;
  severity: 'warning' | 'critical';
  message: string;
  timestamp: number;
  metadata?: Record<string, any>;
}

export const checkPerformanceAlerts = query({
  handler: async (ctx) => {
    const alerts: Alert[] = [];

    // Check recent function performance
    const recentMetrics = await ctx.db
      .query('metrics')
      .filter((q) => q.eq(q.field('name'), 'query_duration'))
      .order('desc')
      .take(100);

    if (recentMetrics.length > 0) {
      const avgDuration = recentMetrics.reduce((sum, m) => sum + m.value, 0) / recentMetrics.length;

      if (avgDuration > 5000) {
        alerts.push({
          type: 'performance',
          severity: 'critical',
          message: `Very slow queries detected: avg ${avgDuration.toFixed(0)}ms`,
          timestamp: Date.now(),
          metadata: { avgDuration },
        });
      } else if (avgDuration > 1000) {
        alerts.push({
          type: 'performance',
          severity: 'warning',
          message: `Slow queries detected: avg ${avgDuration.toFixed(0)}ms`,
          timestamp: Date.now(),
          metadata: { avgDuration },
        });
      }
    }

    return alerts;
  },
});

export const logAlert = internalMutation({
  args: {
    type: v.string(),
    severity: v.union(v.literal('warning'), v.literal('critical')),
    message: v.string(),
    metadata: v.optional(v.any()),
  },
  handler: async (ctx, args) => {
    await ctx.db.insert('alerts', {
      ...args,
      timestamp: Date.now(),
      acknowledged: false,
    });

    // Log to console for Convex Dashboard
    const emoji = args.severity === 'critical' ? 'CRITICAL' : 'WARNING';
    console.log(`[${emoji}] ${args.type}: ${args.message}`);
  },
});

Error Tracking Integration

Integrate with Sentry:

// lib/monitoring.ts
import * as Sentry from '@sentry/nextjs';

export function trackDatabaseError(error: Error, context?: Record<string, any>) {
  Sentry.captureException(error, {
    tags: {
      component: 'database',
      provider: 'convex',
    },
    extra: context,
  });
}

export function trackSlowQuery(queryName: string, duration: number, threshold: number = 1000) {
  if (duration > threshold) {
    Sentry.captureMessage('Slow database query detected', {
      level: duration > 5000 ? 'error' : 'warning',
      tags: {
        query_name: queryName,
        performance_issue: 'true',
      },
      extra: {
        duration_ms: duration,
        threshold_ms: threshold,
      },
    });
  }
}

Custom Monitoring Dashboard

Monitoring API Endpoint

// apps/web/app/api/(dev)/monitoring/database/route.ts
import { getConvexClient } from '@/lib/convex-server';
import { api } from '@/convex/_generated/api';

export async function GET() {
  try {
    const convex = getConvexClient();

    const [health, alerts, metrics] = await Promise.all([
      convex.query(api.health.detailed),
      convex.query(api.alerts.checkPerformanceAlerts),
      convex.query(api.monitoring.getRecentMetrics, {
        name: 'query_duration',
        limit: 50,
      }),
    ]);

    return Response.json({
      status: 'success',
      timestamp: new Date().toISOString(),
      data: {
        health,
        alerts,
        metrics,
        summary: {
          healthy: health.status === 'healthy',
          latency: health.latency,
          alertCount: alerts.length,
          criticalAlerts: alerts.filter((a: any) => a.severity === 'critical').length,
        },
      },
    });
  } catch (error) {
    return Response.json(
      {
        status: 'error',
        error: error instanceof Error ? error.message : 'Monitoring failed',
        timestamp: new Date().toISOString(),
      },
      { status: 500 }
    );
  }
}

Scheduled Health Checks

Use Convex cron jobs for scheduled monitoring:

// convex/crons.ts
import { cronJobs } from 'convex/server';
import { internal } from './_generated/api';

const crons = cronJobs();

// Health check every minute
crons.interval('health-check', { minutes: 1 }, internal.monitoring.runHealthCheck);

// Performance metrics every 5 minutes
crons.interval('collect-metrics', { minutes: 5 }, internal.monitoring.collectMetrics);

// Alert check every minute
crons.interval('check-alerts', { minutes: 1 }, internal.alerts.checkAndNotify);

export default crons;
// convex/monitoring.ts
export const runHealthCheck = internalMutation({
  handler: async (ctx) => {
    const startTime = Date.now();

    try {
      // Test database operations
      await ctx.db.query('users').take(1);

      const latency = Date.now() - startTime;

      // Log health status
      if (latency > 1000) {
        console.warn(`Health check slow: ${latency}ms`);
      }

      // Record metric
      await ctx.db.insert('metrics', {
        name: 'health_check_latency',
        value: latency,
        timestamp: Date.now(),
      });
    } catch (error) {
      console.error('Health check failed:', error);
    }
  },
});

export const collectMetrics = internalMutation({
  handler: async (ctx) => {
    // Collect document counts
    const tables = ['users', 'sessions', 'subscriptions'];

    for (const table of tables) {
      const count = (await ctx.db.query(table as any).collect()).length;

      await ctx.db.insert('metrics', {
        name: `document_count_${table}`,
        value: count,
        timestamp: Date.now(),
      });
    }
  },
});

Best Practices

1. Monitoring Strategy

Implement the four golden signals:

  • Latency: How long requests take
  • Traffic: How many requests you're getting
  • Errors: Rate of requests that fail
  • Saturation: How full your service is

2. Alert Fatigue Prevention

Design meaningful alerts:

// Good: Actionable alerts with context
if (responseTime > 5000) {
  console.error(`CRITICAL: Database response time ${responseTime}ms exceeds 5s threshold`);
}

// Bad: Noisy alerts without context
if (responseTime > 100) {
  console.log('Slow database');
}

3. Log Effectively

Use structured logging:

// convex/logging.ts
export function logStructured(
  level: 'info' | 'warn' | 'error',
  message: string,
  context?: Record<string, any>
) {
  const logEntry = {
    level,
    message,
    timestamp: new Date().toISOString(),
    ...context,
  };

  switch (level) {
    case 'error':
      console.error(JSON.stringify(logEntry));
      break;
    case 'warn':
      console.warn(JSON.stringify(logEntry));
      break;
    default:
      console.log(JSON.stringify(logEntry));
  }
}

4. Monitor What Matters

Focus on business-critical metrics:

  • User authentication success rate
  • Payment processing latency
  • API response times
  • Error rates by endpoint

Viewing Logs

Convex CLI Logs

# Stream logs in real-time
npx convex logs

# Stream production logs
npx convex logs --prod

# Filter by log level
npx convex logs --success  # Only successful operations
npx convex logs --error    # Only errors

Dashboard Logs

Access detailed logs in Convex Dashboard:

  1. Open npx convex dashboard
  2. Navigate to "Logs" tab
  3. Filter by function, time range, or log level
  4. Search for specific messages

Next Steps

Complete your database mastery with the final guide:

  1. Advanced - Advanced Convex features and patterns
  2. Security - Advanced security monitoring
  3. Performance - Performance monitoring integration
  4. Backup & Restore - Backup monitoring

Comprehensive database monitoring ensures your Solo Kit application maintains optimal performance, reliability, and user experience at scale.

Backup & Restore

Protect your data with comprehensive backup strategies, disaster recovery plans, and reliable restore procedures using Convex

Advanced Database Features

Leverage advanced Convex features, complex query patterns, and sophisticated database design techniques in Solo Kit

On this page

Database MonitoringMonitoring OverviewSolo Kit Monitoring ArchitectureMonitoring ComponentsHealth Check ImplementationBuilt-in Health Check SystemHealth Check API EndpointsPerformance MonitoringResponse Time TrackingFunction Performance MonitoringConvex Dashboard MonitoringBuilt-in Monitoring FeaturesMetrics AvailableAlerting & NotificationsPerformance-Based AlertingError Tracking IntegrationCustom Monitoring DashboardMonitoring API EndpointScheduled Health ChecksBest Practices1. Monitoring Strategy2. Alert Fatigue Prevention3. Log Effectively4. Monitor What MattersViewing LogsConvex CLI LogsDashboard LogsNext Steps