Re: BUG #15923: Prepared statements take way too much memory. - Mailing list pgsql-bugs

From Andres Freund
Subject Re: BUG #15923: Prepared statements take way too much memory.
Date
Msg-id 20190726004124.prcb55bp43537vyw@alap3.anarazel.de
Whole thread Raw
In response to Re: AW: BUG #15923: Prepared statements take way too much memory.  (Tom Lane <tgl@sss.pgh.pa.us>)
List pgsql-bugs
Hi,

On 2019-07-25 18:13:33 -0400, Tom Lane wrote:
> FWIW, I've thought for some time that we should invent a memory context
> allocator that's meant for data that doesn't get realloc'd (much) after
> first allocation, with lower overhead than aset.c has.

Same.

Although I've wondered whether we'd actually want to tie that to a
separate memory context, but instead add it as an option to set on a
context. It seems like that could potentially reduce overhead, by
avoiding the need to have multiple contexts around. E.g. something like
MemoryContextUseBulkAlloc().  For aset.c that could e.g. switch the
context's callbacks to ones where AllocSetAlloc() is much simpler (one
strategy for large allocations, for the rest don't manage a freelist,
and don't round up allocation sizes).  Not sure what's better.



FWIW, here's a few steps towards tracing aset.c events:

perf probe -x /home/andres/build/postgres/dev-assert/vpath/src/backend/postgres --add 'AllocSetAlloc
context->name:stringsize:u64'
 
perf probe -x /home/andres/build/postgres/dev-assert/vpath/src/backend/postgres --add 'AllocSetFree:3
context->name:stringchunk->size:u64'
 
perf probe -x /home/andres/build/postgres/dev-assert/vpath/src/backend/postgres --add 'AllocSetFree:18
context->name:stringchunk->size:u64'
 
perf probe -x /home/andres/build/postgres/dev-assert/vpath/src/backend/postgres --add 'AllocSetRealloc:9
context->name:stringoldsize=chunk->size:u64 size:u64'
 
perf probe -x /home/andres/build/postgres/dev-assert/vpath/src/backend/postgres --add 'AllocSetReset
context->name:string'
perf probe -x /home/andres/build/postgres/dev-assert/vpath/src/backend/postgres --add 'AllocSetDelete
context->name:string'
perf probe -x /home/andres/build/postgres/dev-assert/vpath/src/backend/postgres --add 'AllocSetContextCreateInternal
name:string'
perf probe -x /home/andres/build/postgres/dev-assert/vpath/src/backend/postgres --add 'raw_parser'
perf probe -x /home/andres/build/postgres/dev-assert/vpath/src/backend/postgres --add 'raw_parser%return'
perf probe -x /home/andres/build/postgres/dev-assert/vpath/src/backend/postgres --add 'pg_analyze_and_rewrite'
perf probe -x /home/andres/build/postgres/dev-assert/vpath/src/backend/postgres --add 'pg_analyze_and_rewrite%return'
perf probe -x /home/andres/build/postgres/dev-assert/vpath/src/backend/postgres --add 'planner%return'
perf probe -x /home/andres/build/postgres/dev-assert/vpath/src/backend/postgres --add 'planner'

which then can be used with perf record to trace a backend's
allocations:

perf record $(for e in AllocSetContextCreateInternal AllocSetDelete AllocSetReset AllocSetAlloc AllocSetFree
AllocSetReallocAllocSetFree raw_parser raw_parser__return pg_analyze_and_rewrite  pg_analyze_and_rewrite__return
plannerplanner__return ; do echo "-e probe_postgres:$e";done) -p 26963
 
<execute query>
ctrl-c

perf script

        postgres 26963 [000] 58737.065318:  probe_postgres:AllocSetContextCreateInternal: (55eba8540c23)
name_string="TopTransactionContext"
        postgres 26963 [000] 58737.065331:                  probe_postgres:AllocSetAlloc: (55eba853f906)
