Add DeferredChunkScan custom scan - #10292
Conversation
|
@Poroma-Banerjee, @natalya-aksman: please review this pull request.
|
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
| /* | ||
| * Cap the batch at HS_FETCH_BATCH_SIZE: the per-chunk query already carries | ||
| * the LIMIT, and the fetch loop refills across batches, so a large pushed | ||
| * LIMIT must not turn into one oversized cur_tuples allocation. | ||
| */ |
There was a problem hiding this comment.
Is this comment written by a chatbot?
| * are no more chunks. | ||
| */ | ||
| static bool | ||
| open_next_chunk(HypertableScanState *state) |
There was a problem hiding this comment.
Have you considered SPI? Just curious what are pros/cons.
There was a problem hiding this comment.
Initial version did use SPI but there where some problems with nested SPI and cursor mode. plpgsql uses spi internally leading to nesting.
There was a problem hiding this comment.
Makes sense, connect/finish must be strictly nested. Maybe you could run them inside "exec" of this node and the named cursor could persist across this, but I'm not sure how all of this works and whether it will be performant enough.
| #include "ts_catalog/catalog.h" | ||
| #include "utils.h" | ||
|
|
||
| /* Rows fetched per PortalRunFetch when there is no pushed LIMIT to bound it. */ |
There was a problem hiding this comment.
Do we generally want to run this node when there's no LIMIT? I'd expect it to do no better than a normal plan in this case.
There was a problem hiding this comment.
If you have to visit all the chunks, planning the chunks upfront will come out on top, might still be beneficial if you favor fast first tuple. For initial version we want to require LIMIT.
| List *querytree = pg_analyze_and_rewrite_fixedparams(raw, sql, NULL, 0, NULL); | ||
| Query *query = linitial_node(Query, querytree); | ||
| PlannedStmt *plan = | ||
| pg_plan_query(query, sql, CURSOR_OPT_FAST_PLAN | CURSOR_OPT_NO_SCROLL, NULL); |
There was a problem hiding this comment.
The "fast plan" parameter means we only expect to return 10% of tuples from the cursor. It seems that in this case it's not true and we actually expect to return all of them? Given the LIMIT that is already in the per-chunk query.
There was a problem hiding this comment.
This is to favor smaller startup cost
There was a problem hiding this comment.
You have LIMIT in the query for that. This flag additionally reduces the LIMIT ten times. There'a a justification given for cursor, but why is this correct to use here?
| } | ||
|
|
||
| /* | ||
| * In unordered mode we scan chunks in creation_time DESC order, with id DESC as a |
There was a problem hiding this comment.
Why do we order by creation time for unordered mode? If we don't care about order, it would be easier to return them in descending chunk id order and not have to handle any ties. If we do care about it, let's add some explanation.
| MemoryContextAlloc(state->chunk_mcxt, sizeof(HeapTuple) * state->batch_size); | ||
| state->cur_nrows = 0; | ||
| state->cur_row = 0; | ||
| PortalRunFetch(state->cur_portal, FETCH_FORWARD, state->batch_size, state->dest); |
There was a problem hiding this comment.
In this node we only need to return one tuple at a time, so there's no need to use tuple store to materialize intermediate results. Consider using PortalRunSelect returning one row at a time.
| bool descending; | ||
| Oid sortop; /* sort operator, for EXPLAIN */ | ||
| bool nulls_first; /* NULLS FIRST, for EXPLAIN */ | ||
| int push_limit; |
There was a problem hiding this comment.
If I understand the logic correctly, the LIMIT clause is fully handled by the above Limit node, so this node doesn't do any limit tracking itself, and just keeps returning at most LIMIT + OFFSET rows from each chunk in sequence? Would be good to explain this in the comments.
| HS_PRIV_DESCENDING, /* 1 if the ordered sort is descending */ | ||
| HS_PRIV_NULLS_FIRST, /* 1 if the ordered sort is NULLS FIRST */ | ||
| HS_PRIV_COUNT | ||
| } HypertableScanPrivIndex; |
There was a problem hiding this comment.
PrivIndex reads like an index into custom_private itself, consider renaming.
akuzm
left a comment
There was a problem hiding this comment.
I left some comments here and we discussed this on a call, overall I think we have a good idea of what the initial implementation should look like.
1b68765 to
e49575b
Compare
Add a new custom scan node for LIMIT queries that leaves the hypertable unexpanded during initial planning and defers chunk enumeration and scanning to execution time. This gives huge speedup to LIMIT queries on hypertables with many chunks. Warm planning time (ms), median of 15, ORDER BY ts LIMIT 1 ┌────────────────────────────┬───────┬───────┬───────┬───────┬───────┬───────┬────────┐ │ config │ 1 │ 10 │ 50 │ 100 │ 1,000 │ 5,000 │ 10,000 │ ├────────────────────────────┼───────┼───────┼───────┼───────┼───────┼───────┼────────┤ │ uncompr, expanded │ 0.010 │ 0.037 │ 0.173 │ 0.372 │ 4.39 │ 35.9 │ 84.3 │ ├────────────────────────────┼───────┼───────┼───────┼───────┼───────┼───────┼────────┤ │ uncompr, DeferredChunkScan │ 0.003 │ 0.003 │ 0.003 │ 0.002 │ 0.002 │ 0.002 │ 0.002 │ ├────────────────────────────┼───────┼───────┼───────┼───────┼───────┼───────┼────────┤ │ compr, expanded │ 0.019 │ 0.136 │ 0.636 │ 1.361 │ 27.1 │ 169.9 │ 363.1 │ ├────────────────────────────┼───────┼───────┼───────┼───────┼───────┼───────┼────────┤ │ compr, DeferredChunkScan │ 0.003 │ 0.003 │ 0.003 │ 0.002 │ 0.002 │ 0.003 │ 0.003 │ └────────────────────────────┴───────┴───────┴───────┴───────┴───────┴───────┴────────┘ Warm execution time (ms), median of 15, ORDER BY ts LIMIT 1 ┌────────────────────────────┬───────┬───────┬───────┬───────┬────────┬───────┬────────┐ │ config │ 1 │ 10 │ 50 │ 100 │ 1,000 │ 5,000 │ 10,000 │ ├────────────────────────────┼───────┼───────┼───────┼───────┼────────┼───────┼────────┤ │ uncompr, expanded │ 0.002 │ 0.005 │ 0.015 │ 0.093 │ 0.88 │ 9.97 │ 23.6 │ ├────────────────────────────┼───────┼───────┼───────┼───────┼────────┼───────┼────────┤ │ uncompr, DeferredChunkScan │ 0.021 │ 0.022 │ 0.018 │ 0.017 │ 0.018 │ 0.017 │ 0.018 │ ├────────────────────────────┼───────┼───────┼───────┼───────┼────────┼───────┼────────┤ │ compr, expanded │ 0.004 │ 0.015 │ 0.143 │ 0.301 │ 6.33 │ 42.4 │ 92.3 │ ├────────────────────────────┼───────┼───────┼───────┼───────┼────────┼───────┼────────┤ │ compr, DeferredChunkScan │ 0.037 │ 0.037 │ 0.029 │ 0.029 │ 6.1e-2 │ 0.030 │ 0.031 │ └────────────────────────────┴───────┴───────┴───────┴───────┴────────┴───────┴────────┘
e49575b to
069a94d
Compare
Add a new custom scan node for LIMIT queries that leaves the
hypertable unexpanded during initial planning and defers chunk
enumeration and scanning to execution time.
This gives huge speedup to LIMIT queries on hypertables with many chunks.