|
@@ -164,6 +164,38 @@ grpc::Status DatabaseGrpcImpl::Get(
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
auto doc = store_.get(targetCollection, request->id());
|
|
auto doc = store_.get(targetCollection, request->id());
|
|
|
|
|
+
|
|
|
|
|
+ // v2.0 Stage 4 — shadow-read on Get. Same gates as Exists: only when
|
|
|
|
|
+ // the mirror is healthy + zero drift + non-system collection. Compares
|
|
|
|
|
+ // presence and core fields (id, version, data). Divergence is logged
|
|
|
|
|
+ // at WARN. MemoryStore stays authoritative.
|
|
|
|
|
+ if (!targetCollection.empty() && targetCollection[0] != '_'
|
|
|
|
|
+ && service_.mirrorHealthy() && service_.mirrorDriftCount() == 0) {
|
|
|
|
|
+ if (auto* ds = service_.docStore()) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ auto lmdb_doc = ds->get(targetCollection, request->id());
|
|
|
|
|
+ const bool mem_found = doc.has_value();
|
|
|
|
|
+ const bool lmdb_found = lmdb_doc.has_value();
|
|
|
|
|
+ if (mem_found != lmdb_found) {
|
|
|
|
|
+ spdlog::warn("v2.0 shadow-read Get presence divergence coll={} id={} "
|
|
|
|
|
+ "memstore={} lmdb={}",
|
|
|
|
|
+ targetCollection, request->id(), mem_found, lmdb_found);
|
|
|
|
|
+ } else if (mem_found && lmdb_found) {
|
|
|
|
|
+ if (doc->id != lmdb_doc->id || doc->version != lmdb_doc->version
|
|
|
|
|
+ || doc->data() != lmdb_doc->data()) {
|
|
|
|
|
+ spdlog::warn("v2.0 shadow-read Get content divergence coll={} id={} "
|
|
|
|
|
+ "mem_ver={} lmdb_ver={}",
|
|
|
|
|
+ targetCollection, request->id(),
|
|
|
|
|
+ doc->version, lmdb_doc->version);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (const std::exception& e) {
|
|
|
|
|
+ spdlog::warn("v2.0 shadow-read Get query failed coll={} id={}: {}",
|
|
|
|
|
+ targetCollection, request->id(), e.what());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
if (!doc) {
|
|
if (!doc) {
|
|
|
response->set_found(false);
|
|
response->set_found(false);
|
|
|
return grpc::Status::OK;
|
|
return grpc::Status::OK;
|