name="TopMemoryContext"size_u64=480
 
        postgres 26963 [000] 58737.065345:                     probe_postgres:raw_parser: (55eba819f68c)
        postgres 26963 [000] 58737.065355:                  probe_postgres:AllocSetAlloc: (55eba853f906)
name="MessageContext"size_u64=160
 
        postgres 26963 [000] 58737.065362:                  probe_postgres:AllocSetAlloc: (55eba853f906)
name="MessageContext"size_u64=944
 
        postgres 26963 [000] 58737.065367:                  probe_postgres:AllocSetAlloc: (55eba853f906)
name="MessageContext"size_u64=64
 
...
        postgres 26963 [000] 58737.067714:                  probe_postgres:AllocSetAlloc: (55eba853f906)
name="MessageContext"size_u64=32
 
        postgres 26963 [000] 58737.067721:             probe_postgres:raw_parser__return: (55eba819f68c <-
55eba83f2254)
        postgres 26963 [000] 58737.067734:                  probe_postgres:AllocSetAlloc: (55eba853f906)
name="TopTransactionContext"size_u64=24
 
        postgres 26963 [000] 58737.067740:                  probe_postgres:AllocSetAlloc: (55eba853f906)
name="TopTransactionContext"size_u64=104
 
        postgres 26963 [000] 58737.067746:         probe_postgres:pg_analyze_and_rewrite: (55eba83f232b)
        postgres 26963 [000] 58737.067753:                  probe_postgres:AllocSetAlloc: (55eba853f906)
name="MessageContext"size_u64=208
 
        postgres 26963 [000] 58737.067759:                  probe_postgres:AllocSetAlloc: (55eba853f906)
name="MessageContext"size_u64=216
 
...
        postgres 26963 [000] 58737.074248:                  probe_postgres:AllocSetAlloc: (55eba853f906)
name="MessageContext"size_u64=32
 
        postgres 26963 [000] 58737.074260:                  probe_postgres:AllocSetAlloc: (55eba853f906)
name="MessageContext"size_u64=32
 
        postgres 26963 [000] 58737.074266: probe_postgres:pg_analyze_and_rewrite__return: (55eba83f232b <-
55eba83f2ae9)
        postgres 26963 [000] 58737.074273:                        probe_postgres:planner: (55eba8326c51)
        postgres 26963 [000] 58737.074280:                  probe_postgres:AllocSetAlloc: (55eba853f906)
name="MessageContext"size_u64=128
 
        postgres 26963 [000] 58737.074305:                  probe_postgres:AllocSetAlloc: (55eba853f906)
name="MessageContext"size_u64=536
 
        postgres 26963 [000] 58737.074316:                  probe_postgres:AllocSetAlloc: (55eba853f906)
name="MessageContext"size_u64=48
 
...
        postgres 26963 [000] 58737.076820:                  probe_postgres:AllocSetAlloc: (55eba853f906)
name="MessageContext"size_u64=16
 
        postgres 26963 [000] 58737.076827:                probe_postgres:AllocSetRealloc: (55eba85403f6)
name="MessageContext"oldsize=32 size_u64=24
 
        postgres 26963 [000] 58737.076843:  probe_postgres:AllocSetContextCreateInternal: (55eba8540c23)
name_string="inline_function"
        postgres 26963 [000] 58737.076854:                  probe_postgres:AllocSetAlloc: (55eba853f906)
name="inline_function"size_u64=179
 
        postgres 26963 [000] 58737.076861:                  probe_postgres:AllocSetAlloc: (55eba853f906)
name="inline_function"size_u64=48
 
        postgres 26963 [000] 58737.076869:                  probe_postgres:AllocSetAlloc: (55eba853f906)
name="inline_function"size_u64=40
 
        postgres 26963 [000] 58737.076876:                  probe_postgres:AllocSetAlloc: (55eba853f906)
name="inline_function"size_u64=16
 
        postgres 26963 [000] 58737.076883:                  probe_postgres:AllocSetAlloc: (55eba853f906)
