Przeglądaj źródła

feat(recovery): RecoveryMode enum + RecoveryOutcome struct (v1.6.1 scaffold)

fszontagh 3 miesięcy temu
rodzic
commit
be5cd99405

+ 1 - 1
VERSION

@@ -1 +1 @@
-1.6.0
+1.6.1

+ 4 - 4
service/src/persistence/persistence_manager.cpp

@@ -80,7 +80,7 @@ void PersistenceManager::stop() {
     spdlog::info("Persistence manager stopped");
 }
 
-bool PersistenceManager::recover(MemoryStore& store) {
+RecoveryOutcome PersistenceManager::recover(MemoryStore& store) {
     spdlog::info("Starting recovery...");
 
     // Store reference for potential snapshot during recovery
@@ -97,7 +97,7 @@ bool PersistenceManager::recover(MemoryStore& store) {
         // Open WAL for replay
         if (!wal_->open()) {
             spdlog::error("Failed to open WAL for recovery");
-            return false;
+            return RecoveryOutcome{.kind = RecoveryOutcome::Kind::Failed, .failureReason = "Failed to open WAL"};
         }
 
         // Replay WAL entries after snapshot
@@ -176,11 +176,11 @@ bool PersistenceManager::recover(MemoryStore& store) {
         spdlog::info("Recovery complete, store has {} documents in {} collections",
                      store.getStats().totalDocuments, store.getStats().totalCollections);
 
-        return true;
+        return RecoveryOutcome{};  // Default: TrivialSuccess
 
     } catch (const std::exception& e) {
         spdlog::error("Recovery failed: {}", e.what());
-        return false;
+        return RecoveryOutcome{.kind = RecoveryOutcome::Kind::Failed, .failureReason = e.what()};
     }
 }
 

+ 52 - 4
service/src/persistence/persistence_manager.hpp

@@ -15,6 +15,45 @@
 
 namespace smartbotic::database {
 
+enum class RecoveryMode {
+    Normal,             // level 0 — latest snapshot only, refuse on failure (default)
+    SnapshotFallback,   // level 1 — try snapshots newest→oldest
+    WalOnly,            // level 2 — ignore snapshots, replay WAL from 0
+    BestEffort,         // level 3 — snapshot_fallback then wal_only
+    ForceEmpty          // level 4 — start empty, preserve files for forensics
+};
+
+std::string recoveryModeToString(RecoveryMode mode);
+RecoveryMode recoveryModeFromString(const std::string& s);  // throws on invalid
+
+/**
+ * Describes how recovery proceeded. A "non-trivial" outcome (anything other than
+ * TrivialSuccess/FreshInstall) triggers auto-readonly mode at the service layer.
+ */
+struct RecoveryOutcome {
+    enum class Kind {
+        TrivialSuccess,      // latest snapshot loaded cleanly
+        FreshInstall,        // no snapshots, no WAL — clean state
+        SnapshotFellBack,    // non-latest snapshot loaded (auto or manual)
+        WalOnlyReplay,       // skipped all snapshots, replayed WAL from 0
+        ForcedEmpty,         // started empty despite data on disk
+        Failed               // recovery refused
+    };
+
+    Kind kind = Kind::TrivialSuccess;
+    std::filesystem::path snapshotUsed;
+    std::filesystem::path expectedSnapshot;
+    std::string failureReason;
+    uint64_t walEntriesReplayed = 0;
+    size_t snapshotsAttempted = 0;
+    size_t snapshotsAvailable = 0;
+
+    bool isNonTrivial() const {
+        return kind != Kind::TrivialSuccess && kind != Kind::FreshInstall;
+    }
+    bool isFailure() const { return kind == Kind::Failed; }
+};
+
 /**
  * Persistence manager coordinates WAL and snapshots for durable storage.
  *
@@ -33,6 +72,15 @@ public:
         uint64_t maxWalSizeMb = 100;           // Trigger snapshot at this WAL size
         bool compressionEnabled = true;
         uint32_t maxSnapshots = 5;
+
+        // NEW in v1.6.1: snapshot durability
+        bool validateAfterWrite = true;
+        bool cleanupOnlyIfVerified = true;
+
+        // NEW in v1.6.1: recovery
+        RecoveryMode recoveryMode = RecoveryMode::Normal;
+        bool autoEscalate = true;
+        bool allowEmptyOnFreshInstall = true;
     };
 
     explicit PersistenceManager(Config config);
@@ -60,11 +108,11 @@ public:
     [[nodiscard]] bool isRunning() const { return running_.load(); }
 
     /**
-     * Recover state into the memory store.
-     * Should be called before start() to load existing data.
-     * @return true if recovery succeeded (even if no data to recover)
+     * Recover state into the memory store. Should be called before start().
+     * @return RecoveryOutcome describing how recovery proceeded. Caller
+     *         inspects outcome.kind to decide whether to auto-readonly.
      */
-    bool recover(MemoryStore& store);
+    RecoveryOutcome recover(MemoryStore& store);
 
     /**
      * Log an insert operation.