|
@@ -2,6 +2,8 @@
|
|
|
|
|
|
|
|
#include "config/collection_config_manager.hpp"
|
|
#include "config/collection_config_manager.hpp"
|
|
|
#include "persistence/history_store.hpp"
|
|
#include "persistence/history_store.hpp"
|
|
|
|
|
+#include "storage/document_store.hpp"
|
|
|
|
|
+#include "storage/dual_write_mirror.hpp"
|
|
|
#include "storage/filter_eval.hpp"
|
|
#include "storage/filter_eval.hpp"
|
|
|
|
|
|
|
|
#if defined(__GLIBC__) && !defined(__APPLE__)
|
|
#if defined(__GLIBC__) && !defined(__APPLE__)
|
|
@@ -291,6 +293,9 @@ std::string MemoryStore::insert(const std::string& collection, Document doc) {
|
|
|
// Track memory increment
|
|
// Track memory increment
|
|
|
uint64_t docSize = estimateDocumentSize(doc);
|
|
uint64_t docSize = estimateDocumentSize(doc);
|
|
|
|
|
|
|
|
|
|
+ // v2.0 dual-write under lock — see header comment on mirrorWriteToDocStore.
|
|
|
|
|
+ mirrorWriteToDocStore(collection, docId, doc, EventType::INSERT);
|
|
|
|
|
+
|
|
|
lock.unlock();
|
|
lock.unlock();
|
|
|
|
|
|
|
|
// Update stats
|
|
// Update stats
|
|
@@ -448,6 +453,9 @@ bool MemoryStore::update(const std::string& collection, const std::string& id, c
|
|
|
// Track memory change (new size)
|
|
// Track memory change (new size)
|
|
|
uint64_t newSize = estimateDocumentSize(updated);
|
|
uint64_t newSize = estimateDocumentSize(updated);
|
|
|
|
|
|
|
|
|
|
+ // v2.0 dual-write under lock
|
|
|
|
|
+ mirrorWriteToDocStore(collection, id, updated, EventType::UPDATE);
|
|
|
|
|
+
|
|
|
lock.unlock();
|
|
lock.unlock();
|
|
|
|
|
|
|
|
// Update stats
|
|
// Update stats
|
|
@@ -558,6 +566,10 @@ std::string MemoryStore::upsert(const std::string& collection, Document doc) {
|
|
|
// Track new document size
|
|
// Track new document size
|
|
|
uint64_t newSize = estimateDocumentSize(doc);
|
|
uint64_t newSize = estimateDocumentSize(doc);
|
|
|
|
|
|
|
|
|
|
+ // v2.0 dual-write under lock (upsert path — INSERT or UPDATE depending on isInsert)
|
|
|
|
|
+ mirrorWriteToDocStore(collection, docId, doc,
|
|
|
|
|
+ isInsert ? EventType::INSERT : EventType::UPDATE);
|
|
|
|
|
+
|
|
|
lock.unlock();
|
|
lock.unlock();
|
|
|
|
|
|
|
|
// Update stats
|
|
// Update stats
|
|
@@ -616,6 +628,9 @@ bool MemoryStore::remove(const std::string& collection, const std::string& id) {
|
|
|
removeVector(*coll, id);
|
|
removeVector(*coll, id);
|
|
|
coll->updatedAt = currentTimeMs();
|
|
coll->updatedAt = currentTimeMs();
|
|
|
|
|
|
|
|
|
|
+ // v2.0 dual-write under lock (delete path)
|
|
|
|
|
+ mirrorWriteToDocStore(collection, id, std::nullopt, EventType::DELETE);
|
|
|
|
|
+
|
|
|
lock.unlock();
|
|
lock.unlock();
|
|
|
|
|
|
|
|
// Update stats
|
|
// Update stats
|
|
@@ -699,6 +714,9 @@ bool MemoryStore::updateIfVersion(const std::string& collection, const std::stri
|
|
|
// Track memory change (new size)
|
|
// Track memory change (new size)
|
|
|
uint64_t newSize = estimateDocumentSize(updated);
|
|
uint64_t newSize = estimateDocumentSize(updated);
|
|
|
|
|
|
|
|
|
|
+ // v2.0 dual-write under lock
|
|
|
|
|
+ mirrorWriteToDocStore(collection, id, updated, EventType::UPDATE);
|
|
|
|
|
+
|
|
|
lock.unlock();
|
|
lock.unlock();
|
|
|
|
|
|
|
|
// Update stats
|
|
// Update stats
|
|
@@ -782,6 +800,9 @@ uint64_t MemoryStore::patchDocument(const std::string& collection, const std::st
|
|
|
// Capture the updated document for callbacks after releasing the lock
|
|
// Capture the updated document for callbacks after releasing the lock
|
|
|
Document updatedDoc = it->second;
|
|
Document updatedDoc = it->second;
|
|
|
|
|
|
|
|
|
|
+ // v2.0 dual-write under lock
|
|
|
|
|
+ mirrorWriteToDocStore(collection, id, updatedDoc, EventType::UPDATE);
|
|
|
|
|
+
|
|
|
lock.unlock();
|
|
lock.unlock();
|
|
|
|
|
|
|
|
// Update stats
|
|
// Update stats
|
|
@@ -1041,6 +1062,9 @@ bool MemoryStore::setAdd(const std::string& collection, const std::string& setId
|
|
|
coll->documents[setId] = doc;
|
|
coll->documents[setId] = doc;
|
|
|
coll->updatedAt = doc.updatedAt;
|
|
coll->updatedAt = doc.updatedAt;
|
|
|
|
|
|
|
|
|
|
+ // v2.0 dual-write under lock
|
|
|
|
|
+ mirrorWriteToDocStore(collection, setId, doc, EventType::INSERT);
|
|
|
|
|
+
|
|
|
lock.unlock();
|
|
lock.unlock();
|
|
|
|
|
|
|
|
{
|
|
{
|
|
@@ -1080,6 +1104,9 @@ bool MemoryStore::setAdd(const std::string& collection, const std::string& setId
|
|
|
|
|
|
|
|
Document updated = it->second;
|
|
Document updated = it->second;
|
|
|
|
|
|
|
|
|
|
+ // v2.0 dual-write under lock (sadd-style: append member, update doc)
|
|
|
|
|
+ mirrorWriteToDocStore(collection, setId, updated, EventType::UPDATE);
|
|
|
|
|
+
|
|
|
lock.unlock();
|
|
lock.unlock();
|
|
|
|
|
|
|
|
{
|
|
{
|
|
@@ -1133,6 +1160,9 @@ bool MemoryStore::setRemove(const std::string& collection, const std::string& se
|
|
|
|
|
|
|
|
Document updated = it->second;
|
|
Document updated = it->second;
|
|
|
|
|
|
|
|
|
|
+ // v2.0 dual-write under lock (srem-style: trim members, update doc)
|
|
|
|
|
+ mirrorWriteToDocStore(collection, setId, updated, EventType::UPDATE);
|
|
|
|
|
+
|
|
|
lock.unlock();
|
|
lock.unlock();
|
|
|
|
|
|
|
|
{
|
|
{
|
|
@@ -1509,6 +1539,11 @@ uint64_t MemoryStore::expireDocuments() {
|
|
|
coll->documents.erase(docIt);
|
|
coll->documents.erase(docIt);
|
|
|
expired++;
|
|
expired++;
|
|
|
|
|
|
|
|
|
|
+ // v2.0 dual-write under lock — expire is semantically a delete
|
|
|
|
|
+ // for the substrate. Mirror it as DELETE so LMDB doesn't keep
|
|
|
|
|
+ // an orphan doc whose MemoryStore version has expired.
|
|
|
|
|
+ mirrorWriteToDocStore(collName, id, std::nullopt, EventType::DELETE);
|
|
|
|
|
+
|
|
|
// Emit callbacks (unlock first to avoid deadlock)
|
|
// Emit callbacks (unlock first to avoid deadlock)
|
|
|
collLock.unlock();
|
|
collLock.unlock();
|
|
|
|
|
|
|
@@ -1816,6 +1851,11 @@ uint64_t MemoryStore::restoreToVersion(const std::string& collection, const std:
|
|
|
|
|
|
|
|
coll->updatedAt = restoredDoc.updatedAt;
|
|
coll->updatedAt = restoredDoc.updatedAt;
|
|
|
|
|
|
|
|
|
|
+ // v2.0 dual-write under lock (restore path — INSERT if doc was previously
|
|
|
|
|
+ // deleted/missing, UPDATE if we replaced an existing doc).
|
|
|
|
|
+ mirrorWriteToDocStore(collection, id, restoredDoc,
|
|
|
|
|
+ wasDeleted ? EventType::INSERT : EventType::UPDATE);
|
|
|
|
|
+
|
|
|
lock.unlock();
|
|
lock.unlock();
|
|
|
|
|
|
|
|
// Update stats
|
|
// Update stats
|
|
@@ -2200,6 +2240,22 @@ void MemoryStore::emitPersist(const std::string& collection, const std::string&
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+void MemoryStore::setDocumentStoreMirror(smartbotic::db::storage::DocumentStore* ds,
|
|
|
|
|
+ std::atomic<bool>* healthy,
|
|
|
|
|
+ std::atomic<uint64_t>* drift) {
|
|
|
|
|
+ docStoreMirror_ = ds;
|
|
|
|
|
+ mirrorHealthy_ = healthy;
|
|
|
|
|
+ mirrorDriftCount_ = drift;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void MemoryStore::mirrorWriteToDocStore(const std::string& collection, const std::string& id,
|
|
|
|
|
+ const std::optional<Document>& doc, EventType eventType) {
|
|
|
|
|
+ if (!docStoreMirror_ || !mirrorHealthy_ || !mirrorDriftCount_) return;
|
|
|
|
|
+ smartbotic::db::storage::applyDualWriteMirror(
|
|
|
|
|
+ docStoreMirror_, *mirrorHealthy_, *mirrorDriftCount_,
|
|
|
|
|
+ collection, id, doc, eventType);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
void MemoryStore::addToExpirationIndex(CollectionData& coll, const std::string& id, uint64_t expiresAt) {
|
|
void MemoryStore::addToExpirationIndex(CollectionData& coll, const std::string& id, uint64_t expiresAt) {
|
|
|
coll.expirationIndex[expiresAt].insert(id);
|
|
coll.expirationIndex[expiresAt].insert(id);
|
|
|
}
|
|
}
|