Unity Database Health Service
Production-ready Unity MonoBehaviour for comprehensive database monitoring in Unity headless servers.
Table of Contents
- Description
- Supported Platforms
- Architecture
- Features
- Quick Start
- Configuration
- Events
- Manual Operations
- Status Display
- Integration Example
- Performance Notes
- Troubleshooting
- Production Recommendations
- Flow Diagram
- See Also
Description
Status: reference implementation, not compiled.
Unity/DatabaseHealthService.csis a complete, reviewed MonoBehaviour that is entirely inside a comment block. FishMMO-DB targets .NET Standard 2.1 and cannot referenceUnityEngine.dll, so nothing in this file is built by any configuration today, and no scene contains the component. To use it, copy the file into the Unity project (the header suggestsAssets/Scripts/Server/Implementation/DatabaseHealthService.cs), remove the enclosing comment block, and reference the FishMMO-Database assembly from the server asmdef. Everything below describes the code as written, not something currently running.
DatabaseHealthService is a Unity MonoBehaviour wrapper around the FishMMO.Database.Npgsql.Monitoring stack. Once moved into the Unity project it is dropped into a headless server scene and handed an initialized Database instance, after which it periodically polls connectivity, samples the connection pool, refreshes aggregated performance metrics, and raises plain C# event Action<T> callbacks that can be wired into alerting systems (Slack, PagerDuty, Discord webhook, etc.). All thresholds and intervals are configurable from the Unity Inspector. The health monitoring itself lives in the library and is reachable without Unity via IDatabase.HealthMonitor and IDatabase.MetricsTracker.
Supported Platforms
| Target | Status |
|---|---|
| Unity 6.3 LTS — headless Linux server build | Yes (recommended) |
| Unity 6.3 LTS — headless Windows server build | Yes |
| Unity 6.3 LTS — Editor (Play Mode) | Yes (for development) |
| Requirement | Notes |
|---|---|
| FishMMO-DB DLL | Must be present in Assets/Dependencies/ |
| PostgreSQL | 14+, reachable from the headless server |
Architecture
Unity Scene
└── GameObject "DatabaseHealthMonitor"
└── DatabaseHealthService (MonoBehaviour)
├── ref Database (FishMMO.Database)
│ └── NpgsqlDbContextFactory
│ └── Monitoring/
│ ├── Health/DatabaseHealthMonitor
│ ├── Metrics/DatabaseMetricsTracker
│ └── Diagnostics/QueryPerformanceTracker
└── Inspector-driven InvokeRepeating calls (Unity main thread, no coroutines):
├── PerformHealthCheck (initialHealthCheckDelay, then healthCheckInterval)
├── CheckPoolHealth (poolCheckInterval / 2, then poolCheckInterval)
└── RefreshMetrics (every metricsRefreshInterval)
Features
✅ Automatic Health Checks - Periodic connectivity and response time monitoring
✅ Connection Pool Monitoring - Open-connection utilization and exhaustion tracking
✅ Query Performance Tracking - Slow query detection and metrics collection
✅ Configurable Thresholds - Pool warning/critical utilization levels drive the status and events
✅ Inspector Integration - Real-time status display in Unity Editor
✅ Event System - Subscribe to health/pool/slow query events
✅ Context Menu Commands - Manual health checks via Unity Editor
Quick Start
1. Add to Scene
// Add DatabaseHealthService component to a GameObject in your server scene
GameObject healthMonitor = new GameObject("DatabaseHealthMonitor");
DatabaseHealthService healthService = healthMonitor.AddComponent<DatabaseHealthService>();
2. Initialize
// In your server startup code (after Database initialization)
public class ServerManager : MonoBehaviour
{
private Database database;
private DatabaseHealthService healthService;
void Start()
{
var configuration = new ConfigurationBuilder()
.SetBasePath("./Config")
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: false)
.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT")}.json", optional: true, reloadOnChange: false)
.AddEnvironmentVariables()
.Build();
// Initialize database
database = new Database(
configuration,
enableLogging: false,
commandTimeout: 10,
healthCheckWarningMs: 100,
healthCheckCriticalMs: 500);
// Initialize health service
healthService = GetComponent<DatabaseHealthService>();
healthService.Initialize(database);
}
}
3. Configure (Optional)
Use the Unity Inspector to adjust:
- Health Check Interval: How often to check database connectivity (default: 30s)
- Pool Check Interval: How often to check connection pool (default: 15s)
- Metrics Refresh Interval: How often to refresh the metrics summary (default: 60s)
- Alert Thresholds: Warning/critical levels for open-connection utilization (default: 70%/85%)
Configuration
Inspector Fields
Health Check Configuration
healthCheckInterval: Seconds between full health checks (30s default)initialHealthCheckDelay: Delay before first check (5s default)enableHealthChecks: Toggle automatic health checks
Pool Monitoring Configuration
enablePoolMonitoring: Toggle pool health monitoringpoolCheckInterval: Seconds between pool checks (15s default)poolWarningThreshold: Utilization % for warnings (70% default)poolCriticalThreshold: Utilization % for critical alerts (85% default)
Metrics Configuration
enableMetricsRefresh: Toggle the periodic metrics refreshmetricsRefreshInterval: Seconds between refreshes (60s default)
There are no alerting fields on the component. It logs nothing except one warning when a manual
check is attempted before Initialize; alerting is the subscriber's job, and the slow query
threshold belongs to QueryPerformanceTracking in appsettings.json.
Events
Seven plain C# events, all Action<T>:
| Event | Payload |
|---|---|
OnHealthCheckCompleted |
HealthCheckResult — every check |
OnHealthStatusChanged |
HealthCheckResult — only when the status changes |
OnPoolHealthChecked |
PoolHealthResult — every pool sample |
OnPoolStatusChanged |
PoolHealthResult — only when the status changes |
OnSlowQueryDetected |
SlowQueryEventArgs |
OnMetricsSummaryUpdated |
MetricsSummary |
OnMonitoringError |
Exception |
Subscribe to events for external monitoring systems:
healthService.OnHealthStatusChanged += (result) =>
{
if (result.Status == HealthStatus.Unhealthy)
{
// Send alert to Slack, PagerDuty, etc.
SendCriticalAlert($"Database unhealthy: {result.Message}");
}
};
healthService.OnPoolStatusChanged += (poolHealth) =>
{
if (poolHealth.Status == PoolHealthStatus.Critical)
{
// Pool is running low on connections
LogWarning($"Pool critical: {poolHealth.RecommendedAction}");
}
};
healthService.OnSlowQueryDetected += (slowQuery) =>
{
// Log slow queries to your monitoring system
LogSlowQuery(slowQuery.OperationName, slowQuery.Duration);
};
Manual Operations
Via Code
// Trigger manual health check
healthService.ManualHealthCheck();
// Check pool health
healthService.ManualPoolCheck();
// Refresh the cached metrics summary (and raise OnMetricsSummaryUpdated)
healthService.ManualMetricsRefresh();
// Read the current status, or a formatted report
HealthStatus status = healthService.GetCurrentHealthStatus();
string report = healthService.GetHealthReport();
Debug.Log(report);
// Control monitoring
healthService.StartMonitoring();
healthService.StopMonitoring();
Via Unity Editor
Right-click the component in the Inspector to access context menu commands:
- Perform Health Check - Immediate health check
- Check Pool Health - Immediate pool check
- Log Metrics - Print current metrics
- Print Health Report - Full formatted report
- Start/Stop Monitoring - Control automatic monitoring
Status Display
The Inspector shows real-time read-only status:
currentHealthStatus: Database health (Healthy/Degraded/Unhealthy)lastHealthMessage: Last check messagelastResponseTimeMs: Database response timepoolStatus: Pool health statuspoolUtilization: Pool usage percentagepoolConnections: Active/Max connectionstotalQueries: Total queries executedsuccessRate: Query success percentageavgResponseTimeMs: Average query time
Integration Example
Complete server integration:
using UnityEngine;
using FishMMO.Database;
using FishMMO.Server.Database;
using FishMMO.Database.Npgsql.Monitoring.Health;
using Microsoft.Extensions.Configuration;
public class GameServerManager : MonoBehaviour
{
private Database database;
private DatabaseHealthService healthService;
async void Start()
{
var configuration = new ConfigurationBuilder()
.SetBasePath("./Config")
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: false)
.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT")}.json", optional: true, reloadOnChange: false)
.AddEnvironmentVariables()
.Build();
// Initialize database
database = new Database(
configuration,
enableLogging: false);
// Setup health monitoring
GameObject healthObj = new GameObject("DatabaseHealthMonitor");
healthService = healthObj.AddComponent<DatabaseHealthService>();
healthService.Initialize(database);
// Subscribe to critical alerts
healthService.OnHealthStatusChanged += OnDatabaseHealthChanged;
healthService.OnPoolStatusChanged += OnPoolHealthChanged;
Debug.Log("Database and monitoring initialized successfully.");
}
private void OnDatabaseHealthChanged(HealthCheckResult result)
{
if (result.Status == HealthStatus.Unhealthy)
{
// Critical: Database unavailable
// Trigger server shutdown or failover
Debug.LogError($"CRITICAL: Database unhealthy - {result.Message}");
// InitiateServerShutdown();
}
else if (result.Status == HealthStatus.Degraded)
{
// Warning: Performance degraded
Debug.LogWarning($"WARNING: Database degraded - {result.Message}");
}
}
private void OnPoolHealthChanged(PoolHealthResult poolHealth)
{
if (poolHealth.Status == PoolHealthStatus.Unhealthy)
{
// Pool exhausted - may need to reject new connections
Debug.LogError($"Pool exhausted: {poolHealth.Message}");
Debug.LogError($"Action: {poolHealth.RecommendedAction}");
}
}
void OnDestroy()
{
// Cleanup subscriptions
if (healthService != null)
{
healthService.OnHealthStatusChanged -= OnDatabaseHealthChanged;
healthService.OnPoolStatusChanged -= OnPoolHealthChanged;
}
}
}
Performance Notes
- Health checks perform a lightweight
SELECT 1query - minimal overhead - Pool checks are in-memory operations - no database query
- Metrics refresh aggregates cached data - no performance impact
- Safe for frequent monitoring (15-30 second intervals)
- All operations run on Unity's main thread (no async/await needed)
Troubleshooting
"Not Initialized" in Inspector
- Ensure
Initialize(database)is called after database creation - Check for exceptions during initialization in the console
No Health Checks Running
- Verify
enableHealthChecksis checked in Inspector - Check that the GameObject is active
- Check
IsMonitoring—StartMonitoringreturns silently ifIsInitializedis false, and the component logs nothing when it starts
Pool Always Shows "Unknown"
- Ensure database is properly initialized
- Verify DbContextFactory is of type
NpgsqlDbContextFactory - Check that pool metrics are being tracked
Metrics Not Updating
- Enable query performance tracking in
appsettings.json:"QueryPerformanceTracking": { "Enabled": true, "Level": "Standard" }
Production Recommendations
- Set appropriate thresholds based on your workload
- Integrate with external monitoring (Slack, PagerDuty, Datadog)
- Log health reports to persistent storage for historical analysis
- Monitor pool exhaustion - indicates need to increase
MaxPoolSize - Track slow queries - optimize or add indexes for recurring slow operations
Flow Diagram
flowchart TD
Start[Scene loads] --> Add[AddComponent DatabaseHealthService]
Add --> Init["Initialize(database)"]
Init --> Start3[Start coroutines]
Start3 --> Health["InvokeRepeating PerformHealthCheck"]
Start3 --> Pool["InvokeRepeating CheckPoolHealth"]
Start3 --> Metrics["InvokeRepeating RefreshMetrics"]
Health -->|SELECT 1| DB[(PostgreSQL)]
DB -->|ok/fail + ms| Result[HealthCheckResult]
Result --> Evt1[OnHealthStatusChanged]
Pool -->|read counters| CPM[ConnectionPoolMetrics]
CPM --> Util{Utilization}
Util -->|"greater than poolWarningThreshold"| Warn[Status: Warning]
Util -->|"greater than poolCriticalThreshold"| Crit[Status: Critical]
Util --> Evt2[OnPoolStatusChanged]
Metrics -->|read tracker| QPT[QueryPerformanceTracker]
QPT -->|slow query| Evt3[OnSlowQueryDetected]
QPT -->|summary| Sum[OnMetricsSummaryUpdated]
Evt1 --> Ext[External alerting<br/>Slack / PagerDuty / Discord]
Evt2 --> Ext
Evt3 --> Ext
See Also
- Monitoring/README.md - Connection pool health, metrics, and query diagnostics
- Database.cs - Main database orchestrator
- DatabaseHealthMonitor.cs - Core health check logic