|
@@ -128,25 +128,43 @@ public:
|
|
|
|
|
|
|
|
bool update(const std::string& collection, const std::string& id, const nlohmann::json& data,
|
|
bool update(const std::string& collection, const std::string& id, const nlohmann::json& data,
|
|
|
const std::string& actor) {
|
|
const std::string& actor) {
|
|
|
- smartbotic::databasepb::UpdateRequest request;
|
|
|
|
|
- request.set_collection(collection);
|
|
|
|
|
- request.set_id(id);
|
|
|
|
|
- request.set_data(data.dump());
|
|
|
|
|
- if (!actor.empty()) {
|
|
|
|
|
- request.set_actor(actor);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // Strip metadata fields from data — callers pass the JSON from get() which
|
|
|
|
|
+ // includes _version, _id, etc. We use _version for optimistic locking.
|
|
|
|
|
+ auto cleanData = data;
|
|
|
|
|
+ cleanData.erase("_id");
|
|
|
|
|
+ cleanData.erase("_version");
|
|
|
|
|
+ cleanData.erase("_created_at");
|
|
|
|
|
+ cleanData.erase("_updated_at");
|
|
|
|
|
+ cleanData.erase("_created_by");
|
|
|
|
|
+ cleanData.erase("_updated_by");
|
|
|
|
|
+
|
|
|
|
|
+ // Optimistic locking with automatic retry:
|
|
|
|
|
+ // 1. Read current version
|
|
|
|
|
+ // 2. Call updateIfVersion
|
|
|
|
|
+ // 3. On version conflict (another writer), re-read and retry
|
|
|
|
|
+ for (uint32_t attempt = 0; attempt <= config_.maxRetries; ++attempt) {
|
|
|
|
|
+ // Get current version
|
|
|
|
|
+ auto current = get(collection, id);
|
|
|
|
|
+ if (!current) {
|
|
|
|
|
+ return false; // document doesn't exist
|
|
|
|
|
+ }
|
|
|
|
|
+ uint64_t version = (*current)["_version"].get<uint64_t>();
|
|
|
|
|
|
|
|
- smartbotic::databasepb::UpdateResponse response;
|
|
|
|
|
- grpc::ClientContext context;
|
|
|
|
|
- setDeadline(context);
|
|
|
|
|
|
|
+ // Attempt version-checked update
|
|
|
|
|
+ if (updateIfVersion(collection, id, cleanData, version, actor)) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- auto status = stub_->Update(&context, request, &response);
|
|
|
|
|
- if (!status.ok()) {
|
|
|
|
|
- spdlog::error("Client::update failed: {}", status.error_message());
|
|
|
|
|
- return false;
|
|
|
|
|
|
|
+ // Version conflict — another client wrote between our get() and update
|
|
|
|
|
+ if (attempt < config_.maxRetries) {
|
|
|
|
|
+ spdlog::debug("Client::update version conflict on {}/{}, retry {}/{}",
|
|
|
|
|
+ collection, id, attempt + 1, config_.maxRetries);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- return response.success();
|
|
|
|
|
|
|
+ spdlog::warn("Client::update failed after {} retries due to version conflicts on {}/{}",
|
|
|
|
|
+ config_.maxRetries, collection, id);
|
|
|
|
|
+ return false;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
bool updateIfVersion(const std::string& collection, const std::string& id,
|
|
bool updateIfVersion(const std::string& collection, const std::string& id,
|