Această pagină poate fi doar citită. Poți vedea sursa, dar nu poți modifica pagina. Consultă administratorul dacă ești de părere că ceva este în neregulă. ====== De ce rm nu eliberează întotdeauna spațiul pe disc? ====== {{tag>linux administrare-sistem filesystem depanare disc}} Atunci când execuți comanda ''rm'' pe Linux, poți observa o situație aparent contradictorie: fișierul dispare din listările directorului, dar spațiul ocupat pe disc rămâne neschimbat. ''df'' arată același procent de utilizare ca înainte, deși ''du'' confirmă că fișierul nu mai există. Ce se întâmplă, de fapt? ===== Cum funcționează ștergerea pe Linux ===== ==== Inode-uri și legături (hardlinks) ==== Fiecare fișier de pe un sistem de fișiere Linux este reprezentat intern printr-un **inode** - o structură de date care conține metadatele fișierului (permisiuni, proprietar, dimensiune, pointeri către blocurile de date de pe disc) și un **contor de legături** (link count). Numele fișierului dintr-un director nu este decât o **legătură** (hardlink) către inode-ul respectiv. Același inode poate avea mai multe nume, în directoare diferite - toate sunt legături egale. ==== Ce face de fapt rm ==== Comanda ''rm'' apelează syscall-ul ''unlink()''. Acesta: - Elimină intrarea numelui din directorul părinte - **Decrementează** contorul de legături al inode-ului Blocurile de date de pe disc sunt eliberate **doar** când: - Contorul de legături ajunge la **zero** (nu mai există niciun nume care să refere inode-ul), **ȘI** - Nu există niciun **descriptor de fișier deschis** care să refere inode-ul Dacă un proces are fișierul deschis în momentul ștergerii, kernelul marchează inode-ul ca ''(deleted)'', dar păstrează blocurile alocate atâta timp cât procesul nu închide descriptorul. ===== Diagnosticarea problemei ===== ==== Găsirea fișierelor șterse care ocupă spațiu ==== Comanda de bază pentru identificarea fișierelor marcate ''(deleted)'' dar încă deschise de procese: <code bash> sudo lsof +L1 </code> Opțiunea ''+L1'' afișează fișierele al căror link count este mai mic decât 1 (adică zero - șterse din sistemul de fișiere, dar cu descriptori activi). Rezultatul arată ceva de forma: <code> COMMAND PID USER FD TYPE DEVICE SIZE/OFF NLINK NODE NAME nginx 1234 root 10w REG 8,1 2147483648 0 123456 /var/log/nginx/access.log (deleted) mysqld 5678 mysql 11w REG 8,1 5368709120 0 789012 /var/lib/mysql/binlog.000123 (deleted) </code> Câmpul **SIZE/OFF** arată câți octeți ocupă fișierul pe disc, iar **NLINK=0** confirmă că a fost șters. ==== Filtrarea rezultatelor ==== <code bash> # Filtrare după dimensiune (mai mare de 100MB) sudo lsof +L1 | awk 'NR==1 || $7 > 104857600' # Filtrare după serviciu specific sudo lsof +L1 | grep nginx # Sortare după dimensiune (descrescător) sudo lsof +L1 | sort -k7 -rn | head -20 </code> ==== Verificarea discrepanței df vs du ==== Dacă bănuiești că spațiul nu a fost eliberat, compară ieșirile: <code bash> # Spațiu real pe partiție (ce vede kernelul) df -h /var/log # Spațiu calculat din fișiere vizibile (ce vede du) du -sh /var/log </code> O diferență semnificativă între cele două indică fișiere ''(deleted)'' cu descriptori activi. Poți calcula și totalul spațiului blocat pe întreg sistemul: <code bash> sudo lsof +L1 | awk 'NR>1 {sum += $7} END {printf "Total blocat: %.2f GB\n", sum/1073741824}' </code> ==== Verificare rapidă cu /proc ==== Poți inspecta direct ce fișiere are deschis un proces (de exemplu PID 1234): <code bash> ls -la /proc/1234/fd/ | grep deleted </code> ===== Eliberarea spațiului ===== ==== Varianta 1: Repornirea serviciului responsabil ==== Cea mai simplă soluție - procesul care ține fișierul deschis va primi semnalul SIGTERM, va închide descriptorii, iar kernelul eliberează blocurile: <code bash> sudo systemctl restart nume-serviciu </code> **Avantaj:** simplu, curat, resetează și bufferele interne ale serviciului. **Dezavantaj:** produce o întrerupere scurtă a serviciului (downtime). Pentru servicii critice, planifică o fereastră de mentenanță sau folosește varianta cu truncate de mai jos. ==== Varianta 2: Golirea fișierului fără a închide descriptorul ==== Aceasta este **metoda recomandată pentru fișiere de log** - nu rupe descriptorul de fișier al procesului, deci nu există niciun risc de erori sau pierderi de date în aplicație. <code bash> # Metoda 1 - redirecționare shell > /cale/catre/fisier.log # Metoda 2 - truncate (mai explicită, preferată în scripturi) truncate -s 0 /cale/catre/fisier.log </code> Operatorul ''>'' deschide fișierul în mod scriere cu trunchiare și îl închide imediat, fără să scrie nimic. Procesul care ținea fișierul deschis continuă să scrie în același descriptor - acum la offset 0. Ambele metode sunt echivalente. ==== De ce nu rm + touch? ==== <code bash> # GREȘIT pentru loguri active: rm /var/log/nginx/access.log touch /var/log/nginx/access.log </code> ''rm'' șterge intrarea din director, dar procesul continuă să scrie în inode-ul vechi. ''touch'' creează un fișier nou cu un inode nou, complet diferit. Rezultatul: procesul ignoră fișierul nou și scrie în continuare în inode-ul ''(deleted)''. Ai acum două probleme: spațiu blocat și log-uri pierdute. Operatorul ''>'' și ''truncate'' modifică fișierul **existent**, la același inode - procesul vede modificarea imediat. ===== Automatizarea cu logrotate ===== Pe sisteme de producție, rotația logurilor se face cu ''logrotate'', configurat cu directiva ''copytruncate'' - exact mecanismul descris mai sus. Exemplu de configurare ''/etc/logrotate.d/nginx'': <code> /var/log/nginx/*.log { daily missingok rotate 14 compress delaycompress notifempty copytruncate } </code> ''copytruncate'' copiază conținutul curent al fișierului într-un fișier backup, apoi trunchiează originalul - fără a semnaliza procesul să redeschidă fișierul. ===== Alte cauze ale discrepanței df/du ===== Fișierele cu descriptori activi nu sunt singura cauză. Alte situații în care ''df'' și ''du'' nu sunt de acord: ^ Cauză ^ Explicație ^ | **Fișiere șterse cu FD activi** | Subiectul principal al acestui articol | | **Snapshot-uri filesystem** | Btrfs, ZFS sau LVM snapshots ocupă spațiu invizibil pentru ''du'' | | **Fișiere cu găuri (sparse files)** | ''du'' raportează dimensiunea logică, nu blocurile fizice alocate | | **Sistemul de fișiere rezervat** | ext4 rezervă ~5% pentru root (''reserved-blocks-count'') | | **Atribute extinse (xattrs)** | Metadate suplimentare ocupă spațiu necontabilizat de ''du'' | ===== Rezumat ===== * ''rm'' apelează ''unlink()'' și elimină doar **numele** fișierului, nu datele * Blocurile de disc sunt eliberate abia când **link count = 0 ȘI niciun descriptor nu e deschis** * Folosește ''sudo lsof +L1'' pentru a găsi fișierele ''(deleted)'' cu descriptori activi * Repornește serviciul responsabil (cu downtime) sau trunchiază fișierul cu ''>'' sau ''truncate -s 0'' (fără downtime) * Pentru loguri, **niciodată** ''rm'' + ''touch'' pe fișiere active - folosește întotdeauna truncate ===== Resurse suplimentare ===== * [[https://man7.org/linux/man-pages/man2/unlink.2.html|man 2 unlink]] - documentația syscall-ului * [[https://man7.org/linux/man-pages/man8/lsof.8.html|man 8 lsof]] - opțiunile complete ale lsof * [[https://linux.die.net/man/8/logrotate|man 8 logrotate]] - configurarea rotației automate a logurilor