Skip to content

Commit b7257fc

Browse files
committed
Optimize planning times when hypertables have many chunks
This planner optimization reduces planning times when a hypertable has many chunks. It does this by expanding hypertable chunks manually, eliding the `expand_inherited_tables` logic used by PG. Slow planning time were previously seen because `expand_inherited_tables` expands all chunks of a hypertable, without regard to constraints present in the query. Then, `get_relation_info` is the called on all chunks before constraint exclusion. Getting the statistics an many chunks ends up being expensive because RelationGetNumberOfBlocks has to open the file for each relation. This gets even worse under high concurrency. This logic solves this by expanding only the chunks needed to fulfil the query instead of all chunks. In effect, it moves chunk exclusion up in the planning process. But, we actually don't use constraint exclusion here, but rather a variant of range exclusion implemented by HypertableRestrictInfo.
1 parent c660fcd commit b7257fc

30 files changed

+1948
-399
lines changed

src/CMakeLists.txt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,12 @@ set(HEADERS
5656
hypertable_cache.h
5757
hypertable.h
5858
hypertable_insert.h
59+
hypertable_restrict_info.h
5960
indexing.h
60-
parse_rewrite.h
6161
partitioning.h
6262
planner_utils.h
63+
planner_import.h
64+
plan_expand_hypertable.h
6365
process_utility.h
6466
scanner.h
6567
subspace_store.h
@@ -93,13 +95,14 @@ set(SOURCES
9395
hypertable.c
9496
hypertable_cache.c
9597
hypertable_insert.c
98+
hypertable_restrict_info.c
9699
indexing.c
97100
init.c
98-
parse_analyze.c
99-
parse_rewrite.c
100101
partitioning.c
101102
planner.c
103+
planner_import.c
102104
planner_utils.c
105+
plan_expand_hypertable.c
103106
process_utility.c
104107
scanner.c
105108
sort_transform.c

src/chunk.c

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,7 @@ chunk_scan_ctx_init(ChunkScanCtx *ctx, Hyperspace *hs, Point *p)
621621
ctx->space = hs;
622622
ctx->point = p;
623623
ctx->early_abort = false;
624+
ctx->lockmode = NoLock;
624625
}
625626

626627
/*
@@ -762,10 +763,20 @@ chunk_is_complete(ChunkScanCtx *scanctx, Chunk *chunk)
762763
if (scanctx->space->num_dimensions != chunk->constraints->num_dimension_constraints)
763764
return false;
764765

765-
scanctx->data = chunk;
766766
return true;
767767
}
768768

769+
static bool
770+
set_complete_chunk(ChunkScanCtx *scanctx, Chunk *chunk)
771+
{
772+
if (chunk_is_complete(scanctx, chunk))
773+
{
774+
scanctx->data = chunk;
775+
return true;
776+
}
777+
return false;
778+
}
779+
769780
/* Finds the first chunk that has a complete set of constraints. There should be
770781
* only one such chunk in the scan context when scanning for the chunk that
771782
* holds a particular tuple/point. */
@@ -774,7 +785,7 @@ chunk_scan_ctx_get_chunk(ChunkScanCtx *ctx)
774785
{
775786
ctx->data = NULL;
776787

777-
chunk_scan_ctx_foreach_chunk(ctx, chunk_is_complete, 1);
788+
chunk_scan_ctx_foreach_chunk(ctx, set_complete_chunk, 1);
778789

779790
return ctx->data;
780791
}
@@ -837,6 +848,53 @@ chunk_find(Hyperspace *hs, Point *p)
837848
return chunk;
838849
}
839850

