# 高效清理文件系统大量文件的方法

线上环境[找出大量消耗inode的文件目录](https://github.com/huataihuang/cloud-atlas-draft/tree/d20c619da83648a4cc9d8a2fc1ecb3c2188f538f/develop/shell/bash/os/linux/security/audit/find_inode_consume.md)，我们需要有一个快速清理文件的方法。

参考 [Delete only files older than 7 days: -mtime and find](https://unix.stackexchange.com/questions/447360/delete-only-files-older-than-7-days-mtime-and-find)

```
find /tmp/exec -maxdepth 1 -mtime +1 -type f -delete
```

如果要检查文件而不是删除，可以将 `-delete` 改成 `-print`

```
find /tmp/exec -maxdepth 1 -mtime +1 -type f -print
```

但是发现不行

```
#df -i | grep vda
/dev/vda1        2621440 2618372      3068  100% /

[root@server-1.example.com /root]
#find /tmp/exec -maxdepth 1 -mtime +1 -type f -delete

[root@server-1.example.com /root]
#df -i | grep vda
/dev/vda1        2621440 2564624     56816   98% /
```

也就是说都是一天内生成的文件

参考 [find -mtime files older than 1 hour \[duplicate\]](https://stackoverflow.com/questions/543946/find-mtime-files-older-than-1-hour) 改成一小时

原先案例：

```
find /var/www/html/audio -daystart -maxdepth 1 -mmin +59 -type f -name "*.mp3" \
    -exec rm -f {} \;
```

如果要在删除前先验证可以使用echo命令加入

```
... -exec echo rm -f '{}' \;
          ^^^^ Add the 'echo' so you just see the commands that are going to get
               run instead of actual trying them first.
```

我执行：

```
find /tmp/exec -maxdepth 1 -mmin +59 -type f -exec rm -f {} \;
```

不过，这个命令执行起来超级慢。我测试了一下，如果要删除200w+文件，需要大约1.5小时。用watch每2秒检查一下文件清理速度，似乎每秒钟只清理350～400个文件。(计算了一下，每秒400个文件的话，1.5小时确实只能删除 216万 个文件)

为了加快清理速度，我找到 [Efficiently delete large directory containing thousands of files](https://unix.stackexchange.com/questions/37329/efficiently-delete-large-directory-containing-thousands-of-files)

有以下一些推荐方法：

* 较快的删除目录文件方法是使用 `rsync` 对一个空目录和需要删除文件目录进行同步，可以快速清理掉目录下的文件

```
mkdir empty_dir
rsync -a --delete empty_dir/ yourdirectory/
```

但是，这个方法对于我的案例不适用

此外，更快的方法是使用perl，但是我不熟悉perl，没有把握使用这个命令

```bash
cd yourdirectory
perl -e 'for(<*>){((stat)[9]<(unlink))}'
```

* 有推荐使用 `find` 命令的 `-delete` 参数，据说比标准的 `-exec rm -f {} \;` 要快。但是这个命令不是所有平台通用（GNU find才支持 `-delete`），并且也是递归方式删除
* 可以通过 `xargs` 来加速删除，因为 `xargs` 可以并发删除文件，也就是你可以一次传递多个文件给xargs，执行一条删除命令：

```
find /path/to/folder -name "filenamestart*" -type f -print0 | xargs -0rn 20 rm -f
```

> * 这里 `xargs` 的参数 `-0` 表示输入项是以`null`字符结束而不是空白，并且引号和反斜杠不指定。这个参数适合输入项可能包含空格，引号和反斜杠情况。
> * `-r, --no-run-if-empty` 通常即使没有输入也会运行一次，GNU扩展的这个参数在没有包含任何非空白，就不运行
> * `-n 20` 表示最大参数20个
>
> 上述参数结合起来就是表示输入给xargs的一次处理参数是20个，也就是20个并发删除。你可以调整大这个参数以便更大并发。
>
> 不过，根据 `man xargs` 手册提示，xargs能够自动根据平台计算出缓存大小，来决定尽可能大的参数，所以可以不用加 `-n 20` 而是让 `xargs` 自动判断取最大并发。所以可以改为 `| xargs -0r rm -f`

对于我这个案例可以修订脚本命令

```bash
find /tmp/exec -maxdepth 1 -mmin +59 -type f | xargs -0r rm -f
```

### 测试对比

* 先创建一个目录下50w文件

```bash
mkdir test1
cd test1
for i in $(seq 1 500000); do echo testing >> $i.txt; done
```

> 大约15秒就可以创建好500w文件

* 删除测试常规rm

```
time find . -type f -exec rm {} \;
```

完成时间

```
real    10m51.132s
user    1m56.469s
sys    8m49.892s
```

再次测试

```
real    11m36.329s
user    1m37.385s
sys    9m53.831s
```

* 测试find的`-delete`

```
time find . -type f -delete
```

神奇，达到了xargs的效率

```
real    0m10.493s
user    0m0.692s
sys    0m5.105s
```

* 测试删除: xargs并发20个文件

```
time find . -type f | xargs -0rn 20 rm -f
```

> 实测不行，传递参数过长，导致报错 `xargs: argument line too long`

但是可以改成 `find` 加上 `-print0` 参数

```
time find . -type f -print0 | xargs -0rn 20 rm -f
```

删除效率也有很大提高，仅26秒钟，虽然比不上让xargs自动选择buffer的方式

```
real    0m26.355s
user    0m8.334s
sys    0m15.133s
```

* 测试删除: xargs自动使用buffer并发删除文件

```
time find . -type f | xargs -0r rm -f
```

> 实测不行，传递参数过长，导致报错 `xargs: argument line too long`

```
time find . -type f -print0 | xargs -0r rm -f
```

神奇的效率，仅仅12秒钟就可以完成删除

```
real    0m11.995s
user    0m0.984s
sys    0m5.603s
```

## 批量处理

如果有大量的服务器需要检查inode消耗，我们可以使用 [pssh](https://github.com/huataihuang/cloud-atlas-draft/tree/d20c619da83648a4cc9d8a2fc1ecb3c2188f538f/develop/shell/bash/pssh/README.md) 工具：

* 例如对于案例，执行 `pssh -ih hosts 'df -i | grep vda1'` 输出到日志文件 `check_disk.log` 如下：

```
192.168.6.172:/dev/vda1        2621440 2613825      7615  100% /
192.168.6.239:/dev/vda1        2621440 2614148      7292  100% /
```

* 过滤日志进行处理

```
cat check_disk.log | awk '{print $1","$5}'
```

获得类似输出

```
192.168.6.192:/dev/vda1,96%
192.168.6.200:/dev/vda1,5%
192.168.6.202:/dev/vda1,84%
```

* 再进行数据整理

```
cat check_disk.log | awk '{print $1","$5}' | awk -F"[:,%]" '{print $1","$3}'
```

就获得了如下格式

```
192.168.6.183,5
192.168.6.165,82
192.168.6.168,6
...
192.168.6.192,96
192.168.6.200,5
192.168.6.202,84
```

现在进行判断参考 [print line only if number in third field is greater than X \[duplicate\]](https://unix.stackexchange.com/questions/395588/print-line-only-if-number-in-third-field-is-greater-than-x) 或者参考我之前的实践记录 [awk搜索字符串](https://github.com/huataihuang/cloud-atlas-draft/tree/d20c619da83648a4cc9d8a2fc1ecb3c2188f538f/develop/shell/bash/awk_search_string/README.md)

```
cat check_disk.log | awk '{print $1","$5}' | awk -F"[:,%]" '{print $1","$3}' | awk -F, '$2>10 {print $1}'
```

> awk 支持先判断，然后执行打印 参考 [get all rows having a column value greater than a threshold](https://unix.stackexchange.com/questions/356080/get-all-rows-having-a-column-value-greater-than-a-threshold)

所以执行

```
cat check_disk.log | awk '{print $1","$5}' | awk -F"[:,%]" '{print $1","$3}' | awk -F, '$2>10 {print $1}' | tee ip_clean
```

* 现在我们可以开始批量清理了

```
pssh -t 0 -ih ip_clean 'sudo find /tmp/exec -maxdepth 1 -mmin +10 -type f -delete'
```

## 参考

* [Which is the fastest method to delete files in Linux](https://www.slashroot.in/which-is-the-fastest-method-to-delete-files-in-linux) - 有详细对比不同命令的删除时间，不过没有对比 `xargs` 命令
* [Faster way to delete large number of files \[duplicate\]](https://unix.stackexchange.com/questions/96935/faster-way-to-delete-large-number-of-files/211650)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://huataihuang.gitbook.io/cloud-atlas-draft/develop/shell/bash/remove_massive_amounts_of_file.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
