ESQL: Reuse Block Reader only when few fields #141672
Conversation
Stops ESQL from reusing the `BlockLoad.ColumnAtATimeReader`s when loading many of these fields at once. Attempting to reuse these readers means we have to keep all of them in memory. If we don't reuse we can release the memory for each field as we load each `Block`'s. When you load hundreds of blocks this really adds up. Important: This only works for column-at-a-time readers. Mostly, the row-by-row readers don't take much space anyway.
|
Pinging @elastic/es-analytical-engine (Team:Analytics) |
|
I'm still looking into heap attack tests. I might need the follow-up work to really hit - but chekcing. |
nik9000
left a comment
There was a problem hiding this comment.
OK. A few heap attack tests pass now.
Next I'll see if I can get ColumnAtATimeReader working the FromMany reader.
martijnvg
left a comment
There was a problem hiding this comment.
Thanks Nik! I think this looks good.
Looks like some unit test failures for test expecting block loader but now get io supplier. This can be fixed by resolving the supplier and then checking to block loader.
| s.storedFieldsSequentialProportion() | ||
| ) | ||
| ); | ||
| boolean reuseColumnLoaders = fieldExtractExec.attributesToExtract().size() <= context.plannerSettings() |
There was a problem hiding this comment.
This includes fields used for sorting, grouping, sorting etc? I think if sorting does gets pushed down, then those fields aren't part of attributes to extract. Similar to when WHERE gets pushed down. In the push down case, we aren't being slowed down here.
Maybe for other things we could have exceptions. Like for TS command for _tsid and @timestamp. Not in this PR and maybe in a follow up.
There was a problem hiding this comment.
This is used for almost all loading ESQL has to do - sorts, groups, aggs, returning the column. All of it.
I'm not sure we really need exceptions though. This'll only come up if you need more than 30 fields at a time. Mostly this'll come up for stuff like FROM foo | LIMIT 10. Aggs rarely touch 30 fields at a time. But you can make them do it - testAggManyFieldsNoReuse does that. But it's kind of a lot.
If you use time series stuff to get the last value of like 50 fields. Or the rate of that many fields. Then this'll kick in. Probably. I don't know all of the bits y'all have.
| * the paths that need very high performance don't load more than a handful of fields at a time, | ||
| * so they <strong>do</strong> reuse fields. | ||
| */ | ||
| public static final Setting<Integer> REUSE_COLUMN_LOADERS_THRESHOLD = Setting.intSetting( |
👍 |
We were growing more and more and more options to `OperatorTestCase.runDriver`. I need another option in #141672 so I built a builder-style test utility. This removes the original methods, migrating all callers. I've been quite liberal adding utility methods. Those are cheap in a builder-style helper because you don't have to think in terms of combinatorial explosions of parameter - just in terms of "how do I set up all the bits". Now there are ten ways to set the inputs. It's tempting to make some higher level utility methods that call these - or make the common call sites shorter. You init the most common helper like: ``` new TestDriverRunner().builder(driverContext()) ``` But I didn't want it to look like magic. Readers should see this and think, "I can add things before `builder`" and "I can add things to this `builder`."
Stops ESQL from reusing the `BlockLoad.ColumnAtATimeReader`s when loading many of these fields at once. Attempting to reuse these readers means we have to keep all of them in memory. If we don't reuse we can release the memory for each field as we load each `Block`'s. When you load hundreds of blocks this really adds up. Important: This only works for column-at-a-time readers. Mostly, the row-by-row readers don't take much space anyway.
We were growing more and more and more options to `OperatorTestCase.runDriver`. I need another option in elastic#141672 so I built a builder-style test utility. This removes the original methods, migrating all callers. I've been quite liberal adding utility methods. Those are cheap in a builder-style helper because you don't have to think in terms of combinatorial explosions of parameter - just in terms of "how do I set up all the bits". Now there are ten ways to set the inputs. It's tempting to make some higher level utility methods that call these - or make the common call sites shorter. You init the most common helper like: ``` new TestDriverRunner().builder(driverContext()) ``` But I didn't want it to look like magic. Readers should see this and think, "I can add things before `builder`" and "I can add things to this `builder`."
Stops ESQL from reusing the `BlockLoad.ColumnAtATimeReader`s when loading many of these fields at once. Attempting to reuse these readers means we have to keep all of them in memory. If we don't reuse we can release the memory for each field as we load each `Block`'s. When you load hundreds of blocks this really adds up. Important: This only works for column-at-a-time readers. Mostly, the row-by-row readers don't take much space anyway.
We were growing more and more and more options to `OperatorTestCase.runDriver`. I need another option in elastic#141672 so I built a builder-style test utility. This removes the original methods, migrating all callers. I've been quite liberal adding utility methods. Those are cheap in a builder-style helper because you don't have to think in terms of combinatorial explosions of parameter - just in terms of "how do I set up all the bits". Now there are ten ways to set the inputs. It's tempting to make some higher level utility methods that call these - or make the common call sites shorter. You init the most common helper like: ``` new TestDriverRunner().builder(driverContext()) ``` But I didn't want it to look like magic. Readers should see this and think, "I can add things before `builder`" and "I can add things to this `builder`."
We were growing more and more and more options to `OperatorTestCase.runDriver`. I need another option in elastic#141672 so I built a builder-style test utility. This removes the original methods, migrating all callers. I've been quite liberal adding utility methods. Those are cheap in a builder-style helper because you don't have to think in terms of combinatorial explosions of parameter - just in terms of "how do I set up all the bits". Now there are ten ways to set the inputs. It's tempting to make some higher level utility methods that call these - or make the common call sites shorter. You init the most common helper like: ``` new TestDriverRunner().builder(driverContext()) ``` But I didn't want it to look like magic. Readers should see this and think, "I can add things before `builder`" and "I can add things to this `builder`."
ESQL: Load many fields column-at-a-time Adds support for `ColumnAtATimeReader` in the case where we're loading from many segments. This should marginally speed up loading many documents after a top n. More importantly, it lets #141672 kick in when loading from many fields. This should save significantly memory when loading thousands of fields after a `| SORT | LIMIT` sequence. Finally, this changes the rules for `BlockLoader`. Previously you *could* return `null` from `columnAtATimeReader` but must never return `null` from `rowStrideReader`. Now the rule is that you may return null from *either* of the two, but not both. This should let us delete a bunch of code. While we're at it, we should add a `read(builder, docs, offset, nullsFiltered)` override to save a copy.
Stops ESQL from reusing the
BlockLoad.ColumnAtATimeReaders whenloading many of these fields at once. Attempting to reuse these readers
means we have to keep all of them in memory. If we don't reuse we can
release the memory for each field as we load each
Block's. When youload hundreds of blocks this really adds up.
Important: This only works for column-at-a-time readers. This waits for a follow-up change. For truly row-by-row readers, this is fine. They don't use much memory anyway. But we use row-by-row readers as a fallback for reading doc values when loading from many segments. That seems important. Usually if the query wants to load hundreds of fields, it's after a topn. And usually those are "from many segments".