|
|
@@ -1,5 +1,6 @@
|
|
|
#include "database_grpc_impl.hpp"
|
|
|
#include "database_service.hpp"
|
|
|
+#include "storage/document_store.hpp"
|
|
|
#include "json_parse.hpp"
|
|
|
#include "persistence/wal.hpp"
|
|
|
#include "views/projection.hpp"
|
|
|
@@ -451,6 +452,29 @@ grpc::Status DatabaseGrpcImpl::Exists(
|
|
|
pb::ExistsResponse* response
|
|
|
) {
|
|
|
bool exists = store_.exists(request->collection(), request->id());
|
|
|
+
|
|
|
+ // v2.0 Stage 4 — shadow-read: when the mirror is healthy and has zero
|
|
|
+ // drift, also ask doc_store_ and log any divergence. MemoryStore is
|
|
|
+ // still authoritative; this generates the evidence we need before the
|
|
|
+ // next sub-task hard-flips reads onto doc_store_. System collections
|
|
|
+ // are skipped because they aren't mirrored (Stage 5 owns their format).
|
|
|
+ if (!request->collection().empty() && request->collection()[0] != '_'
|
|
|
+ && service_.mirrorHealthy() && service_.mirrorDriftCount() == 0) {
|
|
|
+ if (auto* ds = service_.docStore()) {
|
|
|
+ try {
|
|
|
+ const bool lmdb_exists = ds->exists(request->collection(), request->id());
|
|
|
+ if (lmdb_exists != exists) {
|
|
|
+ spdlog::warn("v2.0 shadow-read Exists divergence coll={} id={} "
|
|
|
+ "memstore={} lmdb={}",
|
|
|
+ request->collection(), request->id(), exists, lmdb_exists);
|
|
|
+ }
|
|
|
+ } catch (const std::exception& e) {
|
|
|
+ spdlog::warn("v2.0 shadow-read Exists query failed coll={} id={}: {}",
|
|
|
+ request->collection(), request->id(), e.what());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
response->set_exists(exists);
|
|
|
return grpc::Status::OK;
|
|
|
}
|