Re: Changing shared_buffers without restart

From: Ashutosh Bapat <ashutosh(dot)bapat(dot)oss(at)gmail(dot)com>
To: Heikki Linnakangas <hlinnaka(at)iki(dot)fi>
Cc: Tomas Vondra <tomas(at)vondra(dot)me>, Peter Eisentraut <peter(at)eisentraut(dot)org>, Thomas Munro <thomas(dot)munro(at)gmail(dot)com>, Dmitry Dolgov <9erthalion6(at)gmail(dot)com>, pgsql-hackers(at)postgresql(dot)org, Robert Haas <robertmhaas(at)gmail(dot)com>, chaturvedipalak1911(at)gmail(dot)com, Andres Freund <andres(at)anarazel(dot)de>
Subject: Re: Changing shared_buffers without restart
Date: 2026-02-09 15:15:28
Message-ID: CAExHW5tSw8r06RLAArvf923cO4NGetitPhQ7AO0o7hsKx8jsNw@mail.gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

On Sun, Feb 8, 2026 at 5:14 AM Heikki Linnakangas <hlinnaka(at)iki(dot)fi> wrote:
>
> I had a look at this (v20260128), focusing on

We committed refactoring of ShmemLock which is causing a merge
conflict. Attaching latest version of patchset with following changes
on top of 20260128.
1. The number of shared memory segments is reduced to two - main and
shared buffer blocks, as Andres suggested in [1]
2. Created separate structures to track properties of Anonymous shared
memory segments and UsedShmem segments.
3. Rebased on top of the ShmemLock infrastructure.

> v20260128-0002-Memory-and-address-space-management-for-bu.patch It
> introduces a new concept of "segment id" and exposes that to various places:
>
> +void *
> +ShmemInitStructInSegment(const char *name, Size size, bool *foundPtr,
> int segment_id)
>
> So for each shmem struct, you now also specify 'segment_id'. (If you
> call the old ShmemInitStruct() function, it defaults to MAIN_SHMEM_SEGMENT.)
>
> I don't quite understand how you're supposed to use different segments
> and when to use different "structs" in the same segment. The next patch
> makes a segment resizeable:
>
> +/*
> + * ShmemResizeStructInSegment -- Resize the given structure in shared
> memory.
> + *
> + * This function resizes an existing shared memory structure while
> preserving
> + * the existing memory location.
> + *
> + * Returns: pointer to the existing structure location, if the resize is
> + * successful, otherwise NULL.
> + */
> +void *
> +ShmemResizeStructInSegment(const char *name, Size size, bool *foundPtr,
> + int segment_id)
>
> Ok, how does that actually work, if you allocate two structs in the
> segment and start to resize them?
e);
>
> I think there's a tacit assumption here that if you want to be able to
> resize a struct, it must be the only struct in the segment. If so,
> what's the point of having a named struct in the segment in the first place?
>

