Loading debian/changelog +11 −0 Original line number Diff line number Diff line mediadb (20260419+22) unstable; urgency=critical * Fix Cluster Media Import data loss / corruption: The import_db_from_buffer sync loop in ClusterMediaBackend now halts replication immediately (after up to 3 retries) if a media chunk fails to replicate over the network. Previously, it would silently ignore failed chunks and continue committing store:id and index blobs, leaving orphan keys on peer nodes ("nicht auf allen nodes heile"). -- Jan Koester <jan.koester@tuxist.de> Sun, 19 Apr 2026 12:30:00 +0200 mediadb (20260419+21) unstable; urgency=low * Fix delete_store in ClusterMediaBackend to properly delete all associated media blobs from cluster storage. Loading src/backend.cpp +18 −35 Original line number Diff line number Diff line Loading @@ -2859,51 +2859,34 @@ bool ClusterMediaBackend::import_db_from_buffer(const std::uint8_t* data, std::s // cluster_op_mutex_ is still held so ClusterMediaBackend reads wait, // but sync cannot interfere. DBG_LOG("[CLUSTER-IMPORT] Phase 2: replicating " << repl_queue.size() << " buffers\n"); std::vector<std::pair<std::string, std::vector<uint8_t>>> failed_entries; int repl_idx = 0; for (auto& [key, buf] : repl_queue) { ++repl_idx; DBG_LOG("[CLUSTER-IMPORT] replicate " << repl_idx << "/" << repl_queue.size() << " key=" << key << " size=" << buf.size() << "\n"); if (!cluster_.replicate(key, buf.data(), buf.size())) { DBG_LOG("[CLUSTER-IMPORT] FAILED key=" << key << "\n"); failed_entries.emplace_back(std::move(key), std::move(buf)); } else { DBG_LOG("[CLUSTER-IMPORT] OK key=" << key << "\n"); buf.clear(); buf.shrink_to_fit(); } } repl_queue.clear(); // Phase 3: Retry failures (using the original buffers kept from Phase 2) if (!failed_entries.empty()) { DBG_LOG("[CLUSTER-IMPORT] Phase 3: retrying " << failed_entries.size() << " failed keys\n"); bool ok = false; constexpr int MAX_RETRIES = 3; for (int attempt = 1; attempt <= MAX_RETRIES && !failed_entries.empty(); ++attempt) { DBG_LOG("[CLUSTER-IMPORT] retry attempt " << attempt << "\n"); for (int attempt = 1; attempt <= MAX_RETRIES; ++attempt) { if (cluster_.replicate(key, buf.data(), buf.size())) { ok = true; break; } DBG_LOG("[CLUSTER-IMPORT] retry attempt " << attempt << " for key=" << key << "\n"); std::this_thread::sleep_for(std::chrono::seconds(attempt * 2)); std::vector<std::pair<std::string, std::vector<uint8_t>>> still_failed; for (auto& [key, buf] : failed_entries) { bool ok = false; if (key == "index") { // Re-generate index buffer (cheap) auto idx_buf = local_.save_index_to_buffer(); ok = cluster_.replicate("index", idx_buf.data(), idx_buf.size()); } else { // store: and media: keys — use the original buffer ok = cluster_.replicate(key, buf.data(), buf.size()); } if (!ok) { still_failed.emplace_back(std::move(key), std::move(buf)); } } failed_entries = std::move(still_failed); if (failed_entries.empty()) { break; } DBG_LOG("[CLUSTER-IMPORT] FAILED key=" << key << " after retries. Aborting import.\n"); importing_.store(false); return false; } DBG_LOG("[CLUSTER-IMPORT] OK key=" << key << "\n"); buf.clear(); buf.shrink_to_fit(); } repl_queue.clear(); DBG_LOG("[CLUSTER-IMPORT] complete, success\n"); importing_.store(false); Loading Loading
debian/changelog +11 −0 Original line number Diff line number Diff line mediadb (20260419+22) unstable; urgency=critical * Fix Cluster Media Import data loss / corruption: The import_db_from_buffer sync loop in ClusterMediaBackend now halts replication immediately (after up to 3 retries) if a media chunk fails to replicate over the network. Previously, it would silently ignore failed chunks and continue committing store:id and index blobs, leaving orphan keys on peer nodes ("nicht auf allen nodes heile"). -- Jan Koester <jan.koester@tuxist.de> Sun, 19 Apr 2026 12:30:00 +0200 mediadb (20260419+21) unstable; urgency=low * Fix delete_store in ClusterMediaBackend to properly delete all associated media blobs from cluster storage. Loading
src/backend.cpp +18 −35 Original line number Diff line number Diff line Loading @@ -2859,51 +2859,34 @@ bool ClusterMediaBackend::import_db_from_buffer(const std::uint8_t* data, std::s // cluster_op_mutex_ is still held so ClusterMediaBackend reads wait, // but sync cannot interfere. DBG_LOG("[CLUSTER-IMPORT] Phase 2: replicating " << repl_queue.size() << " buffers\n"); std::vector<std::pair<std::string, std::vector<uint8_t>>> failed_entries; int repl_idx = 0; for (auto& [key, buf] : repl_queue) { ++repl_idx; DBG_LOG("[CLUSTER-IMPORT] replicate " << repl_idx << "/" << repl_queue.size() << " key=" << key << " size=" << buf.size() << "\n"); if (!cluster_.replicate(key, buf.data(), buf.size())) { DBG_LOG("[CLUSTER-IMPORT] FAILED key=" << key << "\n"); failed_entries.emplace_back(std::move(key), std::move(buf)); } else { DBG_LOG("[CLUSTER-IMPORT] OK key=" << key << "\n"); buf.clear(); buf.shrink_to_fit(); } } repl_queue.clear(); // Phase 3: Retry failures (using the original buffers kept from Phase 2) if (!failed_entries.empty()) { DBG_LOG("[CLUSTER-IMPORT] Phase 3: retrying " << failed_entries.size() << " failed keys\n"); bool ok = false; constexpr int MAX_RETRIES = 3; for (int attempt = 1; attempt <= MAX_RETRIES && !failed_entries.empty(); ++attempt) { DBG_LOG("[CLUSTER-IMPORT] retry attempt " << attempt << "\n"); for (int attempt = 1; attempt <= MAX_RETRIES; ++attempt) { if (cluster_.replicate(key, buf.data(), buf.size())) { ok = true; break; } DBG_LOG("[CLUSTER-IMPORT] retry attempt " << attempt << " for key=" << key << "\n"); std::this_thread::sleep_for(std::chrono::seconds(attempt * 2)); std::vector<std::pair<std::string, std::vector<uint8_t>>> still_failed; for (auto& [key, buf] : failed_entries) { bool ok = false; if (key == "index") { // Re-generate index buffer (cheap) auto idx_buf = local_.save_index_to_buffer(); ok = cluster_.replicate("index", idx_buf.data(), idx_buf.size()); } else { // store: and media: keys — use the original buffer ok = cluster_.replicate(key, buf.data(), buf.size()); } if (!ok) { still_failed.emplace_back(std::move(key), std::move(buf)); } } failed_entries = std::move(still_failed); if (failed_entries.empty()) { break; } DBG_LOG("[CLUSTER-IMPORT] FAILED key=" << key << " after retries. Aborting import.\n"); importing_.store(false); return false; } DBG_LOG("[CLUSTER-IMPORT] OK key=" << key << "\n"); buf.clear(); buf.shrink_to_fit(); } repl_queue.clear(); DBG_LOG("[CLUSTER-IMPORT] complete, success\n"); importing_.store(false); Loading