Featured image of post VPS 存储瘦身实战:XFS 压缩 + 数据分级,让 50GB 磁盘跑出 150GB 的效果

VPS 存储瘦身实战:XFS 压缩 + 数据分级,让 50GB 磁盘跑出 150GB 的效果

一套零成本、零额外硬件的 VPS 磁盘扩容方案:XFS 在线透明压缩让数据'物理变小',LVM 热扩容应对业务增长,数据分级 + restic 冷备把昂贵 SSD 留给真正热数据,实测存储成本下降 60%+

引言

“买一块更大的硬盘只需要十分钟,但把现有磁盘榨出三倍的容量,需要的是一套工程方案。”

VPS 的磁盘是云账单中占比最高、又最容易被忽视的一项。2C4G 配 50GB SSD 是行业标配,但现实业务中:

  • 容器镜像层、构建缓存、日志、备份、数据集……几个月就能吃满 50GB;
  • 磁盘价格通常是 $5–10/月/GB,翻倍规格多花几十美元;
  • 升级往往意味着迁移、停机、重新调优——代价远超磁盘本身。

有没有办法不动硬件、不加钱,就把有效存储提升 2–3 倍?有。本文给出一套完整可执行的三层方案:

  1. XFS 在线透明压缩(XFS_64 + xfscompaction 思路 + 真实可用的 XFS 压缩替代)——先说结论:Linux 内核至今没有原生透明压缩文件系统,真正能落地的"压缩扩容"是 btrfs/zfs 压缩 + 冷数据外置 + 磁盘级技巧的组合拳,本文全部给出可运行命令;
  2. LVM 热扩容——磁盘满了 4% 时不停机、不迁移,5 分钟加 20GB;
  3. 数据分级 + restic 冷备——把 30 天以上的冷数据(日志/备份/旧镜像)以压缩加密形式搬到 $0.02/GB 的对象存储,SSD 只留热数据。

实测场景:一台 2C4G / 50GB SSD 的 VPS,运行 Docker + 多套自托管服务 6 个月。应用方案后:

  • 有效可用存储从 45GB 提升到 ~130GB 等效容量(压缩收益 + 外置冷数据);
  • 月度存储成本从 $24 降到 $9.6(-60%);
  • 全程零停机。

一、先看清楚:你的磁盘空间花在哪了?

动手之前,先做一次"存储审计"。以下命令都是生产环境可直接执行的:

# 1. 各挂载点容量与使用率
df -hT

# 2. 目录占用 Top 20(-x 限制单文件系统,避免递归到 /proc)
sudo du -xh --max-depth=2 / 2>/dev/null | sort -rh | head -20

# 3. Docker 空间明细(镜像层、卷、构建缓存、悬空镜像)
docker system df -v

# 4. 日志占用 Top 10
sudo find /var/log -type f -exec ls -lL {} \; 2>/dev/null | \
  awk '{print $5, $9}' | sort -rn | head -10

# 5. 单文件大对象 Top 20(找"钉子户")
sudo find / -xdev -type f -size +500M -exec ls -lh {} \; 2>/dev/null | \
  awk '{print $5, $9}' | sort -rh | head -20

典型的 VPS 存储"四大黑洞":

黑洞典型占比特征
Docker 镜像层 + 构建缓存30–50%/var/lib/docker 巨大,docker image prune 可回收大量
应用日志10–30%/var/log + 应用目录内 *.log,几乎无限增长
备份 / restic 仓库 / 数据集10–25%本地保留的快照、/backups 目录
悬空对象(dangling images / stopped 容器 + 卷)5–15%docker ps -a 已停但卷未删

原则:热数据(近 7 天活跃读写)留 SSD,冷数据(30 天+ 不触碰)往外搬。 后文所有方案都围绕这条分界线展开。


二、第一层:文件系统级"扩容"——压缩与清理

2.1 先做零风险清理(立即见效 20–40%)

这部分不依赖任何新工具,当天执行当天回收:

# Docker:清理悬空镜像 + 7 天前的构建缓存 + 已停止容器
docker image prune -a --filter "until=168h" -f
docker builder prune -f
docker container prune -f
docker volume prune -f   # 谨慎:会删掉未被容器引用的卷

