Reduce memory overheads for storing a Memoize tuple

Started by David Rowley23 days ago9 messageshackers
Beta feature

Hackorum builds and tests every patch posted to the lists, not only commitfest submissions. This is Hackorum's own CI rather than the PostgreSQL project's, and it is still under testing - please report anything that looks wrong.

won't retrysuccessCI history

This thread has been committed, so CI has stopped here. Anything below is the last result it produced.

You can run a PostgreSQL built from this patch straight from Docker, with no checkout and no build:

docker run --rm -p 5432:5432 ghcr.io/hackorum-dev/postgres-patch:t253272
psql -h localhost -U postgres

Built from patchset v7 (message #7), August 21, 2026 at 03:41 PM.

Every patchset is also pushed to a branch of our PostgreSQL fork, so you can check out the same tree CI built. Without a PostgreSQL checkout:

git clone --branch t253272_7 https://github.com/hackorum-dev/postgres.git

In a checkout you already have, add the fork once:

git remote add hackorum https://github.com/hackorum-dev/postgres.git

then, for this patchset and every later one:

git fetch hackorum t253272_7 && git checkout t253272_7

Patchset v7 (message #7) is on t253272_7

Jump to latest
#1David Rowley
dgrowleyml@gmail.com

a0942f441 added ExecCopySlotMinimalTupleExtra(), which accepts a
parameter to specify the number of "extra" bytes that we want to
allocate along with the MinimalTuple. That's now used in
execGrouping.c to save some memory.

The same optimisation can be applied in nodeMemoize.c. MemoizeTuple
has a field for the tuple being stored and 1 other field to point to
the next tuple cached for this MemoizeEntry. Here we could use
ExecCopySlotMinimalTupleExtra() to specify that we want a pointer's
worth of extra bytes palloc'd for the MinimalTuple, and then store the
pointer to the next tuple in those bytes. This saves 16 bytes per
cached tuple. 24 bytes less because we don't palloc a MemoizeTuple
(including the MemoryChunk's 8 bytes), and 8 bytes more for the
ExecCopySlotMinimalTupleExtra bytes, a net saving of 16 bytes per
tuple.

Making Memoize use less memory is useful in cases where the cache
would otherwise have to reload entries that were cached previously but
were evicted due to reaching memory limits.

A quick example:

create table t1 (a int not null);
create table t2 (a int not null);
insert into t1 select x from generate_series(1,100000) x,
generate_series(1,100);
create index on t1 (a);
insert into t2 select x%1000+1 from generate_series(1,1000000)x;
analyze t1,t2;

explain analyze select count(*) from t1 inner join t2 on t1.a=t2.a;

Master: Memory Usage: 3583kB
Patched: Memory Usage: 2801kB

Really, the savings are double what's reported by EXPLAIN ANALYZE, as
CACHE_TUPLE_BYTES doesn't account for any of the MemoryChunks that are
consumed by palloc. We're now doing 1 fewer palloc per tuple due to
the removal of the palloc_object(MemoizeTuple) code, so more like 30%
less memory for this case.

Patch attached.

David

Attachments:

t253272_1
v1-0001-Reduce-memory-overheads-for-storing-a-Memoize-tup.patchapplication/octet-stream; name=v1-0001-Reduce-memory-overheads-for-storing-a-Memoize-tup.patchDownload+66-41
#2Chao Li
li.evan.chao@gmail.com
In reply to: David Rowley (#1)
Re: Reduce memory overheads for storing a Memoize tuple

On Aug 1, 2026, at 16:35, David Rowley <dgrowleyml@gmail.com> wrote:

a0942f441 added ExecCopySlotMinimalTupleExtra(), which accepts a
parameter to specify the number of "extra" bytes that we want to
allocate along with the MinimalTuple. That's now used in
execGrouping.c to save some memory.

The same optimisation can be applied in nodeMemoize.c. MemoizeTuple
has a field for the tuple being stored and 1 other field to point to
the next tuple cached for this MemoizeEntry. Here we could use
ExecCopySlotMinimalTupleExtra() to specify that we want a pointer's
worth of extra bytes palloc'd for the MinimalTuple, and then store the
pointer to the next tuple in those bytes. This saves 16 bytes per
cached tuple. 24 bytes less because we don't palloc a MemoizeTuple
(including the MemoryChunk's 8 bytes), and 8 bytes more for the
ExecCopySlotMinimalTupleExtra bytes, a net saving of 16 bytes per
tuple.

Making Memoize use less memory is useful in cases where the cache
would otherwise have to reload entries that were cached previously but
were evicted due to reaching memory limits.

A quick example:

create table t1 (a int not null);
create table t2 (a int not null);
insert into t1 select x from generate_series(1,100000) x,
generate_series(1,100);
create index on t1 (a);
insert into t2 select x%1000+1 from generate_series(1,1000000)x;
analyze t1,t2;

explain analyze select count(*) from t1 inner join t2 on t1.a=t2.a;

Master: Memory Usage: 3583kB
Patched: Memory Usage: 2801kB

Really, the savings are double what's reported by EXPLAIN ANALYZE, as
CACHE_TUPLE_BYTES doesn't account for any of the MemoryChunks that are
consumed by palloc. We're now doing 1 fewer palloc per tuple due to
the removal of the palloc_object(MemoizeTuple) code, so more like 30%
less memory for this case.

Patch attached.

David
<v1-0001-Reduce-memory-overheads-for-storing-a-Memoize-tup.patch>

The optimized data structure looks good to me. The test result on my side exactly matches yours: 3583kB vs. 2801kB. I had to turn off hash join, otherwise the planner always chose a parallel hash join.

I have only one nitpick. MAXALIGN(sizeof(MinimalTuple)) appears 4 times. Would it make sense to define a macro for it, say MEMOIZE_TUPLE_LINK_SIZE?

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/

#3David Rowley
dgrowleyml@gmail.com
In reply to: Chao Li (#2)
Re: Reduce memory overheads for storing a Memoize tuple

On Mon, 3 Aug 2026 at 16:52, Chao Li <li.evan.chao@gmail.com> wrote:

The optimized data structure looks good to me. The test result on my side exactly matches yours: 3583kB vs. 2801kB. I had to turn off hash join, otherwise the planner always chose a parallel hash join.

Thanks for looking and checking.

I have only one nitpick. MAXALIGN(sizeof(MinimalTuple)) appears 4 times. Would it make sense to define a macro for it, say MEMOIZE_TUPLE_LINK_SIZE?

Good idea. Here's a new patch with that adjustment made.

David

Attachments:

t253272_3
v2-0001-Reduce-memory-overheads-for-storing-a-Memoize-tup.patchapplication/octet-stream; name=v2-0001-Reduce-memory-overheads-for-storing-a-Memoize-tup.patchDownload+75-41
#4Chao Li
li.evan.chao@gmail.com
In reply to: David Rowley (#1)
Re: Reduce memory overheads for storing a Memoize tuple

On Aug 5, 2026, at 13:23, David Rowley <dgrowleyml@gmail.com> wrote:

On Mon, 3 Aug 2026 at 16:52, Chao Li <li.evan.chao@gmail.com> wrote:

The optimized data structure looks good to me. The test result on my side exactly matches yours: 3583kB vs. 2801kB. I had to turn off hash join, otherwise the planner always chose a parallel hash join.

Thanks for looking and checking.

I have only one nitpick. MAXALIGN(sizeof(MinimalTuple)) appears 4 times. Would it make sense to define a macro for it, say MEMOIZE_TUPLE_LINK_SIZE?

Good idea. Here's a new patch with that adjustment made.

David
<v2-0001-Reduce-memory-overheads-for-storing-a-Memoize-tup.patch>

V2 LGTM. I also reran the tests, everything is good.

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/

#5David Rowley
dgrowleyml@gmail.com
In reply to: Chao Li (#4)
Re: Reduce memory overheads for storing a Memoize tuple

On Wed, 5 Aug 2026 at 18:29, Chao Li <li.evan.chao@gmail.com> wrote:

V2 LGTM. I also reran the tests, everything is good.

Thanks for looking. Now pushed.

David

#6Michael Paquier
michael@paquier.xyz
In reply to: David Rowley (#5)
Re: Reduce memory overheads for storing a Memoize tuple

On Fri, Aug 21, 2026 at 02:55:23PM +1200, David Rowley wrote:

On Wed, 5 Aug 2026 at 18:29, Chao Li <li.evan.chao@gmail.com> wrote:

V2 LGTM. I also reran the tests, everything is good.

Thanks for looking. Now pushed.

609f969f61bb has broken the CI for 32-bit builds, and the buildfarm is
showing one failure for now also with -m32:
https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=adder&amp;dt=2026-08-21%2006%3A56%3A10

Could you fix that please? All CI runs are failing for the 32-bit
meson task.

Thanks,
--
Michael

#7Andrey Borodin
amborodin@acm.org
In reply to: Michael Paquier (#6)
Re: Reduce memory overheads for storing a Memoize tuple

On 21 Aug 2026, at 10:20, Michael Paquier <michael@paquier.xyz> wrote:

All CI runs are failing for the 32-bit
meson task.

Hi David, Michael,

I hit the same failure while running bunch of CI tasks today for an unrelated
patch. So decided maybe a little patch could be helpful. I used this to calm
CI of my stuff.

The thing is 1000 small cache entries now fit in the minimum work_mem, so the
test reports no evictions.
PFA a fix that uses COUNT(t1), making Memoize cache the whole inner tuple.

Best regards, Andrey Borodin.

Attachments:

t253272_7
0001-Make-Memoize-eviction-test-work-on-32-bit-builds.patchapplication/octet-stream; name=0001-Make-Memoize-eviction-test-work-on-32-bit-builds.patch; x-unix-mode=0644Download+6-6
#8David Rowley
dgrowleyml@gmail.com
In reply to: Michael Paquier (#6)
Re: Reduce memory overheads for storing a Memoize tuple

On Fri, 21 Aug 2026 at 19:20, Michael Paquier <michael@paquier.xyz> wrote:

609f969f61bb has broken the CI for 32-bit builds, and the buildfarm is
showing one failure for now also with -m32:
https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=adder&amp;dt=2026-08-21%2006%3A56%3A10

Could you fix that please? All CI runs are failing for the 32-bit
meson task.

Fix pushed.

David

#9Michael Paquier
michael@paquier.xyz
In reply to: David Rowley (#8)
Re: Reduce memory overheads for storing a Memoize tuple

On Sat, Aug 22, 2026 at 11:39:37AM +1200, David Rowley wrote:

Fix pushed.

Thanks.
--
Michael