From b8bbfc8d0bd67bcb6b062c7504ec637c24d99073 Mon Sep 17 00:00:00 2001 From: Viktor Kurilko Date: Mon, 25 Dec 2023 16:44:58 +0700 Subject: [PATCH] Add guc diskquota.max_reject_entries that control size of local_disk_quota_reject_map. The size of disk_quota_reject_map is calculated as the maximum size of the local table multiplied by the maximum number of databases. The variables that store the time of the last report on reaching the limit are now stored in shared memory to prevent spam from dynamic workers. --- src/diskquota.c | 3 + src/diskquota_utility.c | 2 +- src/quotamodel.c | 75 ++++++++++------ tests/regress/diskquota_schedule | 1 + .../regress/expected/test_rejectmap_limit.out | 87 +++++++++++++++++++ tests/regress/sql/test_rejectmap_limit.sql | 56 ++++++++++++ 6 files changed, 197 insertions(+), 27 deletions(-) create mode 100644 tests/regress/expected/test_rejectmap_limit.out create mode 100644 tests/regress/sql/test_rejectmap_limit.sql diff --git a/src/diskquota.c b/src/diskquota.c index 725894a9..cc25b70a 100644 --- a/src/diskquota.c +++ b/src/diskquota.c @@ -78,6 +78,7 @@ int diskquota_max_workers = 10; int diskquota_max_table_segments = 0; int diskquota_max_monitored_databases = 0; int diskquota_max_quota_probes = 0; +int diskquota_max_local_reject_entries = 0; int diskquota_hashmap_overflow_report_timeout = 0; DiskQuotaLocks diskquota_locks; @@ -409,6 +410,8 @@ define_guc_variables(void) DefineCustomIntVariable("diskquota.max_quota_probes", "Max number of quotas on the cluster.", NULL, &diskquota_max_quota_probes, 1024 * 1024, 1024 * INIT_QUOTA_MAP_ENTRIES, INT_MAX, PGC_POSTMASTER, 0, NULL, NULL, NULL); + DefineCustomIntVariable("diskquota.max_reject_entries", "Max number of reject entries per database.", NULL, + &diskquota_max_local_reject_entries, 8192, 1, INT_MAX, PGC_POSTMASTER, 0, NULL, NULL, NULL); DefineCustomIntVariable("diskquota.hashmap_overflow_report_timeout", "The duration between each warning report about the shared hashmap overflow (in seconds).", NULL, &diskquota_hashmap_overflow_report_timeout, 60, 0, INT_MAX / 1000, PGC_SUSET, 0, NULL, diff --git a/src/diskquota_utility.c b/src/diskquota_utility.c index d576f54a..f8929b84 100644 --- a/src/diskquota_utility.c +++ b/src/diskquota_utility.c @@ -1702,7 +1702,7 @@ check_hash_fullness(HTAB *hashp, int max_size, const char *warning_message, Time if (*last_overflow_report == 0 || TimestampDifferenceExceeds(*last_overflow_report, current_time, diskquota_hashmap_overflow_report_timeout * 1000)) { - ereport(WARNING, (errmsg(warning_message))); + ereport(WARNING, (errmsg("%s", warning_message))); *last_overflow_report = current_time; } } diff --git a/src/quotamodel.c b/src/quotamodel.c index 67676a0b..a87c26a2 100644 --- a/src/quotamodel.c +++ b/src/quotamodel.c @@ -45,11 +45,7 @@ #include /* cluster level max size of rejectmap */ -#define MAX_DISK_QUOTA_REJECT_ENTRIES (1024 * 1024) -/* cluster level init size of rejectmap */ -#define INIT_DISK_QUOTA_REJECT_ENTRIES 8192 -/* per database level max size of rejectmap */ -#define MAX_LOCAL_DISK_QUOTA_REJECT_ENTRIES 8192 +#define MAX_DISK_QUOTA_REJECT_ENTRIES (diskquota_max_local_reject_entries * diskquota_max_monitored_databases) /* Number of attributes in quota configuration records. */ #define NUM_QUOTA_CONFIG_ATTRS 6 /* Number of entries for diskquota.table_size update SQL */ @@ -87,7 +83,7 @@ int SEGCOUNT = 0; extern int diskquota_max_table_segments; extern int diskquota_max_monitored_databases; extern int diskquota_max_quota_probes; - +extern int diskquota_max_local_reject_entries; /* * local cache of table disk size and corresponding schema and owner. * @@ -142,8 +138,8 @@ typedef enum uint16 quota_key_num[NUM_QUOTA_TYPES] = {1, 1, 2, 2, 1}; Oid quota_key_caches[NUM_QUOTA_TYPES][MAX_NUM_KEYS_QUOTA_MAP] = { {NAMESPACEOID}, {AUTHOID}, {NAMESPACEOID, TABLESPACEOID}, {AUTHOID, TABLESPACEOID}, {TABLESPACEOID}}; -static HTAB *quota_info_map; -static TimestampTz quota_info_map_last_overflow_report = 0; +static HTAB *quota_info_map; +static TimestampTz *quota_info_map_last_overflow_report = NULL; #define QUOTA_INFO_MAP_WARNING \ "[diskquota] the number of quota probe reached the limit, please " \ @@ -187,8 +183,8 @@ struct LocalRejectMapEntry }; /* using hash table to support incremental update the table size entry.*/ -static HTAB *table_size_map = NULL; -static TimestampTz table_size_map_last_overflow_report = 0; +static HTAB *table_size_map = NULL; +static TimestampTz *table_size_map_last_overflow_report = 0; #define TABLE_SIZE_MAP_WARNING \ "[diskquota] the number of tables reached the limit, please increase " \ @@ -198,14 +194,16 @@ static TimestampTz table_size_map_last_overflow_report = 0; static HTAB *disk_quota_reject_map = NULL; static HTAB *local_disk_quota_reject_map = NULL; -static TimestampTz disk_quota_reject_map_last_overflow_report = 0; -static TimestampTz local_disk_quota_reject_map_last_overflow_report = 0; +static TimestampTz disk_quota_reject_map_last_overflow_report = 0; +static TimestampTz *local_disk_quota_reject_map_last_overflow_report = NULL; -#define DISK_QUOTA_REJECT_MAP_WARNING \ - "[diskquota] Shared disk quota reject map size limit reached. " \ - "Some out-of-limit schemas or roles will be lost in rejectmap." +#define DISK_QUOTA_REJECT_MAP_WARNING \ + "[diskquota] the number of quota reject map entries reached the limit, " \ + "please increase the GUC value for diskquota.max_reject_entries." -#define LOCAL_DISK_QUOTA_REJECT_MAP_WARNING "[diskquota] the number of local reject map entries reached the limit." +#define LOCAL_DISK_QUOTA_REJECT_MAP_WARNING \ + "[diskquota] the number of local quota reject map entries reached the limit, " \ + "please increase the GUC value for diskquota.max_reject_entries." static shmem_startup_hook_type prev_shmem_startup_hook = NULL; @@ -253,7 +251,7 @@ update_size_for_quota(int64 size, QuotaType type, Oid *keys, int16 segid) key.type = type; key.segid = segid; action = check_hash_fullness(quota_info_map, diskquota_max_quota_probes, QUOTA_INFO_MAP_WARNING, - "a_info_map_last_overflow_report); + quota_info_map_last_overflow_report); entry = hash_search(quota_info_map, &key, action, &found); /* If the number of quota exceeds the limit, entry will be NULL */ if (entry == NULL) return; @@ -280,7 +278,7 @@ update_limit_for_quota(int64 limit, float segratio, QuotaType type, Oid *keys) key.type = type; key.segid = i; action = check_hash_fullness(quota_info_map, diskquota_max_quota_probes, QUOTA_INFO_MAP_WARNING, - "a_info_map_last_overflow_report); + quota_info_map_last_overflow_report); entry = hash_search(quota_info_map, &key, action, &found); /* If the number of quota exceeds the limit, entry will be NULL */ if (entry == NULL) continue; @@ -309,13 +307,13 @@ add_quota_to_rejectmap(QuotaType type, Oid targetOid, Oid tablespaceoid, bool se keyitem.databaseoid = MyDatabaseId; keyitem.tablespaceoid = tablespaceoid; keyitem.targettype = (uint32)type; - ereport(DEBUG1, (errmsg("[diskquota] Put object %u to rejectmap", targetOid))); HASHACTION action = - check_hash_fullness(local_disk_quota_reject_map, MAX_DISK_QUOTA_REJECT_ENTRIES, - LOCAL_DISK_QUOTA_REJECT_MAP_WARNING, &local_disk_quota_reject_map_last_overflow_report); + check_hash_fullness(local_disk_quota_reject_map, diskquota_max_local_reject_entries, + LOCAL_DISK_QUOTA_REJECT_MAP_WARNING, local_disk_quota_reject_map_last_overflow_report); localrejectentry = hash_search(local_disk_quota_reject_map, &keyitem, action, NULL); if (localrejectentry) { + ereport(DEBUG1, (errmsg("[diskquota] Put object %u to rejectmap", targetOid))); localrejectentry->isexceeded = true; localrejectentry->segexceeded = segexceeded; } @@ -444,7 +442,7 @@ disk_quota_shmem_startup(void) hash_ctl.keysize = sizeof(RejectMapEntry); hash_ctl.entrysize = sizeof(GlobalRejectMapEntry); disk_quota_reject_map = - DiskquotaShmemInitHash("rejectmap whose quota limitation is reached", INIT_DISK_QUOTA_REJECT_ENTRIES, + DiskquotaShmemInitHash("rejectmap whose quota limitation is reached", diskquota_max_local_reject_entries, MAX_DISK_QUOTA_REJECT_ENTRIES, &hash_ctl, HASH_ELEM, DISKQUOTA_TAG_HASH); init_shm_worker_active_tables(); @@ -505,7 +503,9 @@ diskquota_worker_shmem_size() Size size; size = hash_estimate_size(MAX_NUM_TABLE_SIZE_ENTRIES / diskquota_max_monitored_databases + 100, sizeof(TableSizeEntry)); - size = add_size(size, hash_estimate_size(MAX_LOCAL_DISK_QUOTA_REJECT_ENTRIES, sizeof(LocalRejectMapEntry))); + size = add_size(size, hash_estimate_size(diskquota_max_local_reject_entries, sizeof(LocalRejectMapEntry))); + size = add_size(size, sizeof(TimestampTz)); // table_size_map_last_overflow_report + size = add_size(size, sizeof(TimestampTz)); // local_disk_quota_reject_map_last_overflow_report return size; } @@ -532,6 +532,8 @@ DiskQuotaShmemSize(void) size = add_size(size, diskquota_worker_shmem_size() * diskquota_max_monitored_databases); size = add_size(size, hash_estimate_size(MAX_QUOTA_MAP_ENTRIES, sizeof(QuotaInfoEntry)) * diskquota_max_monitored_databases); + size = add_size(size, + sizeof(TimestampTz) * diskquota_max_monitored_databases); // quota_info_map_last_overflow_report } return size; @@ -546,6 +548,7 @@ init_disk_quota_model(uint32 id) { HASHCTL hash_ctl; StringInfoData str; + bool found; initStringInfo(&str); format_name("TableSizeEntrymap", id, &str); @@ -554,6 +557,9 @@ init_disk_quota_model(uint32 id) hash_ctl.entrysize = sizeof(TableSizeEntry); table_size_map = DiskquotaShmemInitHash(str.data, INIT_NUM_TABLE_SIZE_ENTRIES, MAX_NUM_TABLE_SIZE_ENTRIES, &hash_ctl, HASH_ELEM, DISKQUOTA_TAG_HASH); + format_name("TableSizeEntrymap_last_overflow_report", id, &str); + table_size_map_last_overflow_report = ShmemInitStruct(str.data, sizeof(TimestampTz), &found); + if (!found) *table_size_map_last_overflow_report = 0; /* for localrejectmap */ /* WARNNING: The max length of name of the map is 48 */ @@ -562,9 +568,13 @@ init_disk_quota_model(uint32 id) hash_ctl.keysize = sizeof(RejectMapEntry); hash_ctl.entrysize = sizeof(LocalRejectMapEntry); local_disk_quota_reject_map = - DiskquotaShmemInitHash(str.data, MAX_LOCAL_DISK_QUOTA_REJECT_ENTRIES, MAX_LOCAL_DISK_QUOTA_REJECT_ENTRIES, + DiskquotaShmemInitHash(str.data, diskquota_max_local_reject_entries, diskquota_max_local_reject_entries, &hash_ctl, HASH_ELEM, DISKQUOTA_TAG_HASH); + format_name("localrejectmap_last_overflow_report", id, &str); + local_disk_quota_reject_map_last_overflow_report = ShmemInitStruct(str.data, sizeof(TimestampTz), &found); + if (!found) *local_disk_quota_reject_map_last_overflow_report = 0; + /* for quota_info_map */ format_name("QuotaInfoMap", id, &str); memset(&hash_ctl, 0, sizeof(hash_ctl)); @@ -572,6 +582,9 @@ init_disk_quota_model(uint32 id) hash_ctl.keysize = sizeof(QuotaInfoEntryKey); quota_info_map = DiskquotaShmemInitHash(str.data, INIT_QUOTA_MAP_ENTRIES, MAX_QUOTA_MAP_ENTRIES, &hash_ctl, HASH_ELEM, DISKQUOTA_TAG_HASH); + format_name("QuotaInfoMap_last_overflow_report", id, &str); + quota_info_map_last_overflow_report = ShmemInitStruct(str.data, sizeof(TimestampTz), &found); + if (!found) *quota_info_map_last_overflow_report = 0; pfree(str.data); } @@ -595,6 +608,7 @@ vacuum_disk_quota_model(uint32 id) TableSizeEntry *tsentry = NULL; LocalRejectMapEntry *localrejectentry; QuotaInfoEntry *qentry; + bool found; HASHCTL hash_ctl; StringInfoData str; @@ -613,19 +627,25 @@ vacuum_disk_quota_model(uint32 id) hash_search(table_size_map, &tsentry->key, HASH_REMOVE, NULL); } + format_name("TableSizeEntrymap_last_overflow_report", id, &str); + table_size_map_last_overflow_report = ShmemInitStruct(str.data, sizeof(TimestampTz), &found); + if (!found) *table_size_map_last_overflow_report = 0; /* localrejectmap */ format_name("localrejectmap", id, &str); memset(&hash_ctl, 0, sizeof(hash_ctl)); hash_ctl.keysize = sizeof(RejectMapEntry); hash_ctl.entrysize = sizeof(LocalRejectMapEntry); local_disk_quota_reject_map = - DiskquotaShmemInitHash(str.data, MAX_LOCAL_DISK_QUOTA_REJECT_ENTRIES, MAX_LOCAL_DISK_QUOTA_REJECT_ENTRIES, + DiskquotaShmemInitHash(str.data, diskquota_max_local_reject_entries, diskquota_max_local_reject_entries, &hash_ctl, HASH_ELEM, DISKQUOTA_TAG_HASH); hash_seq_init(&iter, local_disk_quota_reject_map); while ((localrejectentry = hash_seq_search(&iter)) != NULL) { hash_search(local_disk_quota_reject_map, &localrejectentry->keyitem, HASH_REMOVE, NULL); } + format_name("localrejectmap_last_overflow_report", id, &str); + local_disk_quota_reject_map_last_overflow_report = ShmemInitStruct(str.data, sizeof(TimestampTz), &found); + if (!found) *local_disk_quota_reject_map_last_overflow_report = 0; /* quota_info_map */ format_name("QuotaInfoMap", id, &str); @@ -639,6 +659,9 @@ vacuum_disk_quota_model(uint32 id) { hash_search(quota_info_map, &qentry->key, HASH_REMOVE, NULL); } + format_name("QuotaInfoMap_last_overflow_report", id, &str); + quota_info_map_last_overflow_report = ShmemInitStruct(str.data, sizeof(TimestampTz), &found); + if (!found) *quota_info_map_last_overflow_report = 0; pfree(str.data); } @@ -989,7 +1012,7 @@ calculate_table_disk_usage(bool is_init, HTAB *local_active_table_stat_map) key.id = TableSizeEntryId(cur_segid); HASHACTION action = check_hash_fullness(table_size_map, MAX_NUM_TABLE_SIZE_ENTRIES, TABLE_SIZE_MAP_WARNING, - &table_size_map_last_overflow_report); + table_size_map_last_overflow_report); tsentry = hash_search(table_size_map, &key, action, &table_size_map_found); if (!table_size_map_found) diff --git a/tests/regress/diskquota_schedule b/tests/regress/diskquota_schedule index 82560063..05baeef3 100644 --- a/tests/regress/diskquota_schedule +++ b/tests/regress/diskquota_schedule @@ -32,6 +32,7 @@ test: test_appendonly test: test_rejectmap test: test_clean_rejectmap_after_drop test: test_rejectmap_mul_db +test: test_rejectmap_limit test: test_ctas_pause test: test_ctas_role test: test_ctas_schema diff --git a/tests/regress/expected/test_rejectmap_limit.out b/tests/regress/expected/test_rejectmap_limit.out new file mode 100644 index 00000000..05361c5d --- /dev/null +++ b/tests/regress/expected/test_rejectmap_limit.out @@ -0,0 +1,87 @@ +-- +-- This file contains tests for limiting reject map +-- +\! gpconfig -c diskquota.max_reject_entries -v 4 > /dev/null +\! gpstop -arf > /dev/null +\c +CREATE DATABASE test_reject_map_limit_01; +\c test_reject_map_limit_01 +CREATE EXTENSION diskquota; +SELECT diskquota.wait_for_worker_new_epoch(); + wait_for_worker_new_epoch +--------------------------- + t +(1 row) + +CREATE EXTERNAL WEB TABLE master_log(line text) + EXECUTE 'cat $GP_SEG_DATADIR/pg_log/$(ls -Art $GP_SEG_DATADIR/pg_log | tail -n 1)' + ON MASTER FORMAT 'TEXT' (DELIMITER 'OFF'); +CREATE SCHEMA s1; +CREATE SCHEMA s2; +CREATE SCHEMA s3; +CREATE SCHEMA s4; +CREATE SCHEMA s5; +SELECT diskquota.set_schema_quota('s1', '1 MB'); + set_schema_quota +------------------ + +(1 row) + +SELECT diskquota.set_schema_quota('s2', '1 MB'); + set_schema_quota +------------------ + +(1 row) + +SELECT diskquota.set_schema_quota('s3', '1 MB'); + set_schema_quota +------------------ + +(1 row) + +SELECT diskquota.set_schema_quota('s4', '1 MB'); + set_schema_quota +------------------ + +(1 row) + +SELECT diskquota.set_schema_quota('s5', '1 MB'); + set_schema_quota +------------------ + +(1 row) + +SELECT diskquota.wait_for_worker_new_epoch(); + wait_for_worker_new_epoch +--------------------------- + t +(1 row) + +CREATE TABLE s1.a(i int) DISTRIBUTED BY (i); +CREATE TABLE s2.a(i int) DISTRIBUTED BY (i); +CREATE TABLE s3.a(i int) DISTRIBUTED BY (i); +CREATE TABLE s4.a(i int) DISTRIBUTED BY (i); +CREATE TABLE s5.a(i int) DISTRIBUTED BY (i); +INSERT INTO s1.a SELECT generate_series(1,100000); +INSERT INTO s2.a SELECT generate_series(1,100000); +INSERT INTO s3.a SELECT generate_series(1,100000); +INSERT INTO s4.a SELECT generate_series(1,100000); +INSERT INTO s5.a SELECT generate_series(1,100000); +SELECT diskquota.wait_for_worker_new_epoch(); + wait_for_worker_new_epoch +--------------------------- + t +(1 row) + +SELECT count(*) FROM master_log WHERE line LIKE '%the number of local quota reject map entries reached the limit%' AND line NOT LIKE '%LOG%'; + count +------- + 1 +(1 row) + +INSERT INTO s5.a SELECT generate_series(1,10); -- should be successful +DROP EXTENSION diskquota; +\c contrib_regression +DROP DATABASE test_reject_map_limit_01; +\! gpconfig -r diskquota.max_reject_entries > /dev/null +\! gpstop -arf > /dev/null diff --git a/tests/regress/sql/test_rejectmap_limit.sql b/tests/regress/sql/test_rejectmap_limit.sql new file mode 100644 index 00000000..4598f572 --- /dev/null +++ b/tests/regress/sql/test_rejectmap_limit.sql @@ -0,0 +1,56 @@ +-- +-- This file contains tests for limiting reject map +-- + +\! gpconfig -c diskquota.max_reject_entries -v 4 > /dev/null +\! gpstop -arf > /dev/null + +\c + +CREATE DATABASE test_reject_map_limit_01; + +\c test_reject_map_limit_01 +CREATE EXTENSION diskquota; +SELECT diskquota.wait_for_worker_new_epoch(); +CREATE EXTERNAL WEB TABLE master_log(line text) + EXECUTE 'cat $GP_SEG_DATADIR/pg_log/$(ls -Art $GP_SEG_DATADIR/pg_log | tail -n 1)' + ON MASTER FORMAT 'TEXT' (DELIMITER 'OFF'); + +CREATE SCHEMA s1; +CREATE SCHEMA s2; +CREATE SCHEMA s3; +CREATE SCHEMA s4; +CREATE SCHEMA s5; + +SELECT diskquota.set_schema_quota('s1', '1 MB'); +SELECT diskquota.set_schema_quota('s2', '1 MB'); +SELECT diskquota.set_schema_quota('s3', '1 MB'); +SELECT diskquota.set_schema_quota('s4', '1 MB'); +SELECT diskquota.set_schema_quota('s5', '1 MB'); +SELECT diskquota.wait_for_worker_new_epoch(); + +CREATE TABLE s1.a(i int) DISTRIBUTED BY (i); +CREATE TABLE s2.a(i int) DISTRIBUTED BY (i); +CREATE TABLE s3.a(i int) DISTRIBUTED BY (i); +CREATE TABLE s4.a(i int) DISTRIBUTED BY (i); +CREATE TABLE s5.a(i int) DISTRIBUTED BY (i); + +INSERT INTO s1.a SELECT generate_series(1,100000); +INSERT INTO s2.a SELECT generate_series(1,100000); +INSERT INTO s3.a SELECT generate_series(1,100000); +INSERT INTO s4.a SELECT generate_series(1,100000); +INSERT INTO s5.a SELECT generate_series(1,100000); + +SELECT diskquota.wait_for_worker_new_epoch(); + +SELECT count(*) FROM master_log WHERE line LIKE '%the number of local quota reject map entries reached the limit%' AND line NOT LIKE '%LOG%'; + +INSERT INTO s5.a SELECT generate_series(1,10); -- should be successful + +DROP EXTENSION diskquota; + +\c contrib_regression +DROP DATABASE test_reject_map_limit_01; + +\! gpconfig -r diskquota.max_reject_entries > /dev/null +\! gpstop -arf > /dev/null \ No newline at end of file