|
@@ -738,6 +738,50 @@ grpc::Status DatabaseGrpcImpl::Find(
|
|
|
|
|
|
|
|
QueryResult result = store_.find(targetCollection, query);
|
|
QueryResult result = store_.find(targetCollection, query);
|
|
|
|
|
|
|
|
|
|
+ // v2.0 Stage 4 — shadow-read on Find. Same gates as Exists/Get. We
|
|
|
|
|
+ // also skip when the result came from the WAL-fallback path (v1.x
|
|
|
|
|
+ // only; LMDB substrate has no equivalent), because that signals
|
|
|
|
|
+ // MemoryStore returned data from outside its hot set — comparing
|
|
|
|
|
+ // would produce spurious divergence. Probe compares total_matched,
|
|
|
|
|
+ // has_more, document count, and per-doc id+version (capped at first
|
|
|
|
|
+ // 32 docs to keep shadow cost bounded for large pages).
|
|
|
|
|
+ if (!targetCollection.empty() && targetCollection[0] != '_'
|
|
|
|
|
+ && !result.usedWalFallback
|
|
|
|
|
+ && service_.mirrorHealthy() && service_.mirrorDriftCount() == 0) {
|
|
|
|
|
+ if (auto* ds = service_.docStore()) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ auto lmdb_scan = ds->scan(targetCollection, query);
|
|
|
|
|
+ if (lmdb_scan.total_matched != result.totalCount
|
|
|
|
|
+ || lmdb_scan.has_more != result.hasMore
|
|
|
|
|
+ || lmdb_scan.documents.size() != result.documents.size()) {
|
|
|
|
|
+ spdlog::warn("v2.0 shadow-read Find shape divergence coll={} "
|
|
|
|
|
+ "mem_total={} lmdb_total={} mem_count={} lmdb_count={} "
|
|
|
|
|
+ "mem_more={} lmdb_more={}",
|
|
|
|
|
+ targetCollection,
|
|
|
|
|
+ result.totalCount, lmdb_scan.total_matched,
|
|
|
|
|
+ result.documents.size(), lmdb_scan.documents.size(),
|
|
|
|
|
+ result.hasMore, lmdb_scan.has_more);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ const size_t cap = std::min<size_t>(result.documents.size(), 32);
|
|
|
|
|
+ for (size_t i = 0; i < cap; ++i) {
|
|
|
|
|
+ const auto& m = result.documents[i];
|
|
|
|
|
+ const auto& l = lmdb_scan.documents[i];
|
|
|
|
|
+ if (m.id != l.id || m.version != l.version) {
|
|
|
|
|
+ spdlog::warn("v2.0 shadow-read Find doc divergence coll={} pos={} "
|
|
|
|
|
+ "mem_id={} mem_ver={} lmdb_id={} lmdb_ver={}",
|
|
|
|
|
+ targetCollection, i,
|
|
|
|
|
+ m.id, m.version, l.id, l.version);
|
|
|
|
|
+ break; // one log per query is enough signal
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (const std::exception& e) {
|
|
|
|
|
+ spdlog::warn("v2.0 shadow-read Find query failed coll={}: {}",
|
|
|
|
|
+ targetCollection, e.what());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
response->set_total_count(result.totalCount);
|
|
response->set_total_count(result.totalCount);
|
|
|
response->set_has_more(result.hasMore);
|
|
response->set_has_more(result.hasMore);
|
|
|
|
|
|