Describe the bug
On icingadb-web 1.4.0 (current latest release), the Icinga DB Health check reports:
Icinga DB is outdated, please upgrade to version 1.4.0 or later.
…even though the Icinga DB daemon is newer than 1.4.0 (in my case v1.5.1), the schema is fully migrated, and data is flowing (fresh heartbeat).
This looks identical to #1233 / #1230, but it is a different root cause. #1230 was a version-string comparison bug fixed in 1.2.1; that fix (IcingaHealth::normalizeVersion()) is present here. In this case the daemon version is never read at all, because the schema-version gate added later (#1196) resolves incorrectly. The buggy code is still present in main.
Root cause
IcingaHealth::checkHealth() only reads icingadb_instance.icingadb_version when Backend::supportsDependencies() is true, which requires Backend::getDbSchemaVersion() >= 7 (MySQL) / >= 5 (PostgreSQL). getDbSchemaVersion() is:
// library/Icingadb/Common/Backend.php
self::$schemaVersion = Schema::on(self::getDb())
->columns('version')
->first() // no explicit ordering
->version ?? 0;
with
// library/Icingadb/Model/Schema.php
public function getDefaultSort(): array
{
return ['timestamp desc'];
}
so the effective query is SELECT version FROM icingadb_schema ORDER BY timestamp DESC LIMIT 1.
icingadb_schema holds one row per applied migration. When two migrations are applied within the same millisecond — which happens routinely when several upgrades are applied back-to-back (e.g. stepping through 5 → 6 → 7 during a single upgrade) — multiple rows share the maximum timestamp. ORDER BY timestamp DESC then has a tie, and per SQL semantics the tie-break is unspecified; the engine can return a row whose version is not the maximum.
Concretely, on my MySQL master icingadb_schema looked like:
| id |
version |
timestamp (ms) |
| 1 |
4 |
1739353072000 |
| 2 |
5 |
1782862068000 |
| 3 |
6 |
1782862069000 |
| 4 |
7 |
1782862069000 |
SELECT version FROM icingadb_schema ORDER BY timestamp DESC LIMIT 1 returns 6 (rows 3 and 4 tie on timestamp). So getDbSchemaVersion() = 6, supportsDependencies() = 6 >= 7 = false, Instance::getColumns() omits icingadb_version, and checkHealth() hits ! isset($instance->icingadb_version) → the "outdated" CRITICAL.
This is deterministic given the table state — not a runtime race — so refreshing the page never clears it.
To Reproduce
- Have
icingadb_schema contain two rows that share the maximum timestamp where the higher version is not the one the DB returns first for ORDER BY timestamp DESC LIMIT 1 (occurs naturally when consecutive migrations are applied in the same millisecond).
- Run an Icinga DB daemon ≥ 1.4.0 with
icingadb-web 1.4.0.
- Open Health → the Icinga DB check reports "outdated" despite a healthy, up-to-date daemon.
Expected behavior
getDbSchemaVersion() should return the maximum applied schema version. The schema version is defined by the largest version value; timestamp is not guaranteed unique or strictly ordered with version at millisecond resolution, so it must not be the sort key here.
Suggested fix
Determine the current schema version by version, not by timestamp, e.g.:
self::$schemaVersion = Schema::on(self::getDb())
->columns('version')
->orderBy('version', SORT_DESC)
->limit(1)
->first()
->version ?? 0;
(or SELECT MAX(version)), rather than relying on the Schema model's timestamp desc default sort.
Workaround for affected users
Make the highest schema version's row strictly the latest timestamp — metadata only, the daemon keys migrations on version, not timestamp:
UPDATE icingadb_schema SET timestamp = timestamp + 1 WHERE version = <max_version>;
No service restart needed (the health check recomputes per request).
Your Environment
- Icinga DB Web version: 1.4.0 (latest release; bug also present in
main)
- Icinga DB (daemon) version: v1.5.1
- Icinga Web 2 version: 2.14.0
- Database: MySQL (MySQL/MariaDB affected; the PostgreSQL branch requires
>= 5 and is susceptible in the same way)
- OS: Ubuntu
Related: #1196 (introduced the schema-version gate), #1230 / #1233 (the earlier, distinct version-string comparison bug fixed in 1.2.1).
Describe the bug
On
icingadb-web1.4.0 (current latest release), the Icinga DB Health check reports:…even though the Icinga DB daemon is newer than 1.4.0 (in my case
v1.5.1), the schema is fully migrated, and data is flowing (fresh heartbeat).This looks identical to #1233 / #1230, but it is a different root cause. #1230 was a version-string comparison bug fixed in 1.2.1; that fix (
IcingaHealth::normalizeVersion()) is present here. In this case the daemon version is never read at all, because the schema-version gate added later (#1196) resolves incorrectly. The buggy code is still present inmain.Root cause
IcingaHealth::checkHealth()only readsicingadb_instance.icingadb_versionwhenBackend::supportsDependencies()is true, which requiresBackend::getDbSchemaVersion() >= 7(MySQL) />= 5(PostgreSQL).getDbSchemaVersion()is:with
so the effective query is
SELECT version FROM icingadb_schema ORDER BY timestamp DESC LIMIT 1.icingadb_schemaholds one row per applied migration. When two migrations are applied within the same millisecond — which happens routinely when several upgrades are applied back-to-back (e.g. stepping through 5 → 6 → 7 during a single upgrade) — multiple rows share the maximumtimestamp.ORDER BY timestamp DESCthen has a tie, and per SQL semantics the tie-break is unspecified; the engine can return a row whoseversionis not the maximum.Concretely, on my MySQL master
icingadb_schemalooked like:SELECT version FROM icingadb_schema ORDER BY timestamp DESC LIMIT 1returns 6 (rows 3 and 4 tie on timestamp). SogetDbSchemaVersion()= 6,supportsDependencies()=6 >= 7= false,Instance::getColumns()omitsicingadb_version, andcheckHealth()hits! isset($instance->icingadb_version)→ the "outdated" CRITICAL.This is deterministic given the table state — not a runtime race — so refreshing the page never clears it.
To Reproduce
icingadb_schemacontain two rows that share the maximumtimestampwhere the higherversionis not the one the DB returns first forORDER BY timestamp DESC LIMIT 1(occurs naturally when consecutive migrations are applied in the same millisecond).icingadb-web1.4.0.Expected behavior
getDbSchemaVersion()should return the maximum applied schema version. The schema version is defined by the largestversionvalue;timestampis not guaranteed unique or strictly ordered withversionat millisecond resolution, so it must not be the sort key here.Suggested fix
Determine the current schema version by
version, not bytimestamp, e.g.:(or
SELECT MAX(version)), rather than relying on theSchemamodel'stimestamp descdefault sort.Workaround for affected users
Make the highest schema version's row strictly the latest timestamp — metadata only, the daemon keys migrations on
version, nottimestamp:No service restart needed (the health check recomputes per request).
Your Environment
main)>= 5and is susceptible in the same way)Related: #1196 (introduced the schema-version gate), #1230 / #1233 (the earlier, distinct version-string comparison bug fixed in 1.2.1).