I'd like to propose adding a new GUC to limit the amount of memory a backend
can allocate for its own use. The problem this addresses is that sometimes
one needs to set work_mem fairly high to get good query plans for large joins.
However, some complex queries will then use huge amounts of memory so that
one or a few of them will consume all the memory on the host and run it deep
into swap or trigger the oom killer or worse.

I've attached a patch based on 8.4.1. It works by keeping a track of the
total memory allocated via malloc to AllocBlocks (aset.c). If this is not
shot down/up too badly I will rebase it on CVS and submit it for the next
commit fest.

I would also like to propose a similar limit on temp space use. It is quite
easy for an unintended cartesion product to use hundreds of gigabytes of
scratch space and cause other processes to fail due to lack of disk space.
If this is not objectionable, I'll work on it too.

-dg

-- 
David Gould       da...@sonic.net      510 536 1443    510 282 0869
If simplicity worked, the world would be overrun with insects.
*** ./src/include/utils/memutils.h.orig 2009-09-30 01:54:36.000000000 -0700
--- ./src/include/utils/memutils.h      2009-09-30 03:33:44.000000000 -0700
***************
*** 114,119 ****
--- 114,122 ----
   */
  
  /* aset.c */
+ 
+ extern int max_allocated_mem;
+ 
  extern MemoryContext AllocSetContextCreate(MemoryContext parent,
                                          const char *name,
                                          Size minContextSize,
*** ./src/backend/utils/mmgr/aset.c.orig        2009-09-29 16:14:23.000000000 
-0700
--- ./src/backend/utils/mmgr/aset.c     2009-10-01 03:07:34.000000000 -0700
***************
*** 168,173 ****
--- 168,187 ----
  } AllocBlockData;
  
  /*
+  * AllocBlock accounting maintains total allocated memory to enforce the 
memory use limit.
+  */
+ int max_allocated_mem = 0;
+ Size AllocBlockAccountingMemUsed = 0;
+ 
+ #define AllocBlockAccountingFree(block)       \
+                       (AllocBlockAccountingMemUsed -= block->endptr - (char 
*) (block))
+ #define AllocBlockAccountingAlloc(block)      \
+                        (AllocBlockAccountingMemUsed += block->endptr - (char 
*) (block))
+ #define AllocBlockAccountingOverLimit()       \
+                       (max_allocated_mem != 0 \
+                        && AllocBlockAccountingMemUsed / 1024 > 
max_allocated_mem)
+ 
+ /*
   * AllocChunk
   *            The prefix of each piece of memory in an AllocBlock
   *
***************
*** 393,398 ****
--- 407,423 ----
                context->blocks = block;
                /* Mark block as not to be released at reset time */
                context->keeper = block;
+ 
+               AllocBlockAccountingAlloc(block);
+               if (AllocBlockAccountingOverLimit())
+               {
+                       MemoryContextStats(TopMemoryContext);
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_OUT_OF_MEMORY),
+                                        errmsg("memory limit exceeded"),
+                                        errdetail("Failed while creating 
memory context \"%s\".",
+                                                          name)));
+               }
        }
  
        context->isReset = true;
***************
*** 476,481 ****
--- 501,507 ----
                else
                {
                        /* Normal case, release the block */
+                       AllocBlockAccountingFree(block);
  #ifdef CLOBBER_FREED_MEMORY
                        /* Wipe freed memory for debugging purposes */
                        memset(block, 0x7F, block->freeptr - ((char *) block));
***************
*** 521,526 ****
--- 547,553 ----
        {
                AllocBlock      next = block->next;
  
+               AllocBlockAccountingFree(block);
  #ifdef CLOBBER_FREED_MEMORY
                /* Wipe freed memory for debugging purposes */
                memset(block, 0x7F, block->freeptr - ((char *) block));
***************
*** 597,602 ****
--- 624,640 ----
                        set->blocks = block;
                }
  
+               AllocBlockAccountingAlloc(block);
+               if (AllocBlockAccountingOverLimit())
+               {
+                       MemoryContextStats(TopMemoryContext);
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_OUT_OF_MEMORY),
+                                        errmsg("memory limit exceeded"),
+                                        errdetail("Failed on request of size 
%lu.",
+                                                          (unsigned long) 
size)));
+               }
+ 
                set->isReset = false;
  
                AllocAllocInfo(set, chunk);
