*** a/doc/src/sgml/config.sgml --- b/doc/src/sgml/config.sgml *************** *** 1025,1030 **** SET ENABLE_SEQSCAN TO OFF; --- 1025,1067 ---- + + Disk + + + + temp_file_limit (integer) + + temp_file_limit configuration parameter + + + + Specifies the amount of disk space used by internal sort operations + and hash tables whist writing to temporary disk files. The value + defaults to unlimited (-1). Values larger than zero + constraint the temporary file space usage to be that number of + kilobytes. + + + A given sort or hash operation may write a number of temporary files, + the total space used by all the files produced by one backend is + constrained to be this value or less. If further bytes are written + the current query is canceled. Only superusers can change this + setting. + + + It should be noted that this parameter does not + constrain disk space used for temporary table storage. However if + the temporary table is created from a query then the any sort + and hash files used in query execution will have their space + controlled as above. + + + + + + + Kernel Resource Usage *** a/src/backend/storage/file/fd.c --- b/src/backend/storage/file/fd.c *************** *** 131,136 **** static int max_safe_fds = 32; /* default if not changed */ --- 131,141 ---- /* Flag to tell whether there are files to close/delete at end of transaction */ static bool have_pending_fd_cleanup = false; + /* + * Track the total size of all temporary files + */ + static double temporary_files_size = 0.0; + typedef struct vfd { int fd; /* current FD, or VFD_CLOSED if none */ *************** *** 140,145 **** typedef struct vfd --- 145,151 ---- File lruMoreRecently; /* doubly linked recency-of-use list */ File lruLessRecently; off_t seekPos; /* current logical file position */ + off_t fileSize; /* current size of file */ char *fileName; /* name of file, or NULL for unused VFD */ /* NB: fileName is malloc'd, and must be free'd when closing the VFD */ int fileFlags; /* open(2) flags for (re)opening the file */ *************** *** 887,892 **** PathNameOpenFile(FileName fileName, int fileFlags, int fileMode) --- 893,899 ---- vfdP->fileFlags = fileFlags & ~(O_CREAT | O_TRUNC | O_EXCL); vfdP->fileMode = fileMode; vfdP->seekPos = 0; + vfdP->fileSize = 0; vfdP->fdstate = 0x0; vfdP->resowner = NULL; *************** *** 1123,1128 **** FileClose(File file) --- 1130,1142 ---- if (unlink(vfdP->fileName)) elog(LOG, "could not unlink file \"%s\": %m", vfdP->fileName); } + + if (temp_file_limit >= 0) + { + /* subtract the unlinked file size from the total */ + temporary_files_size -= (double)vfdP->fileSize; + vfdP->fileSize = 0; + } } /* Unregister it from the resource owner */ *************** *** 1230,1235 **** int --- 1244,1250 ---- FileWrite(File file, char *buffer, int amount) { int returnCode; + bool increaseSize = false; Assert(FileIsValid(file)); *************** *** 1242,1247 **** FileWrite(File file, char *buffer, int amount) --- 1257,1278 ---- if (returnCode < 0) return returnCode; + /* + * If performing this write will increase the file size, then abort if it will + * exceed temp_file_limit + */ + if (temp_file_limit >= 0 && VfdCache[file].fdstate & FD_TEMPORARY) + { + if (VfdCache[file].seekPos + amount >= VfdCache[file].fileSize) + { + increaseSize = true; + if ((temporary_files_size + (double)amount) / 1024.0 > (double)temp_file_limit) + ereport(ERROR, + (errcode(ERRCODE_QUERY_CANCELED), + errmsg("aborting due to exceeding temp file limit"))); + } + } + retry: errno = 0; returnCode = write(VfdCache[file].fd, buffer, amount); *************** *** 1251,1257 **** retry: --- 1282,1300 ---- errno = ENOSPC; if (returnCode >= 0) + { VfdCache[file].seekPos += returnCode; + + if (temp_file_limit >= 0 && VfdCache[file].fdstate & FD_TEMPORARY) + { + /* If we increase the file size, add it to the total */ + if (increaseSize) + { + temporary_files_size += (double)(VfdCache[file].seekPos - VfdCache[file].fileSize); + VfdCache[file].fileSize = VfdCache[file].seekPos; + } + } + } else { /* *************** *** 1887,1892 **** CleanupTempFiles(bool isProcExit) --- 1930,1936 ---- } have_pending_fd_cleanup = false; + temporary_files_size = 0.0; } /* Clean up "allocated" stdio files and dirs. */ *** a/src/backend/utils/misc/guc.c --- b/src/backend/utils/misc/guc.c *************** *** 423,428 **** int log_min_messages = WARNING; --- 423,429 ---- int client_min_messages = NOTICE; int log_min_duration_statement = -1; int log_temp_files = -1; + int temp_file_limit = -1; int trace_recovery_messages = LOG; int num_temp_buffers = 1024; *************** *** 535,540 **** const char *const config_group_names[] = --- 536,543 ---- gettext_noop("Resource Usage"), /* RESOURCES_MEM */ gettext_noop("Resource Usage / Memory"), + /* RESOURCES_DISK */ + gettext_noop("Resource Usage / Disk"), /* RESOURCES_KERNEL */ gettext_noop("Resource Usage / Kernel Resources"), /* RESOURCES_VACUUM_DELAY */ *************** *** 2185,2190 **** static struct config_int ConfigureNamesInt[] = --- 2188,2203 ---- RELSEG_SIZE, RELSEG_SIZE, RELSEG_SIZE, NULL, NULL, NULL }, + { + {"temp_file_limit", PGC_SUSET, RESOURCES_DISK, + gettext_noop("Sets the maximum size of all temp files used by each session."), + gettext_noop("-1 turns this feature off."), + GUC_UNIT_KB + }, + &temp_file_limit, + -1, -1, MAX_KILOBYTES, + NULL, NULL, NULL + }, { {"wal_block_size", PGC_INTERNAL, PRESET_OPTIONS, *** a/src/backend/utils/misc/postgresql.conf.sample --- b/src/backend/utils/misc/postgresql.conf.sample *************** *** 118,123 **** --- 118,124 ---- #work_mem = 1MB # min 64kB #maintenance_work_mem = 16MB # min 1MB #max_stack_depth = 2MB # min 100kB + #temp_file_limit = -1 # limit backend temp file space # - Kernel Resource Usage - *** a/src/include/utils/guc.h --- b/src/include/utils/guc.h *************** *** 208,213 **** extern int log_min_messages; --- 208,214 ---- extern int client_min_messages; extern int log_min_duration_statement; extern int log_temp_files; + extern int temp_file_limit; extern int num_temp_buffers; *** a/src/include/utils/guc_tables.h --- b/src/include/utils/guc_tables.h *************** *** 59,64 **** enum config_group --- 59,65 ---- CONN_AUTH_SECURITY, RESOURCES, RESOURCES_MEM, + RESOURCES_DISK, RESOURCES_KERNEL, RESOURCES_VACUUM_DELAY, RESOURCES_BGWRITER, *** /dev/null --- b/src/test/regress/expected/resource.out *************** *** 0 **** --- 1,18 ---- + -- + -- RESOURCE + -- Test resource management capabilities + -- + -- temp_file_limit + CREATE TEMPORARY TABLE resourcetemp1 AS SELECT generate_series(1,100000); + -- Should succeed + SET temp_file_limit = 10000; + SELECT count(*) FROM (select * FROM resourcetemp1 ORDER BY 1) AS a; + count + -------- + 100000 + (1 row) + + -- Should fail + SET temp_file_limit = 1000; + SELECT count(*) FROM (select * FROM resourcetemp1 ORDER BY 1) AS a; + ERROR: aborting due to exceeding temp file limit *** a/src/test/regress/serial_schedule --- b/src/test/regress/serial_schedule *************** *** 127,129 **** test: largeobject --- 127,130 ---- test: with test: xml test: stats + test: resource *** /dev/null --- b/src/test/regress/sql/resource.sql *************** *** 0 **** --- 1,17 ---- + -- + -- RESOURCE + -- Test resource management capabilities + -- + + -- temp_file_limit + CREATE TEMPORARY TABLE resourcetemp1 AS SELECT generate_series(1,100000); + + -- Should succeed + SET temp_file_limit = 10000; + + SELECT count(*) FROM (select * FROM resourcetemp1 ORDER BY 1) AS a; + + -- Should fail + SET temp_file_limit = 1000; + + SELECT count(*) FROM (select * FROM resourcetemp1 ORDER BY 1) AS a;