This message was deleted.
# general
s
This message was deleted.
c
druid nested columns come pretty close to what you’re looking for i think https://druid.apache.org/docs/latest/querying/nested-columns.html
the missing piece might be an aggregate function e.g. something like
json_object_agg
, if you want ingestion to build the nested structures instead of just treat them like dimensions
i intend to add something like that function someday, but haven’t got to it yet
it could be modeled at query time with the native “expression” aggregator (which im not sure is documented, but used internally for some sql aggregations), though this hasn’t been wired up to work at ingestion time yet
the column itself is a bit bloated currently probably for your use case since it currently stores a ‘raw’ copy of the data in json form, but i have plans to allow customization of that as well to support cases where the raw data isn’t necessary, as well as control over which columns are indexed, and to allow exclusions, etc
i
Hi @Clint Wylie, Yes indeed very close to json type. At the beginning I thought it’s supported and tried to use json type for this map, but there are no aggregator as you mention
what is needed to implement json_object_agg ?
I might give it a try…
c
the cheapest way would probably be the expression aggregator, im in the process of updating some nested column stuff to have better array support and part of the refactoring should allow me to wire up the expression aggregator to work at ingestion time (i chose not to because we didn’t have support for array columns, but we soon will via the nested column stuff 😅 )
let me see if i can find something like docs for it
https://github.com/apache/druid/pull/11104 is the original PR, but the description is a bit stale
i haven’t tried the json functions in an expression aggregator yet, but i think the idea would be something like define an initial value with json_object expression, then use json_value to extract things from the object, do stuff to them, make a new object with combined values
i
I like your first sentence on the original PR….exactly same thoughts as I had when tried to use js aggregator
thanks a lot for the direction! I’ll try to hack something today
c
then all that is left is wiring up to ingest time, but some of the changes im doing for arrays might take care of that for you
heh, js aggregator and functions are security nightmare 😅
the refactor that will make expression aggregator able to work at ingest time is hinted at in the follow-up work on this PR, https://github.com/apache/druid/pull/13653, so hopefully i will get to it soon
🙌 1
im sort of turning nested columns into a universal column, but they will keep working like they do right now too
hence the using it for schemaless ingest and adding better array support, etc
im especially excited for array typed columns, i’ve been planning and laying foundations for this for years now heh
anyway, getting off track here, feel free to ping me if you want to try to add json_object_agg or something like it as an expression aggregator that works with nested columns
i
thanks a lot @Clint Wylie!
c
with expression based aggregator version of it in place to get basic functionality added, then we can think about optimizing it with a dedicated native aggregator if performance isn’t good enough or whatever
i
I’m trying expression based aggregator and I came to some deadend because of json_query or json_value doesn’t permit define path by variable in my case I need to move over keys (k) and use it to access json object with json_value(__acc, ‘$.‘||k) but ‘$.‘||k is not literal…so I’m getting
Copy code
Error: Unknown exception
Function[json_query] second argument [('$.' || "k")] must be a literal [STRING] value
org.apache.druid.math.expr.ExpressionValidationException
Copy code
{
  "type": "expression",
  "name": "country_id_agg",
  "fields": [
    "countryId"
  ],
  "initialValue": "json_object()",
  "fold": "json_object( map( (k) -> if( k==cast(countryId, string) , array(k, json_query(__acc, '$.'||k) + 1) , array(k,json_query(__acc, '$.'||k)) ), json_keys(__acc, '$') ) )",
  "combine": "json_object(map((k) -> json_query(__acc, '$.'||k) + json_query(country_id_agg, '$.'||k), array_set_add_all(json_keys(__acc, '$'), json_keys(country_id_agg, '$'))))"
}
also tried with arrays(combine function is not yet well defined):
Copy code
{
  "type": "expression",
  "name": "country_id_agg",
  "fields": [
    "countryId"
  ],
  "initialValue": "[]",
  "fold": "array_concat(fold( (kv1, kv2) -> json_object('k', json_value(kv1, '$.k'), 'v', json_value(kv1, '$.v') + json_value(kv2, '$.v') ),     filter((kv) -> json_value(kv, '$.k') == countryId, __acc),       json_object('k', countryId, 'v', 1)      ) ,      filter((kv) -> json_value(kv, '$.k') != countryId, __acc)      )",
  "combine": "array_concat(__acc, country_id_agg)"
}
however it fails on
Copy code
Error: Unknown exception