***************
*** 767,772 ****
--- 805,821 ----
  
                block->next = set->blocks;
                set->blocks = block;
+ 
+               AllocBlockAccountingAlloc(block);
+               if (AllocBlockAccountingOverLimit())
+               {
+                       MemoryContextStats(TopMemoryContext);
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_OUT_OF_MEMORY),
+                                        errmsg("memory limit exceeded"),
+                                        errdetail("Failed on request of size 
%lu.",
+                                                          (unsigned long) 
size)));
+               }
        }
  
        /*
***************
*** 843,848 ****
--- 892,898 ----
                        set->blocks = block->next;
                else
                        prevblock->next = block->next;
+               AllocBlockAccountingFree(block);
  #ifdef CLOBBER_FREED_MEMORY
                /* Wipe freed memory for debugging purposes */
                memset(block, 0x7F, block->freeptr - ((char *) block));
*** ./src/backend/utils/misc/guc.c.orig 2009-09-30 01:39:18.000000000 -0700
--- ./src/backend/utils/misc/guc.c      2009-10-01 03:03:13.000000000 -0700
***************
*** 167,172 ****
--- 167,173 ----
  static bool assign_maxconnections(int newval, bool doit, GucSource source);
  static bool assign_autovacuum_max_workers(int newval, bool doit, GucSource 
source);
  static bool assign_effective_io_concurrency(int newval, bool doit, GucSource 
source);
+ static bool assign_max_allocated_mem(int newval, bool doit, GucSource source);
  static const char *assign_pgstat_temp_directory(const char *newval, bool 
doit, GucSource source);
  
  static char *config_enum_get_options(struct config_enum * record,
***************
*** 1415,1420 ****
--- 1416,1431 ----
        },
  
        {
+               {"max_allocated_mem", PGC_USERSET, RESOURCES_MEM,
+                       gettext_noop("Sets the maximum memory that can be 
allocated by a session."),
+                       gettext_noop("Exceeding this limit will cause the 
current operation to fail."),
+                       GUC_UNIT_KB
+               },
+               &max_allocated_mem,
+               0, 0, MAX_KILOBYTES, assign_max_allocated_mem, NULL
+       },
+ 
+       {
                {"max_stack_depth", PGC_SUSET, RESOURCES_MEM,
                        gettext_noop("Sets the maximum stack depth, in 
kilobytes."),
                        NULL,
***************
*** 7673,7676 ****
--- 7684,7696 ----
                return newval;
  }
  
+ static bool
+ assign_max_allocated_mem(int newval, bool doit, GucSource source)
+ {
+       /* minimum enforceable limit is 16MB */
+       if (newval != 0 && newval < 16384)
+               return false;
+       return true;
+ }
+ 
  #include "guc-file.c"
*** ./doc/src/sgml/config.sgml.orig     2009-10-01 02:15:56.000000000 -0700
--- ./doc/src/sgml/config.sgml  2009-10-01 03:02:19.000000000 -0700
***************
*** 859,864 ****
--- 859,887 ----
        </listitem>
       </varlistentry>
  
+      <varlistentry id="guc-max-allocated-mem" xreflabel="max_allocated_mem">
+       <term><varname>max_allocated_mem</varname> (<type>integer</type>)</term>
+       <indexterm>
+        <primary><varname>max_allocated_mem</> configuration 
parameter</primary>
+       </indexterm>
+       <listitem>
+        <para>
+         Limits the maximum amount of memory that an individual backend process
+         can allocate for its own use. This prevents a single session running 
a large
+         or badly behaved query from consuming excessive system memory. The 
default
+         setting is zero (<literal>0</>) which disables this limit. This is
+         appropriate for most situations.
+        </para>
+         The minimum non-zero setting is 16 megabytes (<literal>16MB</>). It 
should
+         always be set larger than <varname>maintenance_work_mem</varname>, and
+         usually several times larger than <varname>work_mem</varname>. Useful
+         settings will depend on the nature of the workload. A starting point 
might
+         be one fourth the total memory on the host machine. Setting this too 
small
+         may prevent large or complex queries from running. 
+        </para>
+       </listitem>
+      </varlistentry>
+ 
       <varlistentry id="guc-max-stack-depth" xreflabel="max_stack_depth">
        <term><varname>max_stack_depth</varname> (<type>integer</type>)</term>
        <indexterm>
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to