name="inline_function"size_u64=8
 
        postgres 26963 [000] 58737.076891:                     probe_postgres:raw_parser: (55eba819f68c)
        postgres 26963 [000] 58737.076903:                  probe_postgres:AllocSetAlloc: (55eba853f906)
name="inline_function"size_u64=160
 
        postgres 26963 [000] 58737.076910:                  probe_postgres:AllocSetAlloc: (55eba853f906)
name="inline_function"size_u64=180
 
...
        postgres 26963 [000] 58737.091725:                  probe_postgres:AllocSetAlloc: (55eba853f906)
name="MessageContext"size_u64=128
 
        postgres 26963 [000] 58737.091733:                probe_postgres:planner__return: (55eba8326c51 <-
55eba83f24ba)
        postgres 26963 [000] 58737.091740:                  probe_postgres:AllocSetAlloc: (55eba853f906)
name="MessageContext"size_u64=32
 
        postgres 26963 [000] 58737.091749:                   probe_postgres:AllocSetFree: (55eba853f4fc)
name="TopTransactionContext"size=128
 
...

the query here was just \dt+.


it's possible that you'd need to change the offsets slightly, depending
on compiler options. One can see tracable lines with:
perf probe -x /home/andres/build/postgres/dev-assert/vpath/src/backend/postgres --line AllocSetRealloc
and the available variables with:
erf probe -x /home/andres/build/postgres/dev-assert/vpath/src/backend/postgres --vars AllocSetRealloc:9


Shouldn't be hard to parse that into something giving actual stats for
each memory context, and each query phase.


> Such an allocator would be ideal for plancache.c, and perhaps other
> use-cases such as plpgsql function parsetrees.

Probably also for raw parsing. Bet we'd save more than we'd loose by not
freeing during it.


> IMV this would have these properties:
>
> * Doesn't support retail pfree; to recover space you must destroy the
> whole context.  We could just make pfree a no-op.  With the details
> sketched below, repalloc would have to throw an error (because it would
> not know the size of the old chunk), but I think that's OK for the
> intended purpose.

> * Minimum chunk header overhead, ie only the context pointer required by
> the mmgr.c infrastructure.  In particular, don't store the chunk size.

I'm not sure it's worth eliding the size. Will make it hard to adapt the
new type of context to new code - it's pretty hard to guarantee that
there's not one random utility function in some rarely executed path
that does need the size.


> (Possibly, instead of freelists, we'd trouble to track
> end-of-block space in more than one active block, so that we avoid
> wasting such space when a large allocation doesn't quite fit.)

Hm, doesn't aset.c already solve that by not putting the oversized block
at the head of the block list?



> A totally different idea is to make a variant version of copyObject
> that is intended to produce a compact form of a node tree, and does
> not create a separate palloc allocation for each node but just packs
> them as tightly as it can in larger palloc chunks.  This could outperform
> the no-pfree-context idea because it wouldn't need even context-pointer
> overhead for each node.

If done right, it'd probably also yield considerably higher spatial
locality in the memory layout.


> The stumbling block here is that nobody is gonna tolerate maintaining
> two versions of copyfuncs.c, so you'd have to find a way for a single
> set of copy functions to support this output format as well as the
> traditional one.  (Alternatively, maybe we could learn to autogenerate
> the copy functions from annotated struct definitions; people have
> muttered about that for years but not done anything.)

Yea, I think we'd have to autogenerate them to go for that.  I'm kinda
hoping that maybe John Naylor would take a look, after he made all our
lives quite a bit easier with the other catalog script work...

In the 'pipe dream' version of this, that script would be able to
generate code to adjust pointers after a memcpy... That'd make
copyObject() of such flattened trees much much cheaper.

Greetings,

Andres Freund



pgsql-bugs by date:

Previous
From: Michael Paquier
Date:
Subject: Re: REINDEX CONCURRENTLY causes ALTER TABLE to fail
Next
From: Andres Freund
Date:
Subject: Re: BUG #15923: Prepared statements take way too much memory.