This commit resolves duplicate key errors that occur when multiple monitors
attempt to insert statistics simultaneously into stat_* tables.
The fix addresses three interconnected issues:
1. **Circular Dependency Resolution**: database.js imports UptimeCalculator at
module level, but UptimeCalculator needs Database.dbConfig. Fixed by using
local imports in UptimeCalculator methods to ensure Database.dbConfig is
properly initialized when accessed.
2. **Database Configuration Initialization**: Database.dbConfig was not set
in the catch block when db-config.json is missing, causing undefined access
errors. Fixed by ensuring Database.dbConfig is always set.
3. **Schema Column Naming Mismatch**: RedBean ORM uses camelCase (pingMin/pingMax)
but Knex migrations create snake_case columns (ping_min/ping_max). Fixed by
using correct snake_case column names in SQL queries.
4. **Atomic Upsert Operations**: Implemented database-specific upsert logic:
- SQLite: INSERT ... ON CONFLICT DO UPDATE
- MariaDB: INSERT ... ON DUPLICATE KEY UPDATE
The solution maintains backward compatibility by falling back to R.store()
when upsert fails, ensuring no data loss while eliminating race conditions
for users with many monitors (200+).
Fixes#5357
Resolves issue where multiple monitors updating statistics simultaneously
can cause "Duplicate entry" database errors for the same monitor_id and
timestamp combination in stat_hourly and stat_daily tables.
Changes:
- Add database-specific upsert logic for SQLite and MariaDB
- Replace R.store() calls with atomic upsert operations
- Add fallback to original R.store() if upsert fails
- Initialize default values for new stat beans to prevent null conflicts
- Use ON CONFLICT/ON DUPLICATE KEY UPDATE for atomic stat updates
This fix is particularly important for high-volume monitoring scenarios
with 400+ monitors where concurrent heartbeats can trigger race conditions
in the stat insertion process.
Fixes#5357