</>CodeWithKarani

Postgres 'Could Not Resize Shared Memory Segment' Is Not Your Disk

Karani GeoffreyKarani Geoffrey7 min read

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: 1gb to the service.
  • On Kubernetes, mount an emptyDir with medium: Memory at /dev/shm.
  • Check it worked with docker exec <container> df -h /dev/shm.
  • If it still fails, lower work_mem or max_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.

Where a Postgres container's memory actually lives Container memory limit, for example 8 GB shared_buffers anonymous mapping, not /dev/shm /dev/shm tmpfs: 64 MB parallel query segments land here work_mem per worker private, per sort or hash node Parallel hash join asks for 55 MB of dynamic shared memory ftruncate on the tmpfs returns ENOSPC, Postgres reports "No space left on device" Data volume on disk: 34% used, 400 GB free Completely uninvolved. This is the filesystem everybody checks first.
Three different memory regions, one error message, and the one people investigate is the one that is fine.

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_buffers does not come from /dev/shm at 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/shm available, 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

  1. 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.
  2. 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.
  3. Check the host. On a plain VM, /dev/shm defaults to half of RAM, so if you are seeing this outside a container the cause is different and probably really is memory pressure. Compare free -h against what the query is asking for.
  4. 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.
#PostgreSQL#Docker#Kubernetes#Shared Memory#Query Performance
Keep reading

Related articles