851+
static bool
852+
append_chunk_oid(ChunkScanCtx *scanctx, Chunk *chunk)
853+
{
854+
if (!chunk_is_complete(scanctx, chunk))
855+
return false;
856+
857+
/* Fill in the rest of the chunk's data from the chunk table */
858+
chunk_fill_stub(chunk, false);
859+
860+
if (scanctx->lockmode != NoLock)
861+
LockRelationOid(chunk->table_id, scanctx->lockmode);
862+
863+
scanctx->data = lappend_oid(scanctx->data, chunk->table_id);
864+
return true;
865+
}
866+
867+
List *
868+
chunk_find_all_oids(Hyperspace *hs, List *dimension_vecs, LOCKMODE lockmode)
869+
{
870+
List *oid_list = NIL;
871+
ChunkScanCtx ctx;
872+
ListCell *lc;
873+
874+
/* The scan context will keep the state accumulated during the scan */
875+
chunk_scan_ctx_init(&ctx, hs, NULL);
876+
877+
/* Do not abort the scan when one chunk is found */
878+
ctx.early_abort = false;
879+
ctx.lockmode = lockmode;
880+
881+
/* Scan all dimensions for slices enclosing the point */
882+
foreach(lc, dimension_vecs)
883+
{
884+
DimensionVec *vec = lfirst(lc);
885+
886+
dimension_slice_and_chunk_constraint_join(&ctx, vec);
887+
}
888+
889+
ctx.data = NIL;
890+
chunk_scan_ctx_foreach_chunk(&ctx, append_chunk_oid, 0);
891+
oid_list = ctx.data;
892+
893+
chunk_scan_ctx_destroy(&ctx);
894+
895+
return oid_list;
896+
}
897+
840898
Chunk *
841899
chunk_copy(Chunk *chunk)
842900
{

src/chunk.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ typedef struct ChunkScanCtx
5151
Hyperspace *space;
5252
Point *point;
5353
bool early_abort;
54+
LOCKMODE lockmode;
5455
void *data;
5556
} ChunkScanCtx;
5657

@@ -66,6 +67,7 @@ extern Chunk *chunk_create(Hypertable *ht, Point *p, const char *schema, const c
6667
extern Chunk *chunk_create_stub(int32 id, int16 num_constraints);
6768
extern void chunk_free(Chunk *chunk);
6869
extern Chunk *chunk_find(Hyperspace *hs, Point *p);
70+
extern List *chunk_find_all_oids(Hyperspace *hs, List *dimension_vecs, LOCKMODE lockmode);
6971
extern Chunk *chunk_copy(Chunk *chunk);
7072
extern Chunk *chunk_get_by_name(const char *schema_name, const char *table_name, int16 num_constraints, bool fail_if_not_found);
7173
extern Chunk *chunk_get_by_relid(Oid relid, int16 num_constraints, bool fail_if_not_found);

src/constraint_aware_append.c

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -420,11 +420,14 @@ constraint_aware_append_path_create(PlannerInfo *root, Hypertable *ht, Path *sub
420420
break;
421421
}
422422

423-
appinfo = linitial(root->append_rel_list);
424-
relid = root->simple_rte_array[appinfo->child_relid]->relid;
423+
if (list_length(root->append_rel_list) > 1)
424+
{
425+
appinfo = linitial(root->append_rel_list);
426+
relid = root->simple_rte_array[appinfo->child_relid]->relid;
425427

426-
if (relid == ht->main_table_relid)
427-
root->append_rel_list = list_delete_first(root->append_rel_list);
428+
if (relid == ht->main_table_relid)
429+
root->append_rel_list = list_delete_first(root->append_rel_list);
430+
}
428431

429432
return &path->cpath.path;
430433
}

src/dimension_slice.c

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
#include <utils/rel.h>
77
#include <catalog/indexing.h>
88
#include <funcapi.h>
9+
#include <utils/lsyscache.h>
10+
#include <catalog/pg_opfamily.h>
11+
#include <catalog/pg_type.h>
912

1013
#include "catalog.h"
1114
#include "dimension_slice.h"
@@ -166,6 +169,84 @@ dimension_slice_scan_limit(int32 dimension_id, int64 coordinate, int limit)
166169
return dimension_vec_sort(&slices);
167170
}
168171

