GroupBy array based result rows
See original GitHub issueMotivation
GroupBy queries internally represent result rows as MapBasedRow
objects, which have the following two fields:
private final DateTime timestamp;
private final Map<String, Object> event;
As a result, we need to do relatively expensive Map put and get operations (typically these are HashMaps or LinkedHashMaps) at many points: when rows are first generated after each segment scan, when they are merged on historicals, when they are serialized and deserialized, and then when they are merged again on the broker.
The overhead is especially noticeable when the resultset of the groupBy query is large.
See also #6389.
Proposed changes
- Create a
ResultRow
class that simply wraps anObject[]
and allows position-based access. - Modify the GroupBy query implementation to use ResultRow throughout, rather than Row / MapBasedRow.
- Add
ObjectMapper decorateObjectMapper(ObjectMapper, QueryType)
to QueryToolChest, to aid in implementing the compatibility plan described in “Operational impact” below. QueryResource would use it so it could serialize results into either arrays or maps depending on the value ofresultAsArray
. DirectDruidClient would use it so it could deserialize results into ResultRow regardless of whether they originated as ResultRows or MapBasedRows. (By the way, the serialized form of a ResultRow would be a simple JSON array.)
Rationale
Some other potential approaches that I considered, and did not go with, include:
- Creating an ArrayBasedRow that implements
org.apache.druid.data.input.Row
(just like MapBasedRow does). The reason for avoiding this is that the interface is all about retrieving fields by name –getRaw(String dimension)
, etc – and I wanted to do positional access instead. - Using
Object[]
instead of a wrapperResultRow
around theObject[]
. It would have saved a little memory, but I thought the benefits of type-safety (it’s clear what ResultRow means when it appears in method signatures) and a nicer API would be worth it.
Operational impact
The format of data in the query cache would not change.
The wire format of groupBy results would change (this is part of the point of the change) but I plan to do this with no compatibility impact, by adding a new query context flag resultAsArray
that defaults to false. If false, Druid would use the array-based result rows for in-memory operations, but then convert them to MapBasedRows for serialization purposes, keeping the wire format compatible. If true, Druid would use array-based result rows for serialization too.
I’d have brokers always set resultAsArray
true on queries they send down to historicals. Since we tell cluster operators to update historicals first, that means that by the time the broker is updated, we can assume the historicals will know how to interpret the option. Users would also be able to set resultAsArray
if they want once brokers are updated, and receive array-based results themselves.
So, due to the above design, there should be no operational impact.
Test plan
Existing unit tests will cover a lot of this. In addition, I plan to test on live clusters, especially the compatibility stuff.
Issue Analytics
- State:
- Created 4 years ago
- Reactions:1
- Comments:5 (5 by maintainers)
The idea is the row order would be determined solely by the granularity, dimensions, aggregators, and post-aggregators, in the following way:
granularity != ALL
then the first element is a row timestamp. Otherwise, the timestamp is omitted.There wouldn’t be headers, callers would be expected to know which element is which based on the above rules.
Would potentially be a nice option for every query type though. I bet for most TopNs granularity is “all” and so the timestamp could be omitted.