From c482323feabae476f92accb696dc1efc5628d589 Mon Sep 17 00:00:00 2001 From: David Rowley Date: Wed, 14 Jul 2021 00:34:18 +1200 Subject: [PATCH v11 1/3] WIP: Cache last used partition in PartitionDesc --- src/backend/executor/execPartition.c | 195 ++++++++++++++++++++++++++- src/backend/partitioning/partdesc.c | 6 + src/include/partitioning/partdesc.h | 11 ++ 3 files changed, 211 insertions(+), 1 deletion(-) diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c index e03ea27299..aca08791e9 100644 --- a/src/backend/executor/execPartition.c +++ b/src/backend/executor/execPartition.c @@ -176,6 +176,8 @@ static void FormPartitionKeyDatum(PartitionDispatch pd, EState *estate, Datum *values, bool *isnull); +static int get_partition_for_tuple_using_cache(PartitionDispatch pd, + Datum *values, bool *isnull); static int get_partition_for_tuple(PartitionDispatch pd, Datum *values, bool *isnull); static char *ExecBuildSlotPartitionKeyDescription(Relation rel, @@ -318,7 +320,7 @@ ExecFindPartition(ModifyTableState *mtstate, * these values, error out. */ if (partdesc->nparts == 0 || - (partidx = get_partition_for_tuple(dispatch, values, isnull)) < 0) + (partidx = get_partition_for_tuple_using_cache(dispatch, values, isnull)) < 0) { char *val_desc; @@ -1332,6 +1334,191 @@ FormPartitionKeyDatum(PartitionDispatch pd, elog(ERROR, "wrong number of partition key expressions"); } +/* + * find_last_partition_for_tuple + * Checks if 'values' and 'isnull' matches the last found partition and + * returns the partition index of that partition or -1 if the given + * values don't belong to the last found partition. + * + * Note: If calculating the correct partition is just as cheap as checking if + * these values belong to the last partition, here we just calculate the + * correct partition for the given values. This is the case for HASH + * partitioning and for LIST partitioning with a NULL value. + */ +static inline int +find_last_partition_for_tuple(PartitionDispatch pd, PartitionDesc partdesc, + Datum *values, bool *isnull) +{ + PartitionKey key; + PartitionBoundInfo boundinfo; + + /* No last partition? No match then. */ + if (partdesc->last_found_part_index == -1) + return -1; + + key = pd->key; + boundinfo = partdesc->boundinfo; + + switch (key->strategy) + { + case PARTITION_STRATEGY_HASH: + { + uint64 rowHash; + + rowHash = compute_partition_hash_value(key->partnatts, + key->partsupfunc, + key->partcollation, + values, isnull); + + /* Just calculate the correct partition and return it */ + return boundinfo->indexes[rowHash % boundinfo->nindexes]; + } + + case PARTITION_STRATEGY_LIST: + if (isnull[0]) + { + /* Just return the NULL partition, if there is one */ + return boundinfo->null_index; + } + else + { + int last_datum_offset = partdesc->last_found_datum_index; + Datum lastDatum = boundinfo->datums[last_datum_offset][0]; + int32 cmpval; + + cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0], + key->partcollation[0], + lastDatum, + values[0])); + + if (cmpval == 0) + return boundinfo->indexes[last_datum_offset]; + break; + } + + case PARTITION_STRATEGY_RANGE: + { + int last_datum_offset = partdesc->last_found_datum_index; + Datum *lastDatums = boundinfo->datums[last_datum_offset]; + PartitionRangeDatumKind *kind = boundinfo->kind[last_datum_offset]; + int32 cmpval; + + /* Check for NULLs and abort the cache check if we find any */ + for (int i = 0; i < key->partnatts; i++) + { + if (isnull[i]) + return -1; + } + + /* Check if the value is equal to the lower bound */ + cmpval = partition_rbound_datum_cmp(key->partsupfunc, + key->partcollation, + lastDatums, + kind, + values, + key->partnatts); + + if (cmpval == 0) + return boundinfo->indexes[last_datum_offset + 1]; + + else if (cmpval < 0 && last_datum_offset + 1 < boundinfo->ndatums) + { + /* Check if the value is below the upper bound */ + lastDatums = boundinfo->datums[last_datum_offset + 1]; + kind = boundinfo->kind[last_datum_offset + 1]; + cmpval = partition_rbound_datum_cmp(key->partsupfunc, + key->partcollation, + lastDatums, + kind, + values, + key->partnatts); + + if (cmpval > 0) + return boundinfo->indexes[last_datum_offset + 1]; + } + break; + } + + default: + elog(ERROR, "unexpected partition strategy: %d", + (int) key->strategy); + } + + return -1; +} + +/* + * The number of times the same partition must be found in a row before we + * switch from a search for the given values to just checking if the values + * belong to the last found partition. + */ +#define PARTITION_CACHED_FIND_THRESHOLD 16 + +/* + * get_partition_for_tuple_using_cache + * As get_partition_for_tuple, but use caching logic and check if the + * given 'values' and 'isnull' array also belong to the last found + * partition. If it does then this can save an expensive binary search + * for LIST and RANGE partitioning. + */ +static int +get_partition_for_tuple_using_cache(PartitionDispatch pd, Datum *values, + bool *isnull) +{ + PartitionDesc partdesc = pd->partdesc; + int lastpart; + + /* + * When we've found that the same partition matches + * PARTITION_CACHED_FIND_THRESHOLD times in a row, instead of doing a + * partition search, we just check if the last partition found will also + * accept these values. If it does then that'll save us from searching + * for the correct partition. + */ + + /* Have we found the same partition enough times to use the cache? */ + if (partdesc->last_found_count >= PARTITION_CACHED_FIND_THRESHOLD) + { + /* check if these values also belong to the last found partition */ + lastpart = find_last_partition_for_tuple(pd, partdesc, values, isnull); + + if (lastpart == -1) + { + /* + * The last partition did not match. We must fall back on a + * search for the correct partition without the cache. + */ + lastpart = get_partition_for_tuple(pd, values, isnull); + partdesc->last_found_count = 1; + return lastpart; + } + else + { + /* no point in advancing last_found_count any further */ + return lastpart; + } + } + else + { + int thispart; + + /* + * We've not met the threshold for caching yet. Just perform a search. + * get_partition_for_tuple will stash the last_found_part_index. + */ + lastpart = partdesc->last_found_part_index; + thispart = get_partition_for_tuple(pd, values, isnull); + + /* adjust the count accordingly if the partition matched or not */ + if (thispart == lastpart) + partdesc->last_found_count++; + else + partdesc->last_found_count = 1; + + return thispart; + } +} + /* * get_partition_for_tuple * Finds partition of relation which accepts the partition key specified @@ -1380,7 +1567,10 @@ get_partition_for_tuple(PartitionDispatch pd, Datum *values, bool *isnull) boundinfo, values[0], &equal); if (bound_offset >= 0 && equal) + { part_index = boundinfo->indexes[bound_offset]; + partdesc->last_found_datum_index = bound_offset; + } } break; @@ -1419,6 +1609,7 @@ get_partition_for_tuple(PartitionDispatch pd, Datum *values, bool *isnull) * actually exists one. */ part_index = boundinfo->indexes[bound_offset + 1]; + partdesc->last_found_datum_index = bound_offset; } } break; @@ -1435,6 +1626,8 @@ get_partition_for_tuple(PartitionDispatch pd, Datum *values, bool *isnull) if (part_index < 0) part_index = boundinfo->default_index; + partdesc->last_found_part_index = part_index; + return part_index; } diff --git a/src/backend/partitioning/partdesc.c b/src/backend/partitioning/partdesc.c index 8b6e0bd595..737f0edd89 100644 --- a/src/backend/partitioning/partdesc.c +++ b/src/backend/partitioning/partdesc.c @@ -290,6 +290,12 @@ RelationBuildPartitionDesc(Relation rel, bool omit_detached) { oldcxt = MemoryContextSwitchTo(new_pdcxt); partdesc->boundinfo = partition_bounds_copy(boundinfo, key); + + /* Initialize caching fields for speeding up ExecFindPartition */ + partdesc->last_found_datum_index = -1; + partdesc->last_found_part_index = -1; + partdesc->last_found_count = 0; + partdesc->oids = (Oid *) palloc(nparts * sizeof(Oid)); partdesc->is_leaf = (bool *) palloc(nparts * sizeof(bool)); diff --git a/src/include/partitioning/partdesc.h b/src/include/partitioning/partdesc.h index ae1afe3d78..7121ac7f7a 100644 --- a/src/include/partitioning/partdesc.h +++ b/src/include/partitioning/partdesc.h @@ -36,6 +36,17 @@ typedef struct PartitionDescData * the corresponding 'oids' element belongs to * a leaf partition or not */ PartitionBoundInfo boundinfo; /* collection of partition bounds */ + int last_found_datum_index; /* Index into the owning + * PartitionBoundInfo's datum array + * for the last found partition */ + int last_found_part_index; /* Partition index of the last found + * partition or -1 if none have been + * found yet or if we've failed to + * find one */ + int last_found_count; /* Number of times in a row have we found + * values to match the partition + * referenced in the last_found_part_index + * field */ } PartitionDescData; -- 2.35.1.windows.2