Skip to content

Commit

Permalink
Add guc diskquota.max_reject_entries that control size of
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
KnightMurloc committed Dec 25, 2023
1 parent cb4e1bf commit b8bbfc8
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 27 deletions.
3 changes: 3 additions & 0 deletions src/diskquota.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion src/diskquota_utility.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Expand Down
75 changes: 49 additions & 26 deletions src/quotamodel.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,7 @@
#include <math.h>

/* 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 */
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -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 " \
Expand Down Expand Up @@ -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 " \
Expand All @@ -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;

Expand Down Expand Up @@ -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,
&quota_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;
Expand All @@ -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,
&quota_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;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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;
}

Expand All @@ -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;
Expand All @@ -546,6 +548,7 @@ init_disk_quota_model(uint32 id)
{
HASHCTL hash_ctl;
StringInfoData str;
bool found;
initStringInfo(&str);

format_name("TableSizeEntrymap", id, &str);
Expand All @@ -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 */
Expand All @@ -562,16 +568,23 @@ 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));
hash_ctl.entrysize = sizeof(QuotaInfoEntry);
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);
}
Expand All @@ -595,6 +608,7 @@ vacuum_disk_quota_model(uint32 id)
TableSizeEntry *tsentry = NULL;
LocalRejectMapEntry *localrejectentry;
QuotaInfoEntry *qentry;
bool found;

HASHCTL hash_ctl;
StringInfoData str;
Expand All @@ -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);
Expand All @@ -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);
}
Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions tests/regress/diskquota_schedule
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
87 changes: 87 additions & 0 deletions tests/regress/expected/test_rejectmap_limit.out
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit b8bbfc8

Please sign in to comment.