|
|
@@ -638,14 +638,48 @@ QueryResult MemoryStore::find(const std::string& collection, const Query& query)
|
|
|
const_cast<MemoryStore*>(this)->stats_.queryCount++;
|
|
|
}
|
|
|
|
|
|
- // Collect matching documents
|
|
|
+ // Phase 1: Search in-memory documents
|
|
|
+ auto memoryStartTime = std::chrono::steady_clock::now();
|
|
|
std::vector<Document> matches;
|
|
|
+ uint32_t memoryMatchCount = 0;
|
|
|
+
|
|
|
for (const auto& [id, doc] : coll->documents) {
|
|
|
if (doc.isExpired()) {
|
|
|
continue;
|
|
|
}
|
|
|
if (matchesFilters(doc, query.filters)) {
|
|
|
matches.push_back(doc);
|
|
|
+ memoryMatchCount++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ auto memoryElapsed = std::chrono::duration_cast<std::chrono::microseconds>(
|
|
|
+ std::chrono::steady_clock::now() - memoryStartTime).count();
|
|
|
+
|
|
|
+ // Phase 2: Search evicted documents from WAL (only if there are any)
|
|
|
+ uint32_t walMatchCount = 0;
|
|
|
+ uint64_t walElapsed = 0;
|
|
|
+ bool usedWalFallback = false;
|
|
|
+
|
|
|
+ if (hasEvictedDocuments(collection)) {
|
|
|
+ usedWalFallback = true;
|
|
|
+ auto walStartTime = std::chrono::steady_clock::now();
|
|
|
+
|
|
|
+ auto evictedIds = getEvictedDocumentIds(collection);
|
|
|
+ for (const auto& id : evictedIds) {
|
|
|
+ auto doc = peekEvictedDocument(collection, id);
|
|
|
+ if (doc && !doc->isExpired() && matchesFilters(*doc, query.filters)) {
|
|
|
+ matches.push_back(*doc);
|
|
|
+ walMatchCount++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ walElapsed = std::chrono::duration_cast<std::chrono::microseconds>(
|
|
|
+ std::chrono::steady_clock::now() - walStartTime).count();
|
|
|
+
|
|
|
+ if (walMatchCount > 0) {
|
|
|
+ spdlog::debug("WAL fallback for collection '{}': {} evicted docs checked, {} matched in {} µs",
|
|
|
+ collection, evictedIds.size(), walMatchCount, walElapsed);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -720,6 +754,13 @@ QueryResult MemoryStore::find(const std::string& collection, const Query& query)
|
|
|
|
|
|
result.hasMore = end < matches.size();
|
|
|
|
|
|
+ // Record WAL fallback metrics
|
|
|
+ result.usedWalFallback = usedWalFallback;
|
|
|
+ result.memoryMatchCount = memoryMatchCount;
|
|
|
+ result.walMatchCount = walMatchCount;
|
|
|
+ result.memorySearchMicros = memoryElapsed;
|
|
|
+ result.walSearchMicros = walElapsed;
|
|
|
+
|
|
|
// Record timing
|
|
|
auto elapsed = std::chrono::duration_cast<std::chrono::microseconds>(
|
|
|
std::chrono::steady_clock::now() - startTime).count();
|
|
|
@@ -2059,4 +2100,56 @@ std::optional<MemoryStore::EvictedDocumentStub> MemoryStore::getEvictedStub(
|
|
|
return stubIt->second;
|
|
|
}
|
|
|
|
|
|
+bool MemoryStore::hasEvictedDocuments(const std::string& collection) const {
|
|
|
+ std::shared_lock<std::shared_mutex> lock(evictionMutex_);
|
|
|
+ auto collIt = evictedDocs_.find(collection);
|
|
|
+ if (collIt == evictedDocs_.end()) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return !collIt->second.empty();
|
|
|
+}
|
|
|
+
|
|
|
+std::vector<std::string> MemoryStore::getEvictedDocumentIds(const std::string& collection) const {
|
|
|
+ std::shared_lock<std::shared_mutex> lock(evictionMutex_);
|
|
|
+ std::vector<std::string> ids;
|
|
|
+ auto collIt = evictedDocs_.find(collection);
|
|
|
+ if (collIt != evictedDocs_.end()) {
|
|
|
+ ids.reserve(collIt->second.size());
|
|
|
+ for (const auto& [id, stub] : collIt->second) {
|
|
|
+ ids.push_back(id);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return ids;
|
|
|
+}
|
|
|
+
|
|
|
+std::optional<Document> MemoryStore::peekEvictedDocument(
|
|
|
+ const std::string& collection, const std::string& id) const {
|
|
|
+ EvictedDocumentStub stub;
|
|
|
+
|
|
|
+ // Get stub
|
|
|
+ {
|
|
|
+ std::shared_lock<std::shared_mutex> lock(evictionMutex_);
|
|
|
+ auto collIt = evictedDocs_.find(collection);
|
|
|
+ if (collIt == evictedDocs_.end()) {
|
|
|
+ return std::nullopt;
|
|
|
+ }
|
|
|
+ auto stubIt = collIt->second.find(id);
|
|
|
+ if (stubIt == collIt->second.end()) {
|
|
|
+ return std::nullopt;
|
|
|
+ }
|
|
|
+ stub = stubIt->second;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Load from persistence via callback (without re-inserting to memory)
|
|
|
+ std::optional<Document> doc;
|
|
|
+ {
|
|
|
+ std::lock_guard<std::mutex> lock(callbackMutex_);
|
|
|
+ if (documentLoadCallback_) {
|
|
|
+ doc = documentLoadCallback_(collection, id, stub.walSequence);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return doc;
|
|
|
+}
|
|
|
+
|
|
|
} // namespace smartbotic::database
|