172+
/*
173+
* Look for all ranges where value > lower_bound and value < upper_bound
174+
*
175+
*/
176+
DimensionVec *
177+
dimension_slice_scan_range_limit(int32 dimension_id, StrategyNumber start_strategy, int64 start_value, StrategyNumber end_strategy, int64 end_value, int limit)
178+
{
179+
ScanKeyData scankey[3];
180+
DimensionVec *slices = dimension_vec_create(limit > 0 ? limit : DIMENSION_VEC_DEFAULT_SIZE);
181+
int nkeys = 1;
182+
183+
/*
184+
* Perform an index scan for slices matching the dimension's ID and which
185+
* enclose the coordinate.
186+
*/
187+
ScanKeyInit(&scankey[0], Anum_dimension_slice_dimension_id_range_start_range_end_idx_dimension_id,
188+
BTEqualStrategyNumber, F_INT4EQ, Int32GetDatum(dimension_id));
189+
if (start_strategy != InvalidStrategy)
190+
{
191+
Oid opno = get_opfamily_member(INTEGER_BTREE_FAM_OID, INT8OID, INT8OID, start_strategy);
192+
Oid proc = get_opcode(opno);
193+
194+
Assert(OidIsValid(proc));
195+
196+
ScanKeyInit(&scankey[nkeys++],
197+
Anum_dimension_slice_dimension_id_range_start_range_end_idx_range_start,
198+
start_strategy,
199+
proc,
200+
Int64GetDatum(start_value));
201+
}
202+
if (end_strategy != InvalidStrategy)
203+
{
204+
Oid opno = get_opfamily_member(INTEGER_BTREE_FAM_OID, INT8OID, INT8OID, end_strategy);
205+
Oid proc = get_opcode(opno);
206+
207+
Assert(OidIsValid(proc));
208+
209+
/*
210+
* range_end is stored as exclusive, so add 1 to the value being
211+
* searched. Also avoid overflow
212+
*/
213+
if (end_value != PG_INT64_MAX)
214+
{
215+
end_value++;
216+
217+
/*
218+
* If getting as input INT64_MAX-1, need to remap the incremented
219+
* value back to INT64_MAX-1
220+
*/
221+
end_value = REMAP_LAST_COORDINATE(end_value);
222+
}
223+
else
224+
{
225+
/*
226+
* The point with INT64_MAX gets mapped to INT64_MAX-1 so
227+
* incrementing that gets you to INT_64MAX
228+
*/
229+
end_value = PG_INT64_MAX;
230+
}
231+
232+
ScanKeyInit(&scankey[nkeys++],
233+
Anum_dimension_slice_dimension_id_range_start_range_end_idx_range_end,
234+
end_strategy,
235+
proc,
236+
Int64GetDatum(end_value));
237+
}
238+
239+
dimension_slice_scan_limit_internal(DIMENSION_SLICE_DIMENSION_ID_RANGE_START_RANGE_END_IDX,
240+
scankey,
241+
nkeys,
242+
dimension_vec_tuple_found,
243+
&slices,
244+
limit,
245+
AccessShareLock);
246+
247+
return dimension_vec_sort(&slices);
248+
}
249+
169250
/*
170251
* Scan for slices that collide/overlap with the given range.
171252
*
@@ -408,13 +489,15 @@ dimension_slice_cut(DimensionSlice *to_cut, DimensionSlice *other, int64 coord)
408489
{
409490
/* Cut "before" the coordinate */
410491
to_cut->fd.range_start = other->fd.range_end;
492+
411493
return true;
412494
}
413495
else if (other->fd.range_start > coord &&
414496
other->fd.range_start < to_cut->fd.range_end)
415497
{
416498
/* Cut "after" the coordinate */
417499
to_cut->fd.range_end = other->fd.range_start;
500+
418501
return true;
419502
}
420503

src/dimension_slice.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ typedef struct DimensionVec DimensionVec;
2525
typedef struct Hypercube Hypercube;
2626

2727
extern DimensionVec *dimension_slice_scan_limit(int32 dimension_id, int64 coordinate, int limit);
28+
extern DimensionVec *dimension_slice_scan_range_limit(int32 dimension_id, StrategyNumber start_strategy, int64 start_value, StrategyNumber end_strategy, int64 end_value, int limit);
2829
extern DimensionVec *dimension_slice_collision_scan_limit(int32 dimension_id, int64 range_start, int64 range_end, int limit);
2930
extern Hypercube *dimension_slice_point_scan(Hyperspace *space, int64 point[]);
3031
extern DimensionSlice *dimension_slice_scan_for_existing(DimensionSlice *slice);

0 commit comments

Comments
 (0)