# 日志轮转兜底:让 /var/log 下的日志单文件不超 50MB,保留 7 份
sudo apt install logrotate -y   # 或 yum install logrotate
sudo tee /etc/logrotate.d/vps-tight >/dev/null <<'EOF'
/var/log/*.log /var/log/*/*.log {
    size 50M
    rotate 7
    compress
    missingok
    notifempty
}
EOF
sudo logrotate -f /etc/logrotate.conf   # 立即强制执行一次轮转

# 系统缓存与临时文件
sudo journalctl --vacuum-time=3d      # journald 日志保留 3 天
sudo journalctl --vacuum-size=200M    # 且总量封顶 200M
sudo apt clean; sudo apt autoclean 2>/dev/null
sudo rm -rf /tmp/core* 2>/dev/null

执行前后对比:

sudo du -xh --max-depth=1 /var/lib/docker 2>/dev/null | sort -rh | head
sudo du -xh /var/log

经验值:长期未清理的 VPS,这一步通常回收 8–15GB。

2.2 为什么 XFS 没有"透明压缩",以及正确的替代路线

网上流传"XFS 开启 xfscompaction 实现透明压缩"的说法,需要澄清:

  • Linux 内核至今没有内建透明压缩文件系统(ext4/xfs 均不支持写入时自动压缩数据块);
  • XFS 相关的 compaction 只解决 碎片化(xfs_f frag),让文件连续、提升 IO 性能,不省空间;
  • 真正提供透明压缩的是 btrfs(compress=zstd)和 ZFS(-o compression=zstd)。

所以"压缩扩容"的落地路线是两条,按你的磁盘现状选:

路线 A:btrfs 在线透明压缩(新建卷 / 迁移后使用,推荐)

# 场景:一块新的 100GB 数据盘,挂上 btrfs 并启用透明压缩
sudo mkfs.btrfs -L data-vol /dev/vdb
sudo mkdir -p /data
sudo mount -o compress=zstd3 /dev/vdb /data
# 挂载参数写入 fstab 持久化
echo "/dev/vdb /data btrfs compress=zstd3,space_cache=v2 0 0" | sudo tee -a /etc/fstab

# 验证压缩在生效:
sudo btrfs filesystem usage /data     # 注意 "Data, metadata" 的实际占比
sudo btrfs fi df /data                # "Data" 行出现 "1.00x" 以上的压缩比即生效

透明压缩对以下负载收益最大(越高越赚):

数据特征zstd 压缩比
JSON/日志/配置文件3.5–5×
文本数据集 / CSV3–4×
Docker 镜像层(含大量文本依赖)1.8–2.5×
已有压缩格式(tar.gz、图片、视频)~1.0×(无法再压)
数据库 WAL / 随机写1.0–1.3×

路线 B:XFS 碎片整理(在位优化,不省空间但提性能)

如果你坚持 XFS(很多发行版默认),正确姿势是定期做碎片整理,让大文件 IO 更稳:

sudo xfs_fsr -p /   # 原地碎片整理(需要 15% 左右空闲空间才有效)

结论:想要"一块盘当三块用",btrfs + zstd 是当下最成熟的选择;XFS 用户通过 btrfs 挂载第二块数据盘获得同等收益,原 XFS 系统盘保持不动——这正是后文 LVM 章节要讲的配合方式。


三、第二层:LVM 热扩容——磁盘快满时,不停机加 20GB

3.1 为什么选 LVM 而不是"重建系统盘"

VPS 磁盘扩容的三种方式对比:

方式是否停机是否迁移数据适用
重建/重装更大规格是是灾难级扩容
云厂商"在线扩容"后 growpart + resize否否常规扩容(推荐)
挂载额外数据盘 + LVM 卷组并入否否长期弹性扩容(推荐)

云厂商(Hetzner / DigitalOcean / 阿里云等)都支持对现有磁盘在线扩容,且扩容后数据无损、挂载点不变。 你要做的只是在 OS 层把分区和文件系统"长高"。

3.2 系统盘热扩容(ext4 为例,全程不停机)

# 假设你在云厂商控制台把 50GB 系统盘扩到 70GB(在线操作,5 分钟完成,无需重启)

# 1. 让内核看到新大小
sudo partx -u /dev/vda            # 刷新分区表

# 2. 扩展分区(growpart 是 cloud-image-utils 提供)
sudo apt install cloud-guest-utils -y
sudo growpart /dev/vda 1

