瀏覽代碼

feat(memory): MemoryPressure state machine (v1.7.0 T3)

Four levels driven by estimatedMemoryBytes_ / maxMemoryBytes:
  NORMAL    — below soft threshold
  SOFT      — soft ≤ usage < hard — will trigger trickle eviction (T4)
  HARD      — hard ≤ usage < emergency — aggressive evict (T4) + event (T10)
  EMERGENCY — usage ≥ emergency — admission control rejects writes (T7)

MemoryStore exposes pressure() and pressurePercent() for other
subsystems. Periodic eviction-loop log now shows the current pressure
level. No behavior change yet — T4 wires the state machine into the
eviction trigger; T7 into admission control.
fszontagh 3 月之前
父節點
當前提交
a6b55f2df2
共有 2 個文件被更改,包括 65 次插入2 次删除
  1. 33 2
      service/src/memory_store.cpp
  2. 32 0
      service/src/memory_store.hpp

+ 33 - 2
service/src/memory_store.cpp

@@ -20,6 +20,16 @@
 
 namespace smartbotic::database {
 
+const char* memoryPressureToString(MemoryPressure p) {
+    switch (p) {
+        case MemoryPressure::Normal:    return "normal";
+        case MemoryPressure::Soft:      return "soft";
+        case MemoryPressure::Hard:      return "hard";
+        case MemoryPressure::Emergency: return "emergency";
+    }
+    return "normal";
+}
+
 MemoryStore::MemoryStore(Config config)
     : config_(std::move(config)) {
 }
@@ -1501,6 +1511,26 @@ MemoryStore::Stats MemoryStore::getStats() const {
     return stats;
 }
 
+MemoryPressure MemoryStore::pressure() const {
+    uint64_t used = estimatedMemoryBytes_.load(std::memory_order_relaxed);
+    uint64_t max = config_.maxMemoryBytes;
+    if (max == 0) return MemoryPressure::Normal;  // unbounded — always normal
+
+    uint32_t percent = static_cast<uint32_t>(std::min<uint64_t>(100, used * 100 / max));
+
+    if (percent >= config_.memoryEmergencyPercent) return MemoryPressure::Emergency;
+    if (percent >= config_.memoryHardPercent)      return MemoryPressure::Hard;
+    if (percent >= config_.memorySoftPercent)      return MemoryPressure::Soft;
+    return MemoryPressure::Normal;
+}
+
+uint32_t MemoryStore::pressurePercent() const {
+    uint64_t used = estimatedMemoryBytes_.load(std::memory_order_relaxed);
+    uint64_t max = config_.maxMemoryBytes;
+    if (max == 0) return 0;
+    return static_cast<uint32_t>(std::min<uint64_t>(100, used * 100 / max));
+}
+
 // ===== Version History =====
 
 void MemoryStore::saveToHistory(CollectionData& coll, const Document& currentDoc) {
@@ -2343,9 +2373,10 @@ void MemoryStore::evictionLoop() {
 
         // Log memory status every 12 checks (~60 seconds with 5s interval)
         if (checkCount % 12 == 1) {
-            spdlog::info("Memory check: {} MB estimated, {} MB threshold, {} docs, {} evicted",
+            spdlog::info("Memory check: {} MB estimated ({}%, pressure={}), {} docs, {} evicted",
                         stats.estimatedMemoryBytes / (1024 * 1024),
-                        threshold / (1024 * 1024),
+                        pressurePercent(),
+                        memoryPressureToString(pressure()),
                         stats.totalDocuments,
                         stats.evictedCount);
         }

+ 32 - 0
service/src/memory_store.hpp

@@ -18,6 +18,21 @@
 
 namespace smartbotic::database {
 
+/**
+ * Memory pressure level, computed from estimatedMemoryBytes_ / maxMemoryBytes.
+ */
+enum class MemoryPressure {
+    Normal = 0,     // < soft% — no action
+    Soft = 1,       // soft% ≤ usage < hard% — trickle evict
+    Hard = 2,       // hard% ≤ usage < emergency% — aggressive evict + pressure event
+    Emergency = 3   // usage ≥ emergency% — refuse writes (admission control)
+};
+
+/**
+ * Convert memory pressure to human-readable string.
+ */
+const char* memoryPressureToString(MemoryPressure p);
+
 // Forward declaration — full include lives in memory_store.cpp to avoid
 // circular dependency (CollectionConfigManager itself holds a MemoryStore&).
 class CollectionConfigManager;
@@ -361,6 +376,19 @@ public:
      */
     [[nodiscard]] const Config& getConfig() const { return config_; }
 
+    /**
+     * Current memory pressure level, computed from estimated bytes vs max.
+     * Cheap: reads two atomics + does three comparisons.
+     * Called from the eviction loop, admission control, and event fanout.
+     */
+    [[nodiscard]] MemoryPressure pressure() const;
+
+    /**
+     * Current memory usage as a percentage of max (0-100).
+     * Convenience accessor for stats/observability.
+     */
+    [[nodiscard]] uint32_t pressurePercent() const;
+
     // ===== LRU Eviction =====
 
     /**
@@ -615,6 +643,10 @@ private:
     // Incremental memory tracking - avoids expensive recalculation in getStats()
     std::atomic<uint64_t> estimatedMemoryBytes_{0};
 
+    // Track last observed pressure for edge-triggered event fanout (T10)
+    // mutable because pressure() is const but may update this as a side effect
+    mutable std::atomic<MemoryPressure> lastObservedPressure_{MemoryPressure::Normal};
+
     // ===== LRU Eviction =====
 
     // Evicted documents: collection -> docId -> stub