Clpp#2275
Conversation
…ackage Docker Compose project (resolves y-scope#1747). (y-scope#1773) Co-authored-by: sitaowang1998 <sitaowang1998@outlook.com>
…ngs to server configuration (fixes y-scope#1799). (y-scope#1803) Co-authored-by: davemarco <83603688+davemarco@users.noreply.github.com>
…ation for new schema (fixes y-scope#1866). (y-scope#1867)
…efault when value is empty (fixes y-scope#1854). (y-scope#1855)
…type inference (fixes y-scope#1864). (y-scope#1865) Co-authored-by: Lin Zhihao <59785146+LinZhihao-723@users.noreply.github.com>
…uling. (y-scope#1829) Co-authored-by: Devin Gibson <gibber9809@users.noreply.github.com> Co-authored-by: Kirk Rodrigues <2454684+kirkrodrigues@users.noreply.github.com>
…e mutually exclusive in sbin search scripts (fixes y-scope#1859). (y-scope#1871)
…ist (hopfully not possible).
…ls due to parsing the next event's timestamp.
Changed create_output_handler refactor.
| m_string_type |= LiteralType::ClpStringT; | ||
| } | ||
| // if (m_v.find(' ') != std::string::npos) { | ||
| m_string_type = LiteralType::ClpStringT | LiteralType::VarStringT; |
There was a problem hiding this comment.
Making a note that we'll need to resolve this properly before merging.
I think one thing we could do is just handle column resolution in a special way for clp+ that understands that the VarString/ClpString types are interchangeable for its purposes, and also just add a new as_string(...) method to Literal.hpp that gives you a string if the underlying is either VarString or ClpString.
The main complicating factors are:
- Will have to watch out for any filtering code that uses as_var_string/as_clp_string
- The
NarrowTypespass handles some special cases for not equals queries on predicates that can be interpreted as just ClpString or just VarString. E.g.:
NOT msg<ClpString,VarString>: "varstring"->msg<ClpString> exists OR (NOT msg<VarString>: "varstring")
NOT msg<ClpString,VarString>: "clp string"->(NOT msg<ClpString>: "clp string") OR msg<VarString> exists.
Need to think harder about whether that second point matters though.
There was a problem hiding this comment.
Thought about it a bit more -- I think it might be worth adding an option to NarrowTypes to disable the transformation into separate ClpString and VarString predicates, and use that option in the clp+ path. Since clp+ currently uses NodeType::VarString for captured variables we can produce VarString column entries that contain a space, which means that the transformation isn't even necessarily correct unless we also change column resolution to be aware of that, and even if we do make column resolution account for that we lose the perf benefit we get in clp-s from this transformation, so there's really no point in doing it for clp+.
gibber9809
left a comment
There was a problem hiding this comment.
Read through the JsonParser ingestion path and left some comments. Going to make sure I at least also take a look at ArchiveWriter changes before I leave. Should be able to catch any format changes I'm worried about that way, which is the only kind of change I'm actually worried about for release-blocking.
| } else { | ||
| node_id = m_archive_writer | ||
| ->add_node(node_id_stack.top(), NodeType::VarString, cur_key); | ||
| m_current_parsed_message.add_unordered_value(value); |
There was a problem hiding this comment.
Applies to all of these blocks that check if a string contains a space before deciding how to encode it, but did we ever do the experiment where we encode all strings in clp+ using the log-mechanic path?
Only reason I'm bringing this up is that if we decide to do this later on (e.g., because handling everything with log-mechanic might make it simpler to do things like PII removal across all string fields), then we'll probably need to add a lot of special cases in the code for archive versions before and after that change.
| return std::nullopt; | ||
| } | ||
|
|
||
| if (m_retain_float_format) { |
There was a problem hiding this comment.
I think it might make sense to force users to retain float formats for floats inside of strings, since we're supposed to compress string fields losslessly.
| auto parent_node_id{get_parent_schema_node(match.value(), log_msg_node_id)}; | ||
|
|
||
| auto const rule_name{match->ffi_pointers.rule_name.as_cpp_view()}; | ||
| auto const lexeme{match->ffi_pointers.lexeme.as_cpp_view()}; | ||
|
|
||
| SchemaNode::id_t node_id{0}; | ||
| if (0 < match->encoding_idx) { | ||
| auto const matched_encodings{m_log_surgeon_parser->get_encoding(match->encoding_idx)}; | ||
| for (auto const& encoding : matched_encodings) { | ||
| if (clpp::cFloatEncodingName == encoding) { | ||
| if (auto const float_node_id{ | ||
| try_add_float_value(lexeme, parent_node_id, rule_name) | ||
| }; | ||
| float_node_id.has_value()) | ||
| { | ||
| node_id = float_node_id.value(); | ||
| break; | ||
| } | ||
| } else if (clpp::cIntEncodingName == encoding) { | ||
| if (encoded_variable_t var{0}; clp::EncodedVariableInterpreter:: | ||
| convert_string_to_representable_integer_var(lexeme, var)) | ||
| { | ||
| node_id = m_archive_writer | ||
| ->add_node(parent_node_id, NodeType::Integer, rule_name); | ||
| m_current_parsed_message.add_unordered_value(var); | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| if (0 == node_id) { | ||
| node_id = m_archive_writer->add_node(parent_node_id, NodeType::VarString, rule_name); | ||
| m_current_parsed_message.add_unordered_value(lexeme); | ||
| } | ||
| m_current_schema.insert_unordered(node_id); |
There was a problem hiding this comment.
I think I kind of follow how the schema + MPT are being handled here, but I don't 100% get it.
MPT side I'm pretty sure I understand -- we create nodes in the MPT for parent rules with a special node type, which is more or less what I would expect.
Schema side it looks like we're adding nodes to the schema for each parent rule? If I'm interpreting the code correctly, it looks like for a rule a.b.c the schema tree becomes a -> b -> c and the schema becomes node id<a>, node id<b>, node id<c>? And if c has a sibling d (as in they're both subrules of the subrule a.b, and appear as part of the same parent match as opposed to two different instances of a.b being matched) the schema becomes node id<a>, node id<b>, node id<c>, node id<a>, node id<b>, node id<d>? Not sure if I'm completely following the code though.
The main thing I don't understand is how we figure out that c and d are siblings both for decompression, and for supporting search on parent rules to make sure we turn it into leaf queries on c and d that are part of the same parent match. If we don't currently have a good way of figuring this out, I think we'll need one once we implement a version of parent search that ensures that the resulting leaf queries target leafs from the same parent match.
I think what I would have expected is for us to use start_unordered_object/end_unordered_object for each parent rule to create schemas like:
<ParentRule, 3>, <ParentRule, 2>, node_id<c>, node_id<d>
or
<ParentRule, 5>, node_id<a>, <ParentRule, 3>, node_id<b>, node_id<c>, node_id<d>
if including the parent node IDs in the schema helps make parent search simpler.
Either way though, it lets the schema record whether some arbitrary group of leaf nodes is part of the same parent match.
There was a problem hiding this comment.
You're analysis was right that the code couldn't handle it.
The recent update should resolve this by switching parent rules to use start/end_unordered_object.
It was a good point that although the syntax can't expose this limitation yet, we will benefit by having the format ready so that "old" archives are usable when we support it.
gibber9809
left a comment
There was a problem hiding this comment.
Did another round of review mostly looking through the ArchiveWriter. I think I've covered everything I'm worried about on the format side now.
| /** | ||
| * @return The root node ID of the LogType sub-tree. | ||
| * @return -1 if the sub-tree does not exist. | ||
| */ | ||
| auto get_logtype_node() -> SchemaNode::id_t { | ||
| return m_schema_tree.get_subtree_node_id(constants::cDefaultNamespace, NodeType::LogType); | ||
| } |
There was a problem hiding this comment.
Nit: not used anymore? IIRC this is an artifact of an older version of the design.
There was a problem hiding this comment.
You're right. It should be removed now.
There might be a few of these sitting around. I still need to do a thorough pass of all changes in the branch.
| ); | ||
| } | ||
|
|
||
| if (false == m_parsing_spec_str.empty()) { |
There was a problem hiding this comment.
Potential nit, is an empty string technically a legal schema? If so, might make sense to use optional for the parsing spec as well.
| } else { | ||
| log_dict_size = m_log_dict->get_data_size(); | ||
| } | ||
| return log_dict_size + m_var_dict->get_data_size() + m_array_dict->get_data_size() |
There was a problem hiding this comment.
Is that log shape stats array a meaningful size? May want to include that here as well if so.
| if (compressed_size.has_error()) { | ||
| throw OperationFailed(ErrorCodeFailure, __FILENAME__, __LINE__); | ||
| } | ||
| files.emplace_back( |
There was a problem hiding this comment.
This one is pretty important -- the order that different components are packed into the single-file archive is determined by the order of entries of this files array. That means that by doing emplace_back for this and the other new sections we're putting them at the end of the archive after the ERTs. For local search that's fine, but for search from S3 that would e.g. force us to buffer the entire archive in memory so that we can read the parsing spec before we can go back and actually do search.
Generally, we want this array to be sorted in the exact order that we would access these sections during search, so that we can do early termination when streaming the archive from S3. I think we'd probably want to move the parsing spec either before or around the schema tree/schema map, and move the parent rule shapes/log shape stats before or around the dictionaries? You have a better understanding of the requirements for clpp search, so I'll leave the exact order up to you.
| } | ||
|
|
||
| auto ArchiveReaderAdaptor::is_experimental_archive() const -> bool { | ||
| return has_section(constants::cArchiveParentRuleShapesFile); |
There was a problem hiding this comment.
This works fine for distinguishing clp-s and clpp archives, but do you think it would also make sense to use some of the unused header bytes to indicate as much, or record it in one of the metadata packets?
| log_shape.append(log_msg.substr(log_msg_pos)); | ||
|
|
||
| auto [log_shape_id, new_log_shape]{ | ||
| YSTDLIB_ERROR_HANDLING_TRYX(m_archive_writer->update_log_shape_dict(log_shape)) |
There was a problem hiding this comment.
I could be missing something, but do we have to do escaping for these log shapes? I would have assumed so, but I don't see it happening in this code. If we do, it might be nice to have a class or something to represent an escaped log shape, since it's a bit hard to audit whether escaping rules are being respected when we're building up the log shape as an std::string directly.
There was a problem hiding this comment.
You're right I just hadn't gotten to it till recently and I hadn't pushed yet. This should be addressed now.
… containing their child rules; fix decompression
Description
Checklist
breaking change.
Validation performed