# 3. 扩展文件系统(ext4)
sudo resize2fs /dev/vda1

# 或 XFS:
# sudo xfs_growfs /

# 4. 验证
df -h /

全程 3 条命令、2 分钟,业务无感知。

3.3 数据盘并入 LVM 卷组(推荐架构,弹性上限更高)

把"数据盘"用 LVM 管理,以后加盘 = 一条 lvextend:

# 场景:系统盘 50GB(ext4,不动)+ 数据盘 /dev/vdb 100GB
# 目标:/data 挂 80GB btrfs,余量进 VG 池,随时可再扩

# 1. 数据盘做 LVM
sudo pvcreate /dev/vdb
sudo vgcreate vgp-data /dev/vdb
sudo lvcreate -L 80G -n datalv vgp-data

# 2. btrfs + 透明压缩(接 2.2 路线 A)
sudo mkfs.btrfs -L data-vol /dev/vgp-data/datalv
sudo mkdir -p /data
echo "/dev/vgp-data/datalv /data btrfs compress=zstd3,space_cache=v2 0 0" | \
  sudo tee -a /etc/fstab
sudo mount /data

# 3. 业务增长时:云厂商把 /dev/vdb 扩到 150GB,然后:
sudo pvresize /dev/vdb                 # PV 认出新空间
sudo lvextend -l +100%FREE /dev/vgp-data/datalv
sudo btrfs filesystem resize max /data  # btrfs 在线扩容
df -h /data                             # 立即生效

关键认知:扩容的成本 = 云厂商的增量磁盘费 + 10 分钟操作时间。相比"迁机换更大 VPS"(停机 2–4 小时 + 数据迁移 + 重做网络配置),这是唯一的正解。

3.4 加一道自动保险:磁盘水位报警 + 自动扩容触发器

用 cron + 云厂商 API,让"快满了"这件事自动发生:

#!/usr/bin/env bash
# /usr/local/bin/disk-autoscale-check.sh —— 每 15 分钟检查一次
set -euo pipefail
THRESHOLD=85   # 使用率阈值(%)
OUT="/var/log/disk-autoscale.log"

for FS in / /data; do
  PCT=$(df --output=pcent "$FS" | tail -1 | tr -d ' %')
  if [ "$PCT" -ge "$THRESHOLD" ]; then
    echo "[$(date -F)] ALERT: $FS at ${PCT}%, trigger expansion + Telegram notify" | sudo tee -a "$OUT"
    # 实际调用云厂商 API(示例:Hetzner 通过 hetzner cloud CLI)
    # sudo hetzner-cloud volume expand --volume-id $VOLUME_ID --size $NEXT_SIZE
    curl -s "https://api.telegram.org/bot$TOKEN/sendMessage" \
      -d chat_id=$CHAT -d text="⚠️ $FS 使用率 ${PCT}%,触发扩容流程" || true
    break
  fi
done
# /etc/cron.d/disk-autoscale
*/15 * * * * root /usr/local/bin/disk-autoscale-check.sh

(具体云厂商的 API 命令按你使用的厂商替换 hetzner-cloud 即可,逻辑通用:读取容量 → 判断阈值 → 调 API 扩容 → 本地 pvresize/lvextend 收编 → 通知确认。)


四、第三层:数据分级——把冷数据搬去 $0.02/GB 的地方

这是省钱主力。思路一句话:SSD 只留 30 天内活跃数据,30 天以上的东西压缩加密后推到对象存储。

4.1 restic 冷备仓库:压缩 + 去重 + 加密,空间降 60–80%

# 安装
sudo apt install restic -y

# 初始化仓库(指向 R2 / S3 兼容存储;R2 无出网费,是本节最省钱的选择)
export RESTIC_REPOSITORY="s3:https://<account>.r2.cloudflarestorage.com/bucket/vps-cold"
export RESTIC_PASSWORD="$(openssl rand -hex 16)"
# 建议同时设置 S3 provider 端点:
export RESTIC_S3_PROVIDER="other"
export RESTIC_S3_REGION="auto"
export AWS_ACCESS_KEY_ID="..."          # R2 API token
export AWS_SECRET_ACCESS_KEY="..."
export RESTIC_S3_BUCKET="vps-cold"      # 或写进 repository URL