A segment may contain multiple structures but only the structure
allocated at the end of the segment is allowed to be resizable. If a
structure other than the end structure is tried to be resized,
following Assert will fail.
Assert((char *) segment->ShmemBase + ShmemAllocator->free_offset ==
(char *) result->location + result->allocated_size.

Please note the comment above this assertion is a bit misleading. Will
fix it in the next set of patches. Though in the attached patches we
allocate only one structure, buffer blocks, in BUFFERS_SHMEM_SEGMENT
(apart from PGShmemHeader and ShmemAllocator), it doesn't need to be
the only structure in the segment.

I may be missing something in your question, but the point of having a
named struct in the segment is to be able to discover it from
ShmemIndex.

> I propose this API instead:
>
> void
> ShmemInitStructExt(const char *name, Size size, bool *foundPtr, bool
> resizeable, Size max_size);
>
> void *
> ShmemResizeStruct(const char *name, Size size);
>
> This completely hides the segment ids from the callers, it becomes
> shmem.c's internal business. If you call ShmemInitStructExt with
> resizeable==false, it can do the allocation from the main segment as
> usual. But if you pass resizeable==true, then it creates a separate
> segment for it, so that it can be resized.

This is interesting ... more on this later.

>
> What happens if you call ShmemInitStructExt() and the requested size
> doesn't match the current size?

If the caller wants to fetch an existing resizable structure, it
shouldn't be required to know its current size because it may not know
its correct size when fetching it. The code in the patch is written in
a way to be compliant with current APIs as much. But if we are
introducing new APIs, I think we don't need to be that compliant.

> Could you write a standalone test module in src/test/modules to
> demonstrate how to use the resizable shmem segments, please? That'd
> allow focusing on that interface without worrying all the other
> complexities of shared buffers.

From your writeup it seems like you are leaning towards creating the
shared memory segments on-demand rather than having predefined
segments as done in the patch. I think your proposal is interesting
and might create a possibility for extensions to be able to create
resizable shared memory structures.

Let me first describe what happens in the current design and then
describe how we can implement on-demand (vs. dynamic) shared memory
segments that you seem to be leaning towards.

In the current patches, there are two predefined shared memory
segments: a. MAIN_SHMEM_SEGMENT b. BUFFER_SHMEM_SEGMENT. Each shared
memory segment in shmem.c is backed by a PGShmemHeader returned by
PGSharedMemoryCreate(). Each of these segments have one
PGUsedShmemInfo entry in UsedShmemInfo array and one AnonShmemData
entry in AnonShmemInfo array. When estimating the shared memory size
in CalculateShmemSize(), the sizes returned by all its minions are
counted against the main shared memory segment. Only
BufferManagerShmemSize() adds memory size of the buffer blocks against
BUFFERS_SHMEM_SEGMENT in the given MemoryMappingSizes array.
CreateSharedMemoryAndSemaphores() then creates shared memory for the
two segments and initializes ShmemSegments for the same. The
properties of the shared memory like mmap address or shmids etc. are
placed in PGUsedShmemInfo or AnonShmemData as appropriate. These
structures are used at the time of detaching/reattaching the shared
memory segments. Please note that the output produced by
CalculateShmemSize() is also used to report the total shared memory
used by InitializeShmemGUCs().

A side-note: I didn't find any existing README which describes how we
create and manage the shared memory segment. I think we should
probably write one and place it in storage/ipc as a separate patch and
then update the relevant parts in these patchset. Similarly I think we
could introduce PGUsedShmemInfo and AnonShmemData as a separate
commit.

After the shared memory segments are created shared data structures
are created. All the modules, including any extensions, create their
shared memory structures in the main memory segment. Only the buffer
manager creates BufferBlocks array in BUFFERS_SHMEM_SEGMENT, for which
it uses ShmemInitStructInSegment(). When resizing the buffer pool, the
shared memory segment is resized using PGSharedMemoryResize() and the
buffer blocks array is resized using ShmemResizeStructInSegment(). In
the current infrastructure ShmemInitStructExt() can not create a
shared memory segment right before allocating the data structure as
you suggest.

Since the shared memory segments are predefined, a test module, as you
suggest, does not have a segment that it can use. That led me to think
that you are imagining some kind of on-demand shared memory segments
that extensions or test modules can use. I think that will be a useful
feature by itself. Just as an example, imagine an extension to provide
shared plan cache which resizes the plan cache as needed. However, we
need to make sure that these on-demand shared memory segments work
well with CalculateShmemSize(), PGSharedMemoryDetach(),
AnonymousShmemDetach() etc. I can think of two ways to do this

1. In the current infrastructure, declare NUM_MEMORY_MAPPINGS to be
some larger number e.g. 10 to support 10 on-demand shared memory
segments. An internal module like BufferManagerShmemSize() directly
adds its sizes to the MemoryMappingSizes corresponding to the segment
of its choice (e.g. BUFFER_SHMEM_SEGMENT). Somehow we declare the
number of segments reserved for the internal modules. Each module then
adds its size requirements to the required number of
MemoryMappingSizes[] entries that is passed to shmem_request_hook. If
the number of entries used by all the modules higher than
NUM_MEMORY_MAPPING, the server can not start. These entries are later
transferred to MemoryMappingSizes[] that is passed to
CalculateShmemSize(). Each module is required to remember the
segment_ids it grabbed. CreateSharedMemoryAndSemaphores() then creates
the shared memory segments. The internal and external modules allocate
shared structures in the segments that they grabbed respectively. It
requires passing segment_id to ShmemInitStructExt() though.

2. There is no predefined limit on the number of segments. When
CalculateShmemSize() is called, BufferManagerShmemSize() outputs the
shared memory required in the main segment and total of shared memory
required in the other segments. Similarly shmem_request_hook of each
external module outputs the shared memory required in the main segment
and the total of shared memory required in the other segments. The
main segment is created in CreateSharedMemoryAndSemaphores() directly
using the requested size. The other output size is merely used for
reporting purposes in InitializeShmemGUCs(). When allocating shared
data structures, the main shared memory data structures are allocated
using ShmemInitStruct(), whereas the data structures in other memory
segments are allocated using ShmemInitStructExt() which takes
immediate allocation size and size of address space to be reserved as
arguments. It does not require segment_id though. For every new data
structure that ShmemInitStructExt() encounters, it a. creates a new
shared memory segment using PGSharedMemoryCreate(), b. allocates that
structure with the initial size. This function also needs to create
the PGShmemInfo and AnonShmemData entries corresponding to new
segments and make them available to PGSharedMemoryDetach(),
AnonymousShmemDetach() etc. For that we create a shared hash table in
the main shared memory segment (just like ShmemIndex) where we store
the metadata against each segment name (by coining it from the name of
the structure). We expect ShmemInitStructExt() to allocate structures
and segments only in the Postmaster and only at the beginning. When a
backend starts, it pulls the segment metadata from the shared hash
table which is ultimately used by PGSharedMemoryDetach(),
AnonymousShmemDetach(). At run time any backend which has access to
the shared memory should be able to call ShmemResizeStruct() given a
resizable structure and new size. Some higher level synchronization is
needed to ensure that the same structure is not resized simultaneously
by two backends.

The first approach is simple but has limited use given the fixed
number of segments. Second is more flexible but that's some work. I am
not sure whether it's worth doing all that if there are hardly any
extensions which could use resizable shared data structures.

Please let me know if you had something else in your mind when you
suggested a test module.

The first patch needs some clean up and work to make it committable.
But I was focusing on synchronization which still needs to be fleshed
out. However, if you think that a resizable shared memory segment is a
useful feature by itself that we can target for PG 19, I will focus on
making it committable.

[1] https://www.postgresql.org/message-id/qltuzcdxapofdtb5mrd4em3bzu2qiwhp3cdwdsosmn7rhrtn4u@yaogvphfwc4h

--
Best Wishes,
Ashutosh Bapat

Attachment Content-Type Size
v20260209-0001-Add-a-view-to-read-contents-of-shared-buff.patch application/x-patch 14.8 KB
v20260209-0002-Memory-and-address-space-management-for-bu.patch application/x-patch 95.2 KB
v20260209-0003-Allow-to-resize-shared-memory-without-rest.patch application/x-patch 147.0 KB

In response to

Responses

Browse pgsql-hackers by date

  From Date Subject
Next Message Matheus Alcantara 2026-02-09 15:36:55 Re: Change COPY ... ON_ERROR ignore to ON_ERROR ignore_row
Previous Message Heikki Linnakangas 2026-02-09 15:03:25 Re: Add 64-bit XIDs into PostgreSQL 15