磁盘没满却写不进去:No space left on device 其实是 inode 耗尽
现象:磁盘没满,程序却写不进文件
有个批量导入服务在跑了几小时后开始报错,日志里反复出现:
OSError: [Errno 28] No space left on device
第一反应是磁盘满了,但运维查 df -h 发现使用率才 50% 左右,inode 使用率也显示 50%。既然两个指标都有一半空间,问题出在哪?
这里有个容易被忽略的点:df -h 显示的是数据块的使用情况,而 df -i 显示的是 inode 的使用情况。两者是独立的两套配额。inode 用满了,即使数据块还有大量空闲,新文件照样创建不了,因为系统找不到空闲的目录项来记录这个文件。
先分清是哪一种"满"
df 的输出里有两行关键信息:
Filesystem Size Used Avail Use% Mounted on
/dev/vdb1 100G 50G 50G 50% /
Use%是按数据块算的,超过 90% 需要清理空间。Use%出现在IUse%列且接近 100%,说明是 inode 耗尽。
一条命令同时看两者:
df -h /var /tmp && df -i /var /tmp
如果 df -i 的 IUse% 已经 100%,那磁盘"看起来还有空间"就是错觉。
定位是谁吃掉了 inode
inode 数量和目录下的文件数直接相关,所以找问题要沿着目录树往下走:
# 找出 inode 占用最多的前十个目录
sudo find / -xdev -type d -print0 2>/dev/null | \
sudo xargs -0 -I{} sh -c 'echo "$(ls -A "{}" | wc -l) {}"' | \
sort -rn | head -10
重点排查这几个常见位置:
- 应用日志目录:日志切分产生了成千上万个历史文件,每个文件都占一个 inode。
- 临时目录:CI 构建产物、临时解压目录、缓存分片。
- 数据库 binlog:MySQL 的 binlog 按大小滚动正常,但按时间滚动且保留期很长时,文件数量会失控。
- session / 队列目录:以每个会话一个文件的方式存储数据。
找到嫌疑目录后,看具体是什么在增长:
ls -lS /var/log/app | head -20
find /var/log/app -type f -mtime -1 | wc -l
第二条命令特别有用——统计最近一天新增的文件数,如果远大于预期,说明还有程序在持续制造小文件。
为什么小文件比大文件更容易撑爆 inode
一个 1MB 的文件和一个 1KB 的文件,占用的数据块几乎一样多,但各占一个 inode。inode 的数量级是十万到百万,如果业务按"一个订单一个文件""一次任务一个临时文件"的方式写入,一天就能生成几十万甚至上百万个 inode。
另一个容易被忽略的场景:目录本身也会消耗内存和 inode,但它是独立于文件计数的。大量空目录同样会拖慢文件系统的元数据操作。
修复与清理
确认是可清理文件后,先确认没有进程正在占用:
sudo lsof +L1 | head -20
+L1 表示已删除但仍被进程持有的文件。这类文件不占 inode,但会占着磁盘空间不放,是另一个常见的"空间没释放"来源。
清理时优先用 find 按时间筛,而不是 rm -rf 整个目录:
# 删除 30 天前的日志,保留目录结构
sudo find /var/log/app -type f -name "*.log" -mtime +30 -delete
对正在写入的目录,更稳妥的做法是先轮转、再删除:把当前日志改名,进程会重新创建新文件,然后删除改名的旧文件。这样不会出现应用因为文件句柄丢失而报错。
如果发现是某个程序的写法有问题,根因要修在程序侧。比如把"每个订单一个文件"改成按日期分目录、单文件追加写入,或者直接落到数据库 / 对象存储上。
预防:把 inode 纳入监控
inode 耗尽往往来得比磁盘空间更突然,因为它是一个离散的上限值,等到监控报警时往往已经很满了。建议把 df -i 的使用率和 find 统计的日新增文件数一起纳入告警,并且在配置日志切分时明确设定保留文件数量上限,而不只设定单文件大小或保留天数——很多日志轮转策略只控制体积,不控制数量。
排查这类问题的通用思路是:先分清是数据块还是 inode,再用 find 沿目录树定位,最后从程序侧根治。数据块满了和 inode 满了,症状相似但抓手完全不同,把它们当成两个独立指标来盯,才不至于在"磁盘明明没满"的困惑里绕圈子。
- 点赞
- 收藏
- 关注作者
评论(0)