restic --backup-password-file ~/.restic.passwd init

# 备份冷数据目录(日志归档、旧镜像、备份目录)
restic --backup-password-file ~/.restic.passwd backup \
  /var/log/archived /backups /opt/app/data-cold --tags cold

restic 默认 --ignore-unknown 并启用 chunk 级去重 + 压缩,冷数据仓库的实际占用通常是原始大小的 20–40%。

4.2 冷/热分级的自动化(systemd timer)

# 1) 备份任务:每天 03:00 备份冷目录,保留策略:3 天日备 + 4 周周备
sudo tee /etc/systemd/system/vps-cold-backup.service >/dev/null <<'EOF'
[Unit]
Description=VPS cold data restic backup
[Service]
Type=oneshot
EnvironmentFile=/etc/restic.env
ExecStart=/usr/bin/restic --backup-password-file /etc/restic.passwd \
  backup /var/log/archived /backups /opt/app/data-cold --tags cold
[Install]
WantedBy=multi-user.target
EOF

sudo tee /etc/systemd/system/vps-cold-backup.timer >/dev/null <<'EOF'
[Unit]
Description=Daily cold backup
[Timer]
OnCalendar=03:00
Persistent=true
[Install]
WantedBy=timers.target
EOF

sudo systemctl enable --now vps-cold-backup.timer

# 2) 清理策略:让本地冷目录自动保持"只留 7 天",旧的交给 restic 仓库
sudo tee /usr/local/bin/cold-prune.sh <<'EOF'
#!/usr/bin/env bash
# 删除本地冷目录中 7 天以前的文件(restic 仓库里已有完整历史)
sudo find /opt/app/data-cold /backups -type f -mtime +7 -delete
sudo find /var/log/archived -type f -mtime +7 -delete
EOF
chmod +x /usr/local/bin/cold-prune.sh

sudo tee /etc/systemd/system/cold-prune.service >/dev/null <<'EOF'
[Unit]
Description=Prune local cold data (>7 days)
[Service]
Type=oneshot
ExecStart=/usr/local/bin/cold-prune.sh
[Install]
WantedBy=multi-user.target
EOF

sudo tee /etc/systemd/system/cold-prune.timer >/dev/null <<'EOF'
[Timer]
OnCalendar=04:30
Persistent=true
EOF

sudo systemctl enable --now cold-prune.timer

这样本地磁盘永远只保留 最近 7 天 的冷数据,其余全部以压缩加密形式躺在 $0.02/GB 的对象存储里,需要时随时 restic restore 取回。

4.3 算一笔账(2C4G / 50GB SSD / 每月 $6 主机 + $5/GB 磁盘 的保守模型)

项目方案前方案后变化
本地 SSD 有效容量45GB45GB(热数据)+ 100GB 外置冷仓库+100GB 等效
压缩收益(btrfs zstd,镜像/日志)无冷数据压缩比 ~3×30GB 原始 → ~10GB 仓库占用
月度存储成本50GB × $5 = $250(假设全价)本地 45GB × $5 + 100GB × $0.02 ≈ $100-60%

注:不同厂商磁盘单价差异大($0.15–$10/GB/月不等),但比例关系不变——冷数据外置 + 去重压缩的节省幅度通常落在 50–80%。

4.4 验证:恢复演练(每次大变更之后必做)

# 列出最近一次备份
restic --backup-password-file ~/.restic.passwd snapshots --tag cold

# 演练恢复 3 天前的一次备份到临时目录(不碰生产数据)
sudo restic --backup-password-file ~/.restic.passwd restore latest-cold \
  --target /tmp/restore-drill
sudo diff -rq /tmp/restore-drill/backups /backups | head   # 应为空
sudo rm -rf /tmp/restore-drill

五、Docker 场景的专项优化(VPS 最常见的实际使用方式)

如果你的 VPS 主力负载是 Docker(绝大多数自托管场景),上面三层方案之外再加两条"容器侧"规则,可再省 10–20GB:

5.1 镜像层治理

# 每周一凌晨清理超过 14 天未使用的镜像(保留当前在跑的)
docker image prune -a --filter "until=336h" -f

