{"record":{"id":"ef552b41914ca638","repo":"apache/druid","slug":"received-out-of-order-update-for-table-s-cache","errorCode":null,"errorMessage":"Received out-of-order update for table: %s. Cache: %d, update:%d","messagePattern":"Received out-of-order update for table: (.+?)\\. Cache: (.+?), update:(.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"extensions-core/druid-catalog/src/main/java/org/apache/druid/catalog/sync/CachedMetadataCatalog.java","lineNumber":258,"sourceCode":"    }\n\n    private boolean checkResolved(TableEntry entry, TableMetadata update, String action)\n    {\n      if (entry.table == null) {\n        LOG.error(\"Received %s update for unresolved table: %s\",\n            action,\n            update.id().sqlName()\n        );\n        // TODO: force resync\n        return false;\n      }\n      return true;\n    }\n\n    private boolean checkVersion(TableEntry entry, TableMetadata update)\n    {\n      if (entry.table.updateTime() > update.updateTime()) {\n        LOG.warn(\n            \"Received out-of-order update for table: %s. Cache: %d, update:%d\",\n            update.id().sqlName(),\n            entry.table.updateTime(),\n            update.updateTime()\n        );\n        // TODO: force resync\n        return false;\n      }\n      return true;\n    }\n\n    public synchronized Set<String> tableNames()\n    {\n      Set<String> tables = new HashSet<>();\n      cache.forEach((k, v) -> {\n        if (v.table != null) {\n          tables.add(k);\n        }","sourceCodeStart":240,"sourceCodeEnd":276,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/extensions-core/druid-catalog/src/main/java/org/apache/druid/catalog/sync/CachedMetadataCatalog.java#L240-L276","documentation":"checkVersion() detects an update whose updateTime is older than the timestamp already cached for the table — i.e. an out-of-order (stale) update notification. The stale update is rejected (the method returns false) and a forced resync is planned (TODO in code). This guards the cache against regressing to older metadata.","triggerScenarios":"computeUpdate/computeColumnsUpdate/computePropertiesUpdate receives a TableMetadata update whose updateTime() is less than entry.table.updateTime(); concurrent writers to the same table delivering events out of order; message bus reordering or replay.","commonSituations":"Multiple catalog writers (e.g. two coordinator/broker sessions) updating the same table; clock skew between catalog writer nodes; replayed notifications after reconnect; partitioned delivery channels delivering out of order.","solutions":["Ignore if rare — the stale update is correctly rejected; the cache keeps the newer version.","Check for multiple processes writing to the same catalog table concurrently and ensure a single writer path.","Synchronize clocks (NTP) on hosts producing catalog updates to avoid spurious out-of-order detection.","Trigger resync() to pull the authoritative state, since the code's TODO force-resync is not yet implemented."],"exampleFix":"// before\n// stale notification arrives; cache keeps old view until next periodic resync\n// after\nreceiver.resync(); // explicitly reconcile cache when out-of-order updates are observed","handlingStrategy":"validation","validationCode":"if (update.updateTime() <= entry.table.updateTime()) { skipStaleUpdate(); scheduleResync(); }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep a single writer per catalog table.","Synchronize clocks with NTP across writer hosts.","Use ordered/monotonic delivery for update notifications."],"tags":["cache","out-of-order","concurrency","catalog-sync"],"backgroundTag":"internal-invariant-violation","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}