Skip to content

Commit fac2e5c

Browse files
committed
Cache sort pathkeys per hypertable
The transformed ORDER BY pathkeys only depend on the query and the hypertable, not on the individual chunk. Computing them once per chunk made planning time grow with the square of the chunk count. Cache the result on the parent hypertable and reuse it for every chunk, so planning grows linearly with the chunk count instead. Warm planning time for SELECT * FROM t ORDER BY time (Release, PG18): chunks before after speedup 1000 44 ms 23 ms 1.9x 5000 735 ms 143 ms 5.2x 10000 2707 ms 287 ms 9.4x
1 parent af78205 commit fac2e5c

5 files changed

Lines changed: 54 additions & 6 deletions

File tree

.unreleased/pr_10213

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixes: #10213 Cache sort pathkeys per hypertable

src/planner/planner.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1295,7 +1295,7 @@ apply_optimizations(PlannerInfo *root, TsRelType reltype, RelOptInfo *rel, Range
12951295
* Since the sort optimization adds new paths to the rel it has
12961296
* to happen before any optimizations that replace pathlist.
12971297
*/
1298-
List *transformed_query_pathkeys = ts_sort_transform_get_pathkeys(root, rel, rte, ht);
1298+
List *transformed_query_pathkeys = ts_sort_transform_get_pathkeys(root, rel);
12991299
if (transformed_query_pathkeys != NIL)
13001300
{
13011301
List *orig_query_pathkeys = root->query_pathkeys;

src/planner/planner.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ typedef struct TimescaleDBPrivate
4040

4141
/* Cached equivalence members for compressed chunks. List of (EC, EM) Lists. */
4242
List *compressed_ec_em_pairs;
43+
44+
/* Cached transformed pathkeys */
45+
List *transformed_sort_pathkeys;
46+
bool transformed_sort_pathkeys_valid;
4347
} TimescaleDBPrivate;
4448

4549
extern TSDLLEXPORT bool ts_rte_is_hypertable(const RangeTblEntry *rte);

src/sort_transform.c

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "func_cache.h"
2121
#include "hypertable.h"
2222
#include "import/allpaths.h"
23+
#include "planner/planner.h"
2324
#include "sort_transform.h"
2425

2526
/* This optimizations allows GROUP BY clauses that transform time in
@@ -439,9 +440,8 @@ sort_transform_ec(PlannerInfo *root, EquivalenceClass *orig, Relids child_relids
439440
* For example: an ORDER BY date_trunc('minute', time) can be implemented by
440441
* an ordering of time.
441442
*/
442-
List *
443-
ts_sort_transform_get_pathkeys(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte,
444-
Hypertable *ht)
443+
static List *
444+
sort_transform_compute_pathkeys(PlannerInfo *root, RelOptInfo *rel)
445445
{
446446
/*
447447
* We attack this problem in three steps:
@@ -552,6 +552,50 @@ ts_sort_transform_get_pathkeys(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry
552552
return transformed_query_pathkeys;
553553
}
554554

555+
/*
556+
* The transformed pathkeys are a function of the query's ORDER BY and the
557+
* hypertable, not of the individual chunk (they reference query-global
558+
* equivalence classes; the per-chunk expressions live in the eclass's child
559+
* members). Computing them re-walks the whole ORDER BY eclass, whose membership
560+
* grows with the chunk count, so doing it once per chunk is O(chunks^2). Cache
561+
* the result on the parent hypertable rel and reuse it for every chunk.
562+
*/
563+
List *
564+
ts_sort_transform_get_pathkeys(PlannerInfo *root, RelOptInfo *rel)
565+
{
566+
RelOptInfo *parent = NULL;
567+
568+
if (rel->reloptkind == RELOPT_OTHER_MEMBER_REL)
569+
{
570+
AppendRelInfo *appinfo = root->append_rel_array[rel->relid];
571+
RelOptInfo *candidate = root->simple_rel_array[appinfo->parent_relid];
572+
573+
/*
574+
* A standalone chunk pulled up from a subquery (UNION ALL or a
575+
* flattened LATERAL subquery) is also an OTHER_MEMBER_REL, but its
576+
* append parent is not a hypertable.
577+
*/
578+
if (candidate->fdw_private)
579+
{
580+
parent = candidate;
581+
}
582+
}
583+
584+
/* Standalone chunk (no parent to cache on). */
585+
if (!parent)
586+
{
587+
return sort_transform_compute_pathkeys(root, rel);
588+
}
589+
590+
TimescaleDBPrivate *pp = ts_get_private_reloptinfo(parent);
591+
if (!pp->transformed_sort_pathkeys_valid)
592+
{
593+
pp->transformed_sort_pathkeys = sort_transform_compute_pathkeys(root, rel);
594+
pp->transformed_sort_pathkeys_valid = true;
595+
}
596+
return pp->transformed_sort_pathkeys;
597+
}
598+
555599
/*
556600
* After we have created new paths with transformed pathkeys, replace them back
557601
* with the original pathkeys.

src/sort_transform.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@
1212

1313
extern Expr *ts_sort_transform_expr(Expr *expr);
1414

15-
extern List *ts_sort_transform_get_pathkeys(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte,
16-
Hypertable *ht);
15+
extern List *ts_sort_transform_get_pathkeys(PlannerInfo *root, RelOptInfo *rel);
1716

1817
extern void ts_sort_transform_replace_pathkeys(void *node, List *transformed_pathkeys,
1918
List *original_pathkeys);

0 commit comments

Comments
 (0)