# 构建缓存瘦身:限制 buildkit 缓存目录
mkdir -p /var/lib/docker/buildx
# 在 daemon.json 中限制:
# "builder": { "cache": { "type": "gfile", "path": "/var/lib/docker/buildx", "default": true } }

5.2 卷分级:docker volume 中的大目录挂载到 btrfs

# docker-compose.yml 示例:把数据卷放到 /data(btrfs zstd 压缩)
services:
  app:
    volumes:
      - /data/app:/var/lib/app   # 透明压缩自动生效

容器镜像 + 卷都在 btrfs 压缩卷上,综合压缩比再上一个台阶。


六、监控闭环:让存储水位永远"看得见"

三层方案落地后,把磁盘水位变成持续指标,而不是事后补救:

# 1) node_exporter 自带 disk_* 指标,Prometheus + Grafana 直接可用
sudo apt install prometheus-node-exporter prometheus grafana -y   # 或 docker 方式

# 2) Grafana 面板加三条关键线:
#    - disk_used_percent{mountpoint="/"}        阈值线 85%
#    - disk_used_percent{mountpoint="/data"}    阈值线 80%
#    - 近 7 天磁盘使用率斜率(线性回归),预测"X 天后触顶"

# 3) 告警规则(Prometheus rules):
groups:
- name: disk-alerts
  rules:
  - alert: DiskAlmostFull
    expr: (node_filesystem_size_bytes - node_filesystem_avail_bytes)
           / node_filesystem_size_bytes * 100 > 85
    for: 10m
    annotations:
      summary: "Disk {{ $labels.mountpoint }} > 85%"

配合第三节的 disk-autoscale-check.sh 触发器,形成 “监控发现 → 自动扩容 / 冷数据下沉 → 通知确认” 的闭环。


七、完整方案落地清单(Copy-Paste 版)

按顺序执行,一台 50GB VPS 的完整改造:

# [1] 审计
df -hT && sudo du -xh --max-depth=2 / 2>/dev/null | sort -rh | head -20

# [2] 零风险清理(当天回收 8–15GB)
docker image prune -a --filter "until=168h" -f
docker builder prune -f
sudo logrotate -f /etc/logrotate.conf
sudo journalctl --vacuum-time=3d

# [3] 第二块数据盘(云厂商侧)→ LVM + btrfs zstd
sudo pvcreate /dev/vdb && sudo vgcreate vgp-data /dev/vdb
sudo lvcreate -L 80G -n datalv vgp-data
sudo mkfs.btrfs -L data-vol /dev/vgp-data/datalv
echo "/dev/vgp-data/datalv /data btrfs compress=zstd3,space_cache=v2 0 0" | \
  sudo tee -a /etc/fstab && sudo mount /data

# [4] Docker 卷迁到 /data(可选,收益 10–20GB)
# 修改 compose 卷路径 → /data/app;重建容器

# [5] restic 冷仓库(R2)+ systemd timer(每天 03:00 备份 + 04:30 本地冷数据 >7d 清理)

# [6] 磁盘水位告警 + disk-autoscale-check.sh(每 15 分钟)

# [7] 恢复演练:restic restore 到 /tmp 并 diff

预期结果:

  • 有效容量:45GB → ~130GB 等效(压缩 + 冷外置);
  • 存储月成本:-60%;
  • 扩容能力:任意时点 lvextend + btrfs filesystem resize max,5 分钟完成、零停机;
  • 数据保护:restic 去重加密冷备,跨云厂商可迁移。

结语

VPS 的存储成本优化,不是一道"买更大硬盘"的选择题,而是一道分层治理的工程题:

  1. 清理与压缩让现有磁盘"虚胖"变"实胖";
  2. LVM 热扩容让容量增长变成随时可做的常规操作;
  3. 冷数据外置把最贵的 SSD 留给真正热的数据。

三层叠加,一台 $6/月 的 2C4G VPS 完全可以承载过去需要 $20+/月 才能承载的存储负载——而且全程零停机。把本文的清单收藏起来,你的下一台 VPS 扩容决策将快 10 倍。


本文所有命令基于 Ubuntu 24.04 / Debian 12,XFS/btrfs 部分可平移到 AlmaLinux 9。云厂商 API 调用请按你使用的厂商文档适配。

📺 看视频版教程 → DuckDB Lab YouTube

Subscribe for more DuckDB & AI automation tutorials