Cannot find strategy for type [COMPLEX<json>]

org.apache.druid.java.util.common.IAE
will try to work with arrays only
c
ah yeah, i guess i restricted to only literals for the path expressions; the
Copy code
Cannot find strategy for type [COMPLEX<json>]
message seems a bit strange, what druid version?
will have a closer look sometime today
oh, i think missing an expression to set a value of a key of an existing json object to some value that will make building json_object_agg hard
i
i started meanwhile to implement native extension(took as example FixedBucketHistogram)
c
👍
were a couple of bugs preventing array_agg working with nested types, https://github.com/apache/druid/pull/13781
👀 1
will look into if anything besides an additional native expression would be needed for expression based json_object_agg next downtime i have
implementing native stuff would likely be faster than an expression aggregator, so is still worth exploring
i
yep, I have already basic code for native aggregation, now exploring how to support sql aggregator
thanks for your time @Clint Wylie
c
look for
SqlAggregator
and how extensions wire them in
there should be a handful of examples
👍 1
FixedBucketsHistogramQuantileSqlAggregator
is the fixed bucket histogram one
i
thank you
@Clint Wylie can you explain to me please at high level what are requirements for the aggregator to support rollup as well? (maybe it’s already) I just don’t understand how it’s connected
c
the aggregator does the aggregating during rollup; native ingest with rollup is like grouping on all of the ‘dimensions’; every row in the same query granularity bucket with the same tuple of dimension values will have all of the aggregators defined in the spec to accumulate stuff
its basically the same at query time with different mechanisms
aggregators don’t have to support being ingested and stored as a column and can be strictly query time
for complex types, typically you implement both an aggregate method that can process the raw inputs to build into the complex type (often explicitly defined as a “build” aggregator) and an aggregate method that can handle merging the complex values, for combining the aggregated complex values across segments (often explicitly called “merge” aggregators)
if the aggregator supports ingestion, it can strictly use the ‘merge’ style of aggregation at query time
i
thanks it clear now
ok I have something we can talk about if you have time It’s a bit different, and solves rather specific problem, and not some general capability of aggregate all kinds of JSON complex types… https://github.com/IgorBerman/druid-complexaggs-extension/blob/main/src/test/java/[…]/complexaggs/aggregator/sql/LongFrequencySqlAggregatorTest.java basic tests work, both for native aggregator and sql operator I’d appreciate if you can clarify following : 1. how to finalize to JSON type(currently I’m using ugly json mapper in finalizer to serialize aggregated result to json-string) 2. I assume that I’m starting from regular field(currently only long) and then I count it’s occurrences. My question is - if I will enable rollup, the rollup-ed partial aggregation will be saved in intermediate format? I think I need more tests around it, I’d appreciate any pointers(I couldn’t find something that uses rollup(true) in tests)
c
will try to have a look soon, i need to think a bit about if you want to write out an actual nested column from an aggregator because that will be somewhat tricky, and will probably involve writing it out in two passes, the first to “serialize” it through an indexer to index the column values and then the 2nd to actually write out the nested column
https://github.com/apache/druid/issues/12695 has details on the inner workings of nested columns
👀 1
if you don’t need all of that stuff at query time (fast filtering, optimized value selection from the nested data, etc) then you have other options
lots of complex columns use a generic serializer that just stores a column of whatever variable sized blobs the object strategy spits out
if you want to do something like that, but want compress the blobs, you could re-use some of the parts of the nested column serializer (the
CompressedVariableSizedBlobColumnSerializer
and corresponding
CompressedVariableSizedBlobColumnSupplier
)
btw, if you do not use the nested column serializer directly, i don’t think you can call the type
COMPLEX<json>
since that would conflict with the real nested columns
anyway, i’ll try to have a look at your stuff as soon as i have some free time
oh and a bit of a heads up, the nested column stuff is under very active development, so stuff is still changing internally pretty frequently
👍 1
i
thanks @Clint Wylie for the pointers and additional info. Will try to use compressed blobs, I think I don’t need anything besides imo At basic level I need just ability to aggregate(both at ingestion and at query time), store partial aggregation and then access results of aggregation by subset of dimensions(when doing timeseries or group-by queries) Yes, I have mixed things a bit, will remove
Complex<Json>
reference.
ok, just to update I have working version (tested on 24.0.1 druid) meanwhile only counts long values serialization format is array of [key,value,key2,value2]