-
Notifications
You must be signed in to change notification settings - Fork 92
feat(clp-s)!: Deduplicate cached search results using compound MongoDB IDs. #2509
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 8 commits
767feb1
3b4ad65
2b5deba
d22e01a
cc8303a
7912f44
500bb2c
3143118
4d0b9fb
49fe7e2
e7734fb
19d477b
96538e2
3b38b90
2b6d0e1
b39bf3a
0740535
84e1074
3165049
ee8c1aa
6f214b4
b450ab9
09ad0bc
ee064c8
fd7e107
35b8a10
34342c6
bcad695
084c94f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -160,12 +160,19 @@ async def read_results(self, query_id: str) -> list[dict]: | |
| collection = self._results_cache[str(query_id)] | ||
| results = [] | ||
|
|
||
| async for doc in collection.find({}, limit=SEARCH_MAX_NUM_RESULTS): | ||
| async for raw_doc in collection.find({}, limit=SEARCH_MAX_NUM_RESULTS): | ||
| doc = dict(raw_doc) | ||
| result_id = doc["_id"] | ||
| if isinstance(result_id, dict): | ||
| doc["archive_id"] = result_id["archive_id"] | ||
| doc["log_event_idx"] = result_id["log_event_idx"] | ||
| else: | ||
| doc["log_event_idx"] = doc.pop("log_event_ix") | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure if it's worth providing backward compatibility, considering this is a breaking change that will not provide backward compatibility in other components in the package.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That said, this PR is a breaking change. We should update the PR title accordingly to reflect that. |
||
| doc["link"] = ( | ||
| f"{self._webui_addr}/streamFile?type=json" | ||
| f"&streamId={doc['archive_id']}" | ||
| f"&dataset={CLP_DEFAULT_DATASET_NAME}" | ||
| f"&logEventIdx={doc['log_event_ix']}" | ||
| f"&logEventIdx={doc['log_event_idx']}" | ||
| ) | ||
| doc["_id"] = None | ||
| results.append(doc) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,16 +1,20 @@ | ||
| #include "OutputHandlerImpl.hpp" | ||
|
|
||
| #include <algorithm> | ||
| #include <sstream> | ||
| #include <string> | ||
| #include <string_view> | ||
| #include <vector> | ||
|
|
||
| #include <bsoncxx/builder/basic/document.hpp> | ||
| #include <bsoncxx/builder/basic/kvp.hpp> | ||
| #include <bsoncxx/types.hpp> | ||
| #include <mongocxx/client.hpp> | ||
| #include <mongocxx/collection.hpp> | ||
| #include <mongocxx/exception/bulk_write_exception.hpp> | ||
| #include <mongocxx/exception/exception.hpp> | ||
| #include <mongocxx/instance.hpp> | ||
| #include <mongocxx/options/insert.hpp> | ||
| #include <msgpack.hpp> | ||
| #include <spdlog/spdlog.h> | ||
|
|
||
|
|
@@ -28,6 +32,117 @@ using std::string; | |
| using std::string_view; | ||
|
|
||
| namespace clp_s { | ||
| namespace { | ||
| constexpr int32_t cDuplicateKeyErrorCode{11'000}; | ||
|
|
||
| /** | ||
| * Checks whether a bulk-write reply reports a successful command. | ||
| * @param reply The raw MongoDB bulk-write reply. | ||
| * @return true if the reply contains no command error, false otherwise. | ||
| */ | ||
| [[nodiscard]] auto is_successful_command_reply(bsoncxx::document::view const& reply) -> bool { | ||
|
sitaowang1998 marked this conversation as resolved.
Outdated
|
||
| if (static_cast<bool>(reply["code"]) || static_cast<bool>(reply["errmsg"])) { | ||
| return false; | ||
| } | ||
|
|
||
| auto const command_status = reply["ok"]; | ||
| if (false == static_cast<bool>(command_status)) { | ||
| return true; | ||
|
sitaowang1998 marked this conversation as resolved.
Outdated
|
||
| } | ||
| if (bsoncxx::type::k_double == command_status.type()) { | ||
| return 1.0 == command_status.get_double().value; | ||
| } | ||
| if (bsoncxx::type::k_int32 == command_status.type()) { | ||
| return 1 == command_status.get_int32().value; | ||
| } | ||
| if (bsoncxx::type::k_int64 == command_status.type()) { | ||
| return 1 == command_status.get_int64().value; | ||
| } | ||
|
sitaowang1998 marked this conversation as resolved.
Outdated
|
||
| return false; | ||
| } | ||
|
|
||
| /** | ||
| * Checks whether a bulk-write reply contains any write-concern errors. | ||
| * @param reply The raw MongoDB bulk-write reply. | ||
| * @return true if the reply contains a write-concern error, false otherwise. | ||
| */ | ||
| [[nodiscard]] auto has_write_concern_errors(bsoncxx::document::view const& reply) -> bool { | ||
| if (static_cast<bool>(reply["writeConcernError"])) { | ||
| return true; | ||
| } | ||
|
|
||
| auto const errors_element = reply["writeConcernErrors"]; | ||
| if (false == static_cast<bool>(errors_element)) { | ||
| return false; | ||
| } | ||
| if (bsoncxx::type::k_array != errors_element.type()) { | ||
| return true; | ||
| } | ||
| auto const errors = errors_element.get_array().value; | ||
| return errors.begin() != errors.end(); | ||
| } | ||
|
|
||
| /** | ||
| * Checks whether an entry from a bulk-write reply's `writeErrors` array is a duplicate-key error. | ||
| * @param write_error The write-error entry to inspect. | ||
| * @return true if the entry has MongoDB's duplicate-key error code, false otherwise. | ||
| */ | ||
| [[nodiscard]] auto is_duplicate_key_write_error(bsoncxx::array::element const& write_error) | ||
| -> bool { | ||
| if (bsoncxx::type::k_document != write_error.type()) { | ||
| return false; | ||
| } | ||
|
|
||
| auto const code = write_error.get_document().value["code"]; | ||
| if (false == static_cast<bool>(code)) { | ||
| return false; | ||
| } | ||
| if (bsoncxx::type::k_int32 == code.type()) { | ||
| return cDuplicateKeyErrorCode == code.get_int32().value; | ||
| } | ||
| if (bsoncxx::type::k_int64 == code.type()) { | ||
| return cDuplicateKeyErrorCode == code.get_int64().value; | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| /** | ||
| * Returns whether the bulk write failed only because some documents already exist. | ||
| * | ||
| * Command and write-concern errors are rejected since they mean MongoDB did not confirm the | ||
| * outcome of the entire batch. At least one write error must be present, and every write error | ||
| * must be a duplicate-key error. | ||
| * @param exception The exception containing the raw MongoDB bulk-write reply. | ||
| * @return true if the reply contains only duplicate-key write errors, false otherwise. | ||
| */ | ||
| [[nodiscard]] auto contains_only_duplicate_key_write_errors( | ||
| mongocxx::bulk_write_exception const& exception | ||
| ) -> bool { | ||
| auto const& raw_server_error = exception.raw_server_error(); | ||
| if (false == raw_server_error.has_value()) { | ||
| return false; | ||
| } | ||
|
|
||
| auto const reply = raw_server_error->view(); | ||
| if (false == is_successful_command_reply(reply) || has_write_concern_errors(reply)) { | ||
| return false; | ||
| } | ||
|
|
||
| auto const write_errors_element = reply["writeErrors"]; | ||
| if (false == static_cast<bool>(write_errors_element) | ||
| || bsoncxx::type::k_array != write_errors_element.type()) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| auto const write_errors = write_errors_element.get_array().value; | ||
| if (write_errors.begin() == write_errors.end()) { | ||
| return false; | ||
| } | ||
| return std::all_of(write_errors.begin(), write_errors.end(), is_duplicate_key_write_error); | ||
| } | ||
| } // namespace | ||
|
|
||
| void FileOutputHandler::write( | ||
| string_view message, | ||
| epochtime_t timestamp, | ||
|
|
@@ -96,6 +211,21 @@ ErrorCode ResultsCacheOutputHandler::finish() { | |
| m_results.emplace_back( | ||
| std::move( | ||
| bsoncxx::builder::basic::make_document( | ||
| bsoncxx::builder::basic::kvp( | ||
| constants::results_cache::search::cId, | ||
| bsoncxx::builder::basic::make_document( | ||
| bsoncxx::builder::basic::kvp( | ||
| constants::results_cache::search:: | ||
| cArchiveId, | ||
| result.archive_id | ||
|
sitaowang1998 marked this conversation as resolved.
Outdated
|
||
| ), | ||
| bsoncxx::builder::basic::kvp( | ||
| constants::results_cache::search:: | ||
| cLogEventIdx, | ||
| result.log_event_idx | ||
| ) | ||
| ) | ||
| ), | ||
| bsoncxx::builder::basic::kvp( | ||
| constants::results_cache::search::cOrigFilePath, | ||
| std::move(result.original_path) | ||
|
|
@@ -108,14 +238,6 @@ ErrorCode ResultsCacheOutputHandler::finish() { | |
| constants::results_cache::search::cTimestamp, | ||
| result.timestamp | ||
| ), | ||
| bsoncxx::builder::basic::kvp( | ||
| constants::results_cache::search::cArchiveId, | ||
| std::move(result.archive_id) | ||
| ), | ||
| bsoncxx::builder::basic::kvp( | ||
| constants::results_cache::search::cLogEventIx, | ||
| result.log_event_idx | ||
| ), | ||
| bsoncxx::builder::basic::kvp( | ||
| std::string{constants::results_cache::search::cDataset}, | ||
| std::move(result.dataset) | ||
|
|
@@ -126,21 +248,25 @@ ErrorCode ResultsCacheOutputHandler::finish() { | |
| count++; | ||
|
|
||
| if (count == m_batch_size) { | ||
| m_collection.insert_many(m_results); | ||
| m_results.clear(); | ||
| if (false == insert_results()) { | ||
| return ErrorCode::ErrorCodeFailureDbBulkWrite; | ||
| } | ||
| count = 0; | ||
| } | ||
| } catch (mongocxx::exception const& e) { | ||
| SPDLOG_ERROR("Failed to build or insert search results - {}", e.what()); | ||
| return ErrorCode::ErrorCodeFailureDbBulkWrite; | ||
| } | ||
| } | ||
|
sitaowang1998 marked this conversation as resolved.
Outdated
|
||
|
|
||
| try { | ||
| if (false == m_results.empty()) { | ||
| m_collection.insert_many(m_results); | ||
| m_results.clear(); | ||
| if (false == insert_results()) { | ||
| return ErrorCode::ErrorCodeFailureDbBulkWrite; | ||
| } | ||
| } | ||
| } catch (mongocxx::exception const& e) { | ||
| SPDLOG_ERROR("Failed to insert final search-results batch - {}", e.what()); | ||
| return ErrorCode::ErrorCodeFailureDbBulkWrite; | ||
| } | ||
|
sitaowang1998 marked this conversation as resolved.
Outdated
|
||
| return ErrorCode::ErrorCodeSuccess; | ||
|
|
@@ -178,6 +304,24 @@ void ResultsCacheOutputHandler::write( | |
| } | ||
| } | ||
|
|
||
| auto ResultsCacheOutputHandler::insert_results() -> bool { | ||
| try { | ||
| mongocxx::options::insert options; | ||
| options.ordered(false); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just to make sure my own understanding is correct, this is what allows us to reliably insert all records in the batch even if some of them have duplicate key errors, right?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes.
sitaowang1998 marked this conversation as resolved.
Outdated
|
||
| m_collection.insert_many(m_results, options); | ||
| } catch (mongocxx::bulk_write_exception const& exception) { | ||
| if (false == contains_only_duplicate_key_write_errors(exception)) { | ||
| SPDLOG_ERROR("Failed to insert search results - {}", exception.what()); | ||
| return false; | ||
| } | ||
| } catch (mongocxx::exception const& exception) { | ||
| SPDLOG_ERROR("Failed to insert search results - {}", exception.what()); | ||
| return false; | ||
| } | ||
| m_results.clear(); | ||
| return true; | ||
| } | ||
|
|
||
| CountReducerOutputHandler::CountReducerOutputHandler(int reducer_socket_fd) | ||
| : search::OutputHandler(false, false), | ||
| m_reducer_socket_fd(reducer_socket_fd), | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -200,6 +200,13 @@ class ResultsCacheOutputHandler : public ::clp_s::search::OutputHandler { | |
| void write(std::string_view message) override { write(message, 0, {}, 0); } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With the current implementation, we should probably stop supporting this method. This method is never enabled in the current |
||
|
|
||
| private: | ||
| /** | ||
| * Inserts the pending results as an unordered batch. Duplicate-key errors are treated as | ||
| * success so that retries converge on the complete result set. | ||
| * @return true on success or duplicate-key-only errors, false otherwise. | ||
| */ | ||
| [[nodiscard]] auto insert_results() -> bool; | ||
|
|
||
| mongocxx::client m_client; | ||
| mongocxx::collection m_collection; | ||
| std::vector<bsoncxx::document::value> m_results; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -53,8 +53,9 @@ constexpr char cIsLastChunk[]{"is_last_chunk"}; | |
| } // namespace results_cache::decompression | ||
|
|
||
| namespace results_cache::search { | ||
| constexpr char cId[]{"_id"}; | ||
| constexpr char cOrigFilePath[]{"orig_file_path"}; | ||
| constexpr char cLogEventIx[]{"log_event_ix"}; | ||
| constexpr char cLogEventIdx[]{"log_event_idx"}; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think there are a few more places in the codebase that still use
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's outside the scope of this PR. |
||
| constexpr char cTimestamp[]{"timestamp"}; | ||
| constexpr char cMessage[]{"message"}; | ||
| constexpr char cArchiveId[]{"archive_id"}; | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.