Re: Changing shared_buffers without restart - Mailing list pgsql-hackers
| From | Ashutosh Bapat |
|---|---|
| Subject | Re: Changing shared_buffers without restart |
| Date | |
| Msg-id | CAExHW5tSw8r06RLAArvf923cO4NGetitPhQ7AO0o7hsKx8jsNw@mail.gmail.com Whole thread Raw |
| In response to | Re: Changing shared_buffers without restart (Heikki Linnakangas <hlinnaka@iki.fi>) |
| Responses |
Re: Changing shared_buffers without restart
Re: Changing shared_buffers without restart |
| List | pgsql-hackers |
On Sun, Feb 8, 2026 at 5:14 AM Heikki Linnakangas <hlinnaka@iki.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
pgsql-hackers by date: