Hi team, we have a user case where we would want t...
# general
z
Hi team, we have a user case where we would want to get the latest event and the said event can just occur or occur months(or even years) ago. What would be the recommended approach to make such query performant if possible?
m
Are you referring to upsert?
z
Not upsert. But say I want to find the most recent event that has its state with value “deal_closed”
m
I think you can look at lastwithtime function
z
Thank you! This is exactly what I looked for. Curious how does it work behind the scene?
Hi Mayank, will startree index help here if I wanted the latest timestamp of the event? And the table could have multiple event types and I want to get the latest events across types
m
Last with time works right?
z
I gave it a shot but it seems not perform better. It feels it still has to scan through all matched documents.
m
That can be avoided. I am not clear why upsert is not a solution
z
Can you say more how upsert would help?
m
If you are only interested in the latest record for any primary-key, then you just use the upsert feature. it will overwrite older data for the PK as new data comes in.
z
I think I may not explain my case clearly. Say the latest e1(type = 1) occurred on 2024/01/01, and the latest e2(type = 2) occurred on 2025/01/01. And I want to get the latest timestamp for each type, so I would like the response to be:
Copy code
Type, Latest TS
1, 2024/01/01
2, 2025/01/01
I was wondering if we can build some index around
type/latest_ts
that would enable better query perf
I am thinking adding start tree index for max function on the ts
m
That still seems like upsert to me, with Type as PK
Do you care about other events of type = 1 which are older than 2024/01/01? If not, that is upsert
z
Type is not PK, event has its own id. For type T, it could have events continusouly ingested
Do you care about other events of type = 1 which are older than 2024/01/01? If not, that is upsert
yes we do. We would need other aggregations too
m
Ok, then it is not upsert
I don't think ST index can do this
z
So ST with MAX on ts col would not help?
with an additional dimension on
type
I was inspired by the idea recommended here but for another use case
m
Can you give sample query and sample data? (Few rows)
I want to fully understand your requirement
Say there is 1 metric column, 1 dim column and 1 time column. Give me a few rows, and share how will you query and expect what result
z
Let’s say I have these cols:
id
,
eventType
,
occurredAt
and have rows like:
Copy code
1, t1, 2024/01/01
2, t2, 2024/02/03
3, t1, 2025/01/01
4, t3, 2025/08/03
5, t4, 2022/01/01
I wanted to query like: show me each event type’s latest occurred ts over the past 2 year window and the expected result would be the following and I would want this query to be performant:
Copy code
t1, 2025/01/01
t2, 2024/02/03
t3, 2025/08/03
m
So you don't have any metric column to aggregate?
z
Nope in this case
or can we treat
occurredAt
as a metric column?
b/c I essentially would want the max here
m
How many total columns you have in the actual table?
And are you going to do select * on them for the latest timestamp?
z
it can have other cols like
properties
,
processedAt
or
textSearchCol
m
You can use ST index to have Max on time and split on all columns you need. But that is really not how ST index is supposed to be used.
z
And are you going to do select * on them for the latest timestamp?
for this particular case, I would not b/c I only need the latest ts
m
Yeah then you need to split on all the coluns, which if there are many, ST index won't be able to be performant anyway.
z
why would I have to split on all other cols?
m
Actually, I take that back, ST index won't be used if you use select *
It only kicks in for aggr/group-by query
So the options are window function / lastwithtime
z
What if I have something like:
Copy code
"tableIndexConfig": {
  "starTreeIndexConfigs": [{
    "dimensionsSplitOrder": [
      "eventType"
    ],
    "skipStarNodeCreationForDimensions": [
    ],
    "functionColumnPairs": [
      "MAX__occurredAt"
    ],
    "maxLeafRecords": 10000
  }],
  ...
}
m
The planner won't use ST index if you have select * or select a list of columns
z
and my query is like : select eventType, max(occurredAt) as latestTs from table where occurredAt between 20240101 and 20250803 group by eventType
m
Ok, that is not select *
That is an aggr/group-by query
z
yeah it is not a select *
m
Yes, the questions I am asking are for specific reasons, it will help if answers are very specific
ST index will be used for this query:
Copy code
select eventType, max(occurredAt) as latestTs from table where occurredAt between 20240101 and 20250803 group by eventType
z
and it would perform better than without ST, is my understanding correct? Since it would aggregate over fewer nodes
m
Wait
z
each segment would have
eventType, max(occurredAt)
node
m
You have both max() as well as filter on
occurredAs
Filtering on
occurredAs
will require you to split on it. The moment you split on it, there is no aggregation across rows
z
that is a good point
m
Yeah, this is not how ST index is typically used.
z
so then
select eventType, max(occurredAt) as latestTs from table where occurredAt between 20240101 and 20250803 group by eventType
would have to scan through all the matched rows to find out the max
m
No, you can always have range index on
occurredAt
And if you time partition, then time based pruner will also kick in
But you do need to scan data between the time range in the query
z
I were to remove the
occurredAt
filter, would it perform better with ST since it would not have time based pruner
m
Pruning happens across segment level. If you don't have any time filter, no time pruning will have for segemnts. Within segment range index will still kick in to avoid full scan of the segment
z
but wouldn’t range index be applied to the entire segment but I would need to break down by eventType in this case?
so a segment can have no row for a given type or rows for multiple types
m
you can partition on eventType yes
But it is effective if it is high cardinality
if you only have a few eventTypes, partitioning on it won't help
range index applies to the rows of a segment in order to filter out rows not in range
z
I guess I am still fuzzy how would the query plan be like. Can you help walk me through?
Copy code
select eventType, max(occurredAt) as latestTs from table where occurredAt between 20240101 and 20250803 group by eventType
for data
Copy code
1, t1, 2024/01/01
2, t2, 2024/02/03
3, t1, 2025/01/01
4, t3, 2025/08/03
5, t4, 2022/01/01
and segments are partitioned by a full date. So there would be segments for 2024/01/01, segments for
2024/02/03
. Each segment can contain rows across types
we have time pruner enabled on
occurredAt
m
First any segment that does not contain data for time in range will be pruned out just by looking at the metadata.
Second within each segment, range index will kick in to filter out rows not in range (if any)
Prune out means no data is read for the segment at all
z
Absolutely, I guess my wish was if we have seen event 4: t3, 2025/08/03, we could smartly not check other older segments and rows within those segments or maybe fewer rows/ST nodes within those segments with the help of ST
m
Segments are planned/processed independently/concurrently. Seeing an event in one segment doesn't tell us whether we will or won't see newer / older segments in others
z
yeah that is right. > maybe fewer rows/ST nodes within those segments with the help of ST and this option is not viable neither
m
Is this the actual query you will run in production or are you simplifying things?
Copy code
select eventType, max(occurredAt) as latestTs from table where occurredAt between 20240101 and 20250803 group by eventType
z
this is the essence of a production query. I added occurredAt filter because I would want to leverage time pruner to reduce segments being scanned.
m
How many eventTypes?
z
could be 10 thousands
and rows could be in billilions
it almost seems like a metadata we would need to keep somewhere else
m
Options I see: 1. Dedicated upsert table just for this query. 2. Window Function 3. LastWithTime
That is option 1
it almost seems like a metadata we would need to keep somewhere else
z
yeah now I get this idea
👍 1
I feel like I could throw them into a SQL table lol
m
Sure
z
Thank you Mayank for walking through this with me
> Filtering on
occurredAs
will require you to split on it. The moment you split on it, there is no aggregation across rows One quick question about ST index: does it mean the moment we were to filter on a time pruner enabled col, we can’t leverage ST?
this feels like we have to pick either time pruner vs ST index? I might have misunderstood your statement
m
No that is not try at all. In your case you are doing aggregating on time, most ppl aggregate on metrics
z
but let’s say I want to aggregate on a different metic col, would I still be able to put the filter on
occurredAt
and also have ST index kicked in?
and I don’t split on
occurredAt
m
You need to split on all columns you filter or group by on, including time
In your example, split on occurredAt and aggregate on another metric and ST index will be used
In your original case you are trying to split and aggregate on the same column, one disallows the other right?
z
Correct, I am now not thinking about my original case but how to use ST index in general.
The reason I was wondering is b/c my time col is in milliseconds which is high cardinality
Ah this paragraph might have explained it
Copy code
All dictionary-encoded Time/DateTime columns will be appended to the _dimensionsSplitOrder _following the dimensions, sorted by their cardinality in descending order. Here we assume that time columns will be included in most queries as the range filter column and/or the group by column, so for better performance, we always include them as the last elements in the dimensionsSplitOrder.
> In your example, split on occurredAt and aggregate on another metric and ST index will be used > In your original case you are trying to split and aggregate on the same column, one disallows the other right? would it work if I have a derived col that has the identical value as the time col?
So a config would be like:
Copy code
"tableIndexConfig": {
  "starTreeIndexConfigs": [{
    "dimensionsSplitOrder": [
      "eventType",
      "occurredAt"
    ],
    "skipStarNodeCreationForDimensions": [
    ],
    "functionColumnPairs": [
      "MAX__derivedOccurredAt"
    ],
    "maxLeafRecords": 1
  }],
  ...
}
m
Let me flip the question to you, why do you think it will work?
z
yeah b/c it is a separate col we could think of it as a different metric col I would want to aggregation on
And in the filter, I would also apply occurredAt to activate time pruner
m
Well, those two columns have same value. Splitting on one means splitting on other. So ST index will not have any rows to aggregate
z
would a range play a role here?
like a range of
occurredAt
can result in a set of rows to aggregate?
that’s my naive understanding why a time col can be included in the split dimensions
m
I recommend looking at how ST index works. That will help you understand why this doesn't work
z
I did and in
Default index generation configuration
section, it says:
Copy code
All dictionary-encoded Time/DateTime columns will be appended to the _dimensionsSplitOrder _following the dimensions, sorted by their cardinality in descending order. Here we assume that time columns will be included in most queries as the range filter column and/or the group by column, so for better performance, we always include them as the last elements in the dimensionsSplitOrder.
m
Why is ST index fast for aggregations?
z
since it would not need to scan through all rows to get the max
m
Right so it does precompute aggregations. We agree on this right?
z
yes
m
So my next question to you, how does ST index know which rows it can pre-aggregate? Or do you think it preaggregates the entire segment into 1 value?
z
nope, my understanding it would compute by range if the num of leave nodes is too large
m
Yeah that is the understand gap
It splits the data on all columns first, after splitting on those, if it gets > maxLeafRecords then it aggregates those leaf nodes.
Now if you split on a column it means all leafRecords have a single value for that column, right?
So if you split on
occurredAt
, in the leaf there will be only 1 row to aggregate
ie no pre-aggregation
And hence even if ST is used, it is completely ineffective, because it could not pre-aggregate anything
Hope this helps
z
Why would it need to get to the leafRecords to find the aggregation result for a range?
It could use the higher level nodes to get the max for a given range
m
Just because your query asked for a range doesn't mean ST index knew that range and knows the answer, right?
your query can ask any arbitrary range
z
right but if the node is within the asked range
then it could return directly, right?
m
but you split on occurredAt, there is no range
split => 1 value
z
could you help me understand what this paragraph means:
Copy code
All dictionary-encoded Time/DateTime columns will be appended to the _dimensionsSplitOrder _following the dimensions, sorted by their cardinality in descending order. Here we assume that time columns will be included in most queries as the range filter column and/or the group by column, so for better performance, we always include them as the last elements in the dimensionsSplitOrder.
it is under this section
m
I think I understand where the confusion is.
Let me ask you this: In your case are you splitting on all columns in your schema including
occurredAt
?
z
nope, just
eventType
and
occurredAt
m
I see
z
Copy code
"tableIndexConfig": {
  "starTreeIndexConfigs": [{
    "dimensionsSplitOrder": [
      "eventType",
      "occurredAt"
    ],
    "skipStarNodeCreationForDimensions": [
    ],
    "functionColumnPairs": [
      "MAX__derivedOccurredAt"
    ],
    "maxLeafRecords": 1
  }],
  ...
}
this is the config I imagine
m
My next question, for a specifc combination of
eventType
and
occurredAt
, how many rows will be there?
z
I am not sure, could be 1 or N
m
I am assuming it is 1. And if it is 1, then ST index has now rows to pre-aggregate.
If you think it can be N, can you elaborate how?
z
like multiple events fire at the same time but with different properties
m
but they will all have the same
occurredAt
value, so they are all MAX?
z
Copy code
All dictionary-encoded Time/DateTime columns will be appended to the _dimensionsSplitOrder _following the dimensions, sorted by their cardinality in descending order. Here we assume that time columns will be included in most queries as the range filter column and/or the group by column, so for better performance, we always include them as the last elements in the dimensionsSplitOrder.
I think I am still trying to grab the range filter aspect from the above paragraph
m
Let's stick to your example
Let's worry about doc later
Can you confirm there will be N rows?
z
If ST index has a single node for each row, then I agree with you it is not efficient
m
It is not effective at all (not not efficient)
z
right
m
That's all I am trying to say 😅
z
Right, that’s why I think if the default config would include a time col
then it is essentially make ST index not effective at all
m
Default example assumes metric columns are being aggregated. You are aggregating on time column
z
How would default example get aggregated if it was to split on
occurredAt
or any time col
m
You are mixing default / documentation with your requirement.
Your requirement is unique because your metric column is same as time column. This never happens
z
Can we forget my case for a moment
m
And in your case also, this is happening because you are trying to fit ST index where it doesn't belong
may be we can hop on zoom. We are not converging on slack
z
yeah will you be available next week?
m
i am available now if you are
z
yeah
one min
m
Summarizing the results: 1. In this case, splitting on
occurredAt
will by construction prevent pre-aggregation, as I mentioned above. 2. Solution suggested for this case was to create a derived column with lower time granularity (eg days) and use that in the query and split on that in the ST index. 3. This approach allows for multiple rows to be aggregated across 1 day for the same eventType, helping with performance. 4. Independently, we discussed columnValue based pruner to further optimize performance by pruning segments based on the new derived column (for which time pruner won't take effect).
z
It seems
ColumnValueSegmentPruner
is enabled by default for servers
m
Yes