PostgreSQL kļūda "could not resize shared memory segment ... No space left on device" mulsina, jo datu sējumā parasti ir daudz vietas. Runa nav par šo disku. PostgreSQL mēģina piešķirt dinamisko koplietojamo atmiņu paralēlam vaicājumam, šī atmiņa atrodas /dev/shm, un /dev/shm ir pilns. Docker vidē tas ir tikai 64 MB, ja vien nenorādāt citādi, tāpēc šī kļūda ir tik bieža konteineros un tik reta uz fiziskiem serveriem.
Pilns ziņojums izskatās šādi (segmenta nosaukums un izmērs atšķirsies):
ERROR: could not resize shared memory segment "/PostgreSQL.2051486325" to 67145056 bytes: No space left on device
Tipisks scenārijs: izstrādes vidē datubāze strādā labi, bet produkcijā viena atskaite, analītikas vaicājums vai liels join beidzas ar šo kļūdu, dažreiz tikai tad, kad vienlaikus darbojas vairāki šādi vaicājumi.
Kas īsti tiek palielināts
Kad PostgreSQL sāk paralēlu operāciju, process, kas to uzsāk, vispirms izveido dinamiskās koplietojamās atmiņas segmentu, kas pastāv visas operācijas laikā. Darba procesi (workers) tam pievienojas, lai apmainītos ar stāvokli, kļūdām un datiem. Tas aprakstīts pirmkoda failā README.parallel. Paralēls hash join iet vēl tālāk: tā vietā, lai katrs process būvētu savu kopiju, paralēlo plānu dokumentācija skaidro, ka procesi kopā būvē vienu koplietojamu hash tabulu, un arī tā atrodas koplietojamajā atmiņā.
To, kā šī atmiņa tiek iegūta, nosaka dynamic_shared_memory_type. Resursu konfigurācijas dokumentācija uzskaita posix (shm_open), sysv, windows un mmap un saka, ka pirmā atbalstītā iespēja parasti ir platformas noklusējums, tāpēc Linux vidē parasti redzēsiet posix. Linux vidē POSIX koplietojamās atmiņas objekti ir faili tmpfs failu sistēmā, kas parasti piemontēta /dev/shm.
Kļūdas teksts nāk no dsm_impl.c. Linux vidē PostgreSQL apzināti izsauc posix_fallocate() jaunajam segmentam. Komentārs kodā paskaidro, kāpēc: ja tas vienkārši paplašinātu failu, tmpfs piešķirtu lapas tikai tad, kad tām piekļūst, un vietas trūkums tad nogalinātu procesu ar SIGBUS. Piešķirot vietu jau iepriekš, PostgreSQL saņem tīru ENOSPC kļūdu un atceļ tikai vaicājumu. Tātad ziņojums ir PostgreSQL pašaizsardzība, nevis bojājuma pazīme.
Kāpēc konteineri to sasniedz pirmie
Oficiālā postgres Docker attēla dokumentācija to saka tieši: noklusējuma /dev/shm izmērs konteineriem ir 64 MB, un, kad koplietojamā atmiņa ir izsmelta, jūs saņemat tieši šo kļūdu. Tā iesaka docker run komandai nodot --shm-size (piemērā 256MB) vai līdzvērtīgu shm_size docker compose failā.
Aptuvena sajūta, kāpēc 64 MB ir tik maz: work_mem pēc noklusējuma ir 4 MB, un hash operācijas drīkst izmantot work_mem reiz hash_mem_multiplier (pēc noklusējuma 2.0). Dokumentācija arī brīdina, ka tādi limiti kā work_mem attiecas uz katru paralēlo darba procesu atsevišķi un ka sarežģīts vaicājums var vienlaikus veikt vairākas kārtošanas un hash operācijas. Palieliniet work_mem līdz atskaitēm reālistiskai vērtībai, ļaujiet vienlaikus darboties dažiem paralēliem vaicājumiem, un 64 MB tmpfs ir beigusies. Ārpus konteineriem /dev/shm parasti ir tmpfs bez tieši norādīta izmēra, un kodola tmpfs noklusējums ir puse no fiziskās RAM, tāpēc tas pats vaicājums tur reti sūdzas.
Apstipriniet cēloni divās minūtēs
Pārbaudiet /dev/shm izmēru un aizpildījumu tur, kur PostgreSQL patiešām darbojas:
# konteinerā vai uz hosta
df -h /dev/shm
ls -la /dev/shm | head # PostgreSQL.NNNN faili ir aktīvi DSM segmenti
docker inspect --format '{{.HostConfig.ShmSize}}' my-postgres
Tad apskatiet iestatījumus, kas nosaka, cik daudz dinamiskās koplietojamās atmiņas vaicājumi pieprasa:
SHOW dynamic_shared_memory_type;
SHOW min_dynamic_shared_memory;
SHOW max_parallel_workers_per_gather;
SHOW work_mem;
SHOW hash_mem_multiplier;
Ja /dev/shm ir 64M, dynamic_shared_memory_type ir posix un neizdevušā vaicājuma EXPLAIN plānā redzams Gather vai Parallel Hash, atbilde ir rokā. Ja /dev/shm ir liels un tomēr pildās, paskatieties, kas vēl tur raksta: citi procesi tajā pašā konteinerā vai pod to koplieto.
1. risinājums: piešķiriet /dev/shm saprātīgu izmēru
Šis ir īstais risinājums. Docker un docker compose vidē:
docker run -d --name my-postgres --shm-size=1g postgres:18
# docker compose
services:
db:
image: postgres:18
shm_size: 1gb
Kubernetes nav tieša --shm-size ekvivalenta; /dev/shm izmēru nosaka konteineru izpildvide, tāpēc pārbaudiet to ar df pod iekšienē. Ierastā pieeja ir piemontēt tam pāri atmiņā balstītu emptyDir:
spec:
containers:
- name: postgres
volumeMounts:
- name: dshm
mountPath: /dev/shm
volumes:
- name: dshm
emptyDir:
medium: Memory
sizeLimit: 1Gi
Divas detaļas no Kubernetes sējumu dokumentācijas šeit ir svarīgas. Ar medium: Memory sējums ir tmpfs, un tajā ierakstītie faili tiek ieskaitīti tā konteinera atmiņas limitā, kas tos ierakstīja. Un, ja izmērs nav norādīts, atmiņā balstīts sējums tiek izmērots pēc mezgla pieejamās (allocatable) atmiņas. Tāpēc iestatiet sizeLimit un iekļaujiet sagaidāmo koplietojamo atmiņu pod atmiņas limitā, citādi šīs kļūdas risinājums pārvērtīsies OOM kill. Ja izmantojat operatoru, vispirms pārbaudiet tā dokumentāciju: daži PostgreSQL operatori jau montē koplietojamo atmiņu šādi vai piedāvā tam iestatījumu.
Cik lielu? Dokumentācijā formulas nav. Saprātīga pieeja ir sākt ar dažiem simtiem megabaitu līdz gigabaitam instancei, kurā darbojas analītika, vērot df -h /dev/shm smagāko atskaišu laikā un vajadzības gadījumā palielināt. Tā ir RAM, tāpēc tai jāietilpst blakus shared_buffers un visam pārējam.
2. risinājums: samaziniet to, ko vaicājumi pieprasa
Ja konteineru tieši tagad mainīt nevarat, varat panākt, lai vaicājumi pieprasa mazāk. Paralēlie vaicājumi sesijā ir izslēgti, ja max_parallel_workers_per_gather ir 0:
-- ātrs atvieglojums vienai atskaitei vai sesijai
SET max_parallel_workers_per_gather = 0;
-- vai konkrētai atskaišu lomai
ALTER ROLE reporting SET max_parallel_workers_per_gather = 1;
Tas šiem vaicājumiem novērš kļūdu uz paralēlisma rēķina, tāpēc tas ir pagaidu līdzeklis, nevis konfigurācija, ko paturēt. Agresīva work_mem vai hash_mem_multiplier samazināšana atskaišu lomām arī samazina koplietojamo hash tabulu izmēru. Un bieži īstā problēma ir pats vaicājums: hash join pār desmitiem miljonu rindu, kur varēja izmantot indeksu. Mūsu lēno vaicājumu optimizācijas kontrolsaraksts apraksta, kā tādus atrast un salabot.
Un kā ar min_dynamic_shared_memory?
min_dynamic_shared_memory servera startā rezervē atmiņu paralēlajiem vaicājumiem; noklusējums ir 0. Kad šis apgabals ir nepietiekams vai izlietots, jaunie paralēlie vaicājumi atgriežas pie atmiņas piešķiršanas no operētājsistēmas ar dynamic_shared_memory_type. Tā kā rezervētais apgabals ir daļa no galvenās koplietojamās atmiņas, kas piešķirta startā, nevis fails /dev/shm, tas var palīdzēt, un dokumentācija norāda, ka tam var nākt par labu huge pages. Taču tā ir fiksēta rezervācija, kas tiek atņemta no RAM neatkarīgi no tā, vai tiek izmantota, un viss, kas pārsniedz to, joprojām iet uz /dev/shm. Uztveriet to kā papildu regulēšanu pareizi izmērotam /dev/shm, nevis kā aizstājēju. Tā maiņai vajag restartu.
Kā novērst nākamo reizi
- Iestatiet koplietojamo atmiņu tieši katrā konteinera definīcijā, kas darbina PostgreSQL, gan izstrādē, gan produkcijā, lai vaicājums, kas strādā testos, strādātu arī produkcijā.
- Izmēru plānojiet, ņemot vērā
work_memun paralēlisma iestatījumus.work_memvaimax_parallel_workers_per_gatherpalielināšana, neskatoties uz/dev/shm, ir klasisks veids, kā izraisīt šo kļūdu. - Uzskatiet to par atmiņu. Kubernetes vidē koplietojamās atmiņas sējums tiek ieskaitīts pod atmiņas limitā; ieplānojiet to budžetā.
- Iestatiet brīdinājumu par šo kļūdas ziņojumu PostgreSQL žurnālos. Tas ir precīzs un gandrīz nekad nav viltus trauksme.
Galvenās atziņas
- "could not resize shared memory segment ... No space left on device" nozīmē, ka pilns ir
/dev/shm, nevis datu disks. - Paralēlie vaicājumi, īpaši paralēlie hash join, tur piešķir dinamisko koplietojamo atmiņu; ar
posixLinux vidē tā ir tmpfs. - Docker pēc noklusējuma
/dev/shmir 64 MB. Izmantojiet--shm-sizevaishm_size; Kubernetes vidē piemontējiet atmiņā balstītuemptyDirarsizeLimit. max_parallel_workers_per_gather = 0ir ātrs apvedceļš, nevis risinājums.- Koplietojamā atmiņa ir RAM: iekļaujiet to atmiņas limitos un plānojiet kopā ar
work_mem.