Postgres 'Could Not Resize Shared Memory Segment' Is Not Your Disk
A report that ran fine on your laptop dies in staging. The error says no space left on device, so you SSH
in and run df -h, and the disk is 34 percent full. You run docker system prune
anyway, because what else is there. You grow the volume. Same error, same query, same second.
The message is technically correct and completely misleading. Postgres is not out of disk. It is out of
shared memory, because the container runtime gave it a 64 MB /dev/shm and a parallel query wanted
more than that. The filesystem that is full is a tmpfs living in RAM, and it has nothing to do with the volume
your data is on.
raise the container's shared memory allocation. Docker defaults
/dev/shm to 64 MB regardless of how much memory the container has.
docker run --shm-size=1g postgres:17- In Compose, add
shm_size: 1gbto the service. - On Kubernetes, mount an
emptyDirwithmedium: Memoryat/dev/shm. - Check it worked with
docker exec <container> df -h /dev/shm. - If it still fails, lower
work_memormax_parallel_workers_per_gather. The query genuinely wants more memory than you have.
The exact error
ERROR: could not resize shared memory segment "/PostgreSQL.938232807" to 55334241 bytes:
No space left on device
You will meet it wrapped by whichever driver you use, which is why the search results are so scattered:
pq: could not resize shared memory segment "/PostgreSQL.1394142663" to 12615680 bytes: No space left on device
PG::DiskFull: ERROR: could not resize shared memory segment "/PostgreSQL.938232807" to 55334241 bytes: No space left on device
psycopg2.errors.DiskFull: could not resize shared memory segment ... No space left on device
Note DiskFull in two of those. The SQLSTATE Postgres returns is 53100, which drivers name
"disk full", and that naming sends every developer who sees it to the wrong filesystem. The segment name
starting /PostgreSQL. is the tell: that is a POSIX shared memory object, not a file on your data
volume.
Why a memory problem reports itself as a disk problem
Postgres uses two different kinds of shared memory, and only one of them is involved here.
The main shared memory area, which holds shared_buffers, is allocated once at startup. On
Linux with default settings that comes from an anonymous mapping and never touches
/dev/shm. Lowering shared_buffers therefore does nothing for this error, which is
worth knowing because it is the first thing most advice tells you to try.
Dynamic shared memory is different. When Postgres runs a query in parallel, the leader process and its
worker processes need a region they can all see: a shared hash table for a parallel hash join, a shared tuple
store, the shared state for a parallel bitmap heap scan. That region is allocated at query time and released
when the query finishes. With the default dynamic_shared_memory_type = posix on Linux, those
segments are POSIX shared memory objects, and on Linux POSIX shared memory is implemented as files in
/dev/shm, which is a tmpfs.
So when a parallel hash join asks for 55 MB of dynamic shared memory and the tmpfs at
/dev/shm is 64 MB with other segments already in it, the underlying ftruncate fails
with ENOSPC. Postgres reports what the kernel told it. The kernel said no space left on device,
because from its point of view a full tmpfs is a full device.
Docker's default /dev/shm size is 64 MB. That number was chosen a long time ago for
general-purpose containers and has nothing to do with your container's memory limit. A Postgres container with
a 16 GB memory limit still gets 64 MB of shared memory unless you say otherwise.
The fix, step by step
Step 1: Confirm the diagnosis in ten seconds
docker exec -it my-postgres df -h /dev/shm
Filesystem Size Used Avail Use% Mounted on
shm 64M 64M 0 100% /dev/shm
64M is the default and is the smoking gun. If you catch it mid-query you will see it at or near 100 percent used. Meanwhile the data volume is fine:
docker exec -it my-postgres df -h /var/lib/postgresql/data
Step 2: Give the container real shared memory
For docker run:
docker run -d --name my-postgres \
--shm-size=1g \
-e POSTGRES_PASSWORD=... \
-v pgdata:/var/lib/postgresql/data \
postgres:17
For Compose, it is a service-level key:
services:
db:
image: postgres:17
shm_size: 1gb
volumes:
- pgdata:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
You must recreate the container for this to apply. docker compose up -d will do it;
docker compose restart will not, and that catches people.
How big? 1 GB is a sane starting point for a general workload. It is a ceiling, not a reservation, so a tmpfs sized at 1 GB that only ever holds 80 MB costs you 80 MB. Do not set it to something like 32 GB on a 16 GB host, though. Memory-backed tmpfs pages count against the container's memory, and filling one is a straightforward route to the OOM killer arriving and choosing a victim for you, which is a far worse failure than a cancelled query.
Step 3: On Kubernetes, mount a memory-backed emptyDir
spec:
containers:
- name: postgres
image: postgres:17
volumeMounts:
- name: dshm
mountPath: /dev/shm
resources:
limits:
memory: 8Gi
volumes:
- name: dshm
emptyDir:
medium: Memory
sizeLimit: 1Gi
medium: Memory makes it a tmpfs, and sizeLimit caps it. Remember that a
memory-backed emptyDir counts against the pod's memory limit, so the sizeLimit has to fit inside
resources.limits.memory with room for Postgres itself. If you run Postgres through an operator,
check its documentation first, since most of them manage the /dev/shm mount for you and will
overwrite a hand-edited StatefulSet.
Step 4: Reduce demand if the error survives a bigger tmpfs
At this point the query genuinely wants a lot of memory, and the parallel plan multiplies it. Two knobs, in order of preference:
-- Per session, to test the hypothesis quickly
SET max_parallel_workers_per_gather = 0;
-- rerun the query. If it now succeeds, parallelism was the multiplier.
-- Per session, to see whether the hash is simply too large
SET work_mem = '32MB';
Then look at the plan rather than guessing:
EXPLAIN (ANALYZE, BUFFERS) SELECT ...;
A Parallel Hash Join node whose hash side is far larger than you expected usually means a
missing index, a join that is not selective, or a filter applied after the join instead of before it. Fixing
the plan is better than feeding it more memory, because the memory requirement grows with your data and the
index does not.
Verification
docker exec -it my-postgres df -h /dev/shm
Filesystem Size Used Avail Use% Mounted on
shm 1.0G 8.0K 0% /dev/shm
Then run the query that was failing, and while it runs, watch the segments appear:
docker exec -it my-postgres sh -c 'ls -lh /dev/shm | head'
You should see files named PostgreSQL.<number> appear during execution and disappear when
it completes. If they linger after the query ends, you have a backend that crashed rather than exited cleanly,
which is a different problem worth chasing.
Finally, confirm from inside the database:
SHOW dynamic_shared_memory_type; -- expect: posix
SHOW max_parallel_workers_per_gather;
SHOW work_mem;
What people get wrong
- "Free up disk space." The most common response and always wrong. The full device is a tmpfs in RAM. Pruning images, truncating logs, and growing the volume change nothing.
- "Lower shared_buffers." With Postgres's default settings on Linux,
shared_buffersdoes not come from/dev/shmat all. This trades away cache performance for no benefit. - "Set max_parallel_workers_per_gather to 0 permanently." A legitimate emergency lever, and a bad permanent setting. You have disabled parallel query across the whole instance to work around a 64 MB default that takes one line of YAML to fix.
- "Use --ipc=host." This does make the host's larger
/dev/shmavailable, and it also removes the IPC namespace boundary between the container and the host. Use--shm-size, which solves the same problem without giving that up. - "It only happens under load, so it must be a connection or disk issue." It only happens under load because concurrent parallel queries share one 64 MB tmpfs. Two large reports at the same time is exactly the condition that fills it.
When it is still broken
- Check whether something else in the container uses /dev/shm. Chrome or a headless browser in the same container will eat it. This is the other classic 64 MB victim.
- Look at concurrency, not just the single query. One query needing 200 MB is fine at 1 GB; eight of them at once is not. Cap concurrency for heavy reports, or route them to a replica.
- Check the host. On a plain VM,
/dev/shmdefaults to half of RAM, so if you are seeing this outside a container the cause is different and probably really is memory pressure. Comparefree -hagainst what the query is asking for. - Consider whether the report belongs in Postgres at all. A query that needs hundreds of megabytes of hash space over a growing table is a pre-aggregation waiting to happen. A nightly summary table turns a fragile 40 second report into a 20 millisecond one.
The habit worth taking from this: when an error message names a resource, verify that the resource it names is the one that ran out. Postgres told the truth. The device really was full. It just was not the device anyone went looking at.
Frequently asked questions
- Why does Postgres say 'No space left on device' when my disk is nearly empty?
- The device that is full is the tmpfs mounted at /dev/shm, not your data volume. Postgres allocates dynamic shared memory for parallel query workers as POSIX shared memory objects, which Linux implements as files in /dev/shm, and Docker defaults that filesystem to 64MB regardless of the container's memory limit. Run docker exec on the container with df -h /dev/shm to confirm.
- How do I increase /dev/shm for a Postgres container?
- Add --shm-size=1g to docker run, or shm_size: 1gb to the service in docker-compose.yml, then recreate the container because a restart will not apply it. On Kubernetes, mount an emptyDir with medium: Memory and a sizeLimit at /dev/shm, and make sure that size fits inside the pod's memory limit since memory-backed volumes count against it.
- Will lowering shared_buffers fix this error?
- No. With Postgres's default settings on Linux, shared_buffers is allocated from an anonymous mapping at startup and never uses /dev/shm. Only dynamic shared memory, which parallel query workers allocate at query time, lives there. Lowering shared_buffers costs you cache performance and does nothing for this error.
- Should I just disable parallel query to stop the error?
- Setting max_parallel_workers_per_gather to 0 is a valid emergency lever and a useful diagnostic, because if the query then succeeds you have confirmed parallelism was the multiplier. It is a poor permanent setting though, since it disables parallel execution across the whole instance to work around a 64MB container default that takes one line of configuration to fix.