In this lesson, you will learn how to design custom PostgreSQL extensions that exchange data between multiple backend processes using a shared memory segment.
The Problem: Process Isolation
PostgreSQL is a multi-process architecture. Each client connection is handled by a separate "backend" process that does not share its local memory with others. If you want two separate sessions to coordinate state—such as tracking a custom counter or maintaining a global configuration cache—local variables won't work. Instead, you must allocate a block of memory during server startup that persists globally across the entire database cluster.
Mechanism: shmem_request_hook and ShmemInitStruct
To facilitate this communication, your extension must hook into the server’s initialization phase.
- Requesting Memory: You use the
shmem_request_hookto reserve a specific amount of bytes. PostgreSQL allocates this block when the postmaster process starts. - Accessing the Block: Once the server is running, your backend processes call
ShmemInitStruct. This function looks up the named shared memory block. If it doesn't exist, it creates it; otherwise, it returns a pointer to the existing segment.
Code Illustration
Imagine you want to store a global int32 counter. In your C code, you would define a struct to hold your state and map it to shared memory:
typedef struct {
int32 global_counter;
LWLockId lock; // Crucial for concurrency
} MyExtensionData;
static MyExtensionData *my_data;
void MyExtensionInit(void) {
bool found;
my_data = ShmemInitStruct("my_extension_data", sizeof(MyExtensionData), &found);
if (!found) {
my_data->global_counter = 0;
my_data->lock = LWLockAssign(); // Initialize a lightweight lock
}
}
Critical Considerations
Because multiple processes will attempt to read and write to this memory block simultaneously, you must use PostgreSQL’s LWLock (Lightweight Lock) mechanism. Without locks, your extension will encounter race conditions, leading to data corruption or crashes. Treat the memory as a "critical section" where only one process can read or modify the values at a time, ensuring atomicity across the entire cluster.
Key Takeaway: Shared memory provides a high-performance, cross-process communication channel, but it requires manual memory allocation during startup and strict adherence to LWLock synchronization to prevent data races.