Loading debian/changelog +10 −0 Original line number Diff line number Diff line mediadb (20260419+31) unstable; urgency=critical * Fix critical data corruption during concurrent import + periodic repair/sync: `repair_replication()`, `sync_from_cluster()`, scrub, and rebalance are now blocked while an import is in progress. Previously these could push half-imported index/store metadata to the cluster, overwriting valid data with garbage. -- Jan Koester <jan.koester@tuxist.de> Sun, 19 Apr 2026 17:30:00 +0200 mediadb (20260419+30) unstable; urgency=medium * Add Repair Index button to cluster.html UI. Loading src/app.cpp +8 −0 Original line number Diff line number Diff line Loading @@ -884,6 +884,10 @@ HttpResponse App::handle_cluster_scrub(const HttpRequest& req) { return error_json(400, "cluster not enabled"); } if (import_running_.load()) { return error_json(409, "import in progress — scrub blocked to prevent data corruption"); } if (g_Cluster->isCritical()) { return error_json(503, "cluster is critical — need more nodes online"); } Loading Loading @@ -913,6 +917,10 @@ HttpResponse App::handle_cluster_rebalance(const HttpRequest& req) { return error_json(400, "cluster not enabled"); } if (import_running_.load()) { return error_json(409, "import in progress — rebalance blocked to prevent data corruption"); } if (g_Cluster->isCritical()) { return error_json(503, "cluster is critical — need more nodes online"); } Loading src/backend.cpp +9 −1 Original line number Diff line number Diff line Loading @@ -2981,6 +2981,10 @@ void ClusterMediaBackend::sync_from_cluster() { std::cerr << "[CLUSTER-SYNC] cluster not running, skipping sync\n"; return; } if (importing_.load()) { std::cerr << "[CLUSTER-SYNC] import in progress, skipping sync\n"; return; } uint64_t index_gid = cluster_group_id("index"); bool has_index = false; Loading Loading @@ -3226,6 +3230,8 @@ void ClusterMediaBackend::replicate_store(const std::string& store_id) { void ClusterMediaBackend::repair_replication() { if (!cluster_.isRunning()) return; if (importing_.load()) return; if (!initial_sync_ok_.load()) return; size_t total_peers = cluster_.getTotalPeers(); if (total_peers < 2) return; Loading Loading @@ -3306,8 +3312,10 @@ void ClusterMediaBackend::sync_loop() { // Run repair every 6th cycle (~30s) if (++cycle >= 6) { cycle = 0; if (!importing_.load()) { repair_replication(); } } } catch (const std::exception& e) { DBG_LOG("[CLUSTER-SYNC] periodic sync failed: " << e.what() << "\n"); } catch (...) { Loading Loading
debian/changelog +10 −0 Original line number Diff line number Diff line mediadb (20260419+31) unstable; urgency=critical * Fix critical data corruption during concurrent import + periodic repair/sync: `repair_replication()`, `sync_from_cluster()`, scrub, and rebalance are now blocked while an import is in progress. Previously these could push half-imported index/store metadata to the cluster, overwriting valid data with garbage. -- Jan Koester <jan.koester@tuxist.de> Sun, 19 Apr 2026 17:30:00 +0200 mediadb (20260419+30) unstable; urgency=medium * Add Repair Index button to cluster.html UI. Loading
src/app.cpp +8 −0 Original line number Diff line number Diff line Loading @@ -884,6 +884,10 @@ HttpResponse App::handle_cluster_scrub(const HttpRequest& req) { return error_json(400, "cluster not enabled"); } if (import_running_.load()) { return error_json(409, "import in progress — scrub blocked to prevent data corruption"); } if (g_Cluster->isCritical()) { return error_json(503, "cluster is critical — need more nodes online"); } Loading Loading @@ -913,6 +917,10 @@ HttpResponse App::handle_cluster_rebalance(const HttpRequest& req) { return error_json(400, "cluster not enabled"); } if (import_running_.load()) { return error_json(409, "import in progress — rebalance blocked to prevent data corruption"); } if (g_Cluster->isCritical()) { return error_json(503, "cluster is critical — need more nodes online"); } Loading
src/backend.cpp +9 −1 Original line number Diff line number Diff line Loading @@ -2981,6 +2981,10 @@ void ClusterMediaBackend::sync_from_cluster() { std::cerr << "[CLUSTER-SYNC] cluster not running, skipping sync\n"; return; } if (importing_.load()) { std::cerr << "[CLUSTER-SYNC] import in progress, skipping sync\n"; return; } uint64_t index_gid = cluster_group_id("index"); bool has_index = false; Loading Loading @@ -3226,6 +3230,8 @@ void ClusterMediaBackend::replicate_store(const std::string& store_id) { void ClusterMediaBackend::repair_replication() { if (!cluster_.isRunning()) return; if (importing_.load()) return; if (!initial_sync_ok_.load()) return; size_t total_peers = cluster_.getTotalPeers(); if (total_peers < 2) return; Loading Loading @@ -3306,8 +3312,10 @@ void ClusterMediaBackend::sync_loop() { // Run repair every 6th cycle (~30s) if (++cycle >= 6) { cycle = 0; if (!importing_.load()) { repair_replication(); } } } catch (const std::exception& e) { DBG_LOG("[CLUSTER-SYNC] periodic sync failed: " << e.what() << "\n"); } catch (...) { Loading