You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何自动删除2天后的上传文件?现有方案CPU占用过高求优化

聊天应用过期文件自动删除方案分析与优化

核心结论

给每个上传文件单独设置2天后删除的定时器方案,确实比当前的递归扫描方案更优,而且不用担心定时器数量过多的问题——Node.js的定时器采用高效的红黑树管理,即使每秒新增一个定时器,2天内最多也才172800个,这对Node.js来说完全在性能承受范围内,不会造成明显的CPU或内存开销。

当前递归扫描方案的问题

你当前的实现用了fs.readdirSync和fs.statSync这类同步IO操作,在遍历大量文件时会直接阻塞Node.js的事件循环,导致服务器无法处理其他用户请求;同时递归遍历目录+逐个文件调用stat的系统调用本身就会占用大量CPU资源,这是CPU过高的核心原因。

单文件定时器方案的优势与注意点

优势

  1. 精准删除:文件到期立即删除,不会像扫描方案那样有最多4小时的延迟
  2. 低CPU开销:不需要定期遍历所有文件,只有在文件上传和到期时才有少量操作
  3. 无阻塞风险:异步定时器不会阻塞事件循环

注意事项

  1. 服务器重启丢失定时器:如果服务器重启,所有未触发的定时器都会消失,导致过期文件无法自动删除。解决办法是持久化文件的过期时间——比如把每个文件的路径和到期时间存在数据库(或本地KV存储),服务器启动时读取这些记录,重新为未过期的文件设置定时器。
  2. 无效删除操作:如果用户手动删除了文件,对应的定时器触发时会报错。需要在手动删除文件时,同时从持久化存储中移除记录并取消定时器。
  3. 可选优化:批量定时器:如果担心定时器数量,可以按时间分组(比如按小时),把同一小时到期的文件放在一个列表里,只设置一个定时器处理整批文件,这样定时器数量最多只有48个(覆盖2天),进一步降低开销。

现有递归方案的应急优化

如果暂时不想切换到定时器方案,可以先把同步IO改成异步,避免阻塞事件循环,大幅降低CPU占用:

const fs = require('fs/promises');
const path = require('path');
const rootDir = require('../config').rootDir;
const log = require('../da/log');

const DIRECTORY_MESSAGES = rootDir + '/file/messages';
const DIRECTORY_AVATARS = rootDir + '/file/avatars';
const INTERVAL_MINUTES_MESSAGES = 240;
const INTERVAL_MINUTES_AVATARS = 265;
const ACCESS_DAYS_MESSAGES = 2;
const ACCESS_DAYS_AVATARS = 10;

module.exports = {
    start: function () {
        setInterval(async () => {
            await deleteFiles(DIRECTORY_MESSAGES, ACCESS_DAYS_MESSAGES);
        }, INTERVAL_MINUTES_MESSAGES * 60_000);
        setInterval(async () => {
            await deleteFiles(DIRECTORY_AVATARS, ACCESS_DAYS_AVATARS);
        }, INTERVAL_MINUTES_AVATARS * 60_000);
    }
};

async function deleteFiles(dir, accessDays) {
    await walkDir(dir, async (filePath, isDirectory) => {
        try {
            if (isDirectory) {
                if (filePath === dir) return;
                try {
                    await fs.rmdir(filePath);
                } catch (err) {
                    log.error(`${__filename} :deleteFiles, fs.rmdir, ${err}`);
                }
                return;
            }
            const stat = await fs.stat(filePath);
            const now = Date.now();
            const endTime = new Date(stat.atime).getTime() + accessDays * 86_400_000;
            if (now > endTime) {
                try {
                    await fs.unlink(filePath);
                } catch (err) {
                    log.error(`${__filename} :deleteFiles, fs.unlink, ${err}`);
                }
            }
        } catch (err) {
            console.log(`File Deleter: walkDir: callback: ${err}`);
        }
    });
}

async function walkDir(dir, callback) {
    try {
        const files = await fs.readdir(dir);
        if (files.length === 0) {
            await callback(dir, true);
            return;
        }
        for (const f of files) {
            const dirPath = path.join(dir, f);
            const stat = await fs.stat(dirPath);
            if (stat.isDirectory()) {
                await walkDir(dirPath, callback);
            } else {
                await callback(dirPath, false);
            }
        }
    } catch (err) {
        console.log(`File Deleter: walkDir: ${err}`);
    }
}

这个修改把所有同步IO换成了异步的fs.promises方法,用async/await处理流程,不会阻塞事件循环,CPU占用会明显下降。

总结

  • 长期来看,单文件+持久化定时器方案是最优选择,精准且低开销
  • 短期应急,把现有同步扫描改成异步就能解决CPU过高的问题

内容的提问来源于stack exchange,提问作者danial

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 03:11:27