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

Node.js使用exceljs生成大数据量Excel报堆内存溢出如何解决?

问题根因

  • 第一,workbook、worksheet定义在全局作用域,所有接口请求共享同一个实例,不仅会出现多请求数据混淆的bug,还会导致内存无法被GC回收,请求量越大内存占用越高,最终溢出。
  • 第二,使用Promise.all一次性发起所有分页请求,10万条数据按3000/页拆分后会同时发起30+个请求,所有返回的原始数据、转换后的行数据、Excel的内存对象全部堆在内存中,内存峰值会非常高。同时并发请求还会导致Excel的行顺序和分页顺序不一致,数据排序错乱。
  • 第三,生成文件时先把整个Excel写入内存Buffer,再转成base64格式返回,base64会比原二进制文件大33%,相当于同时在内存中存储了Excel对象、二进制Buffer、base64字符串三个超大对象,进一步放大内存压力。

优化方案

1. 核心逻辑改造

  • 将workbook、worksheet的定义移到接口处理函数内部,每次请求新建独立实例,请求结束后实例会被自动回收,避免内存泄漏和数据混淆。
  • 替换Promise.all全并发请求为串行/限并发请求,保证数据顺序的同时,避免大量数据同时堆在内存中,处理完的分页数据可以及时被GC回收。
  • 放弃base64格式返回,改用流式输出直接把Excel文件写入响应体,不需要把整个文件暂存在内存中,内存占用可以降到KB级别。

2. 改造后Node.js代码示例

const axios = require('axios');
const excel = require("exceljs");

// 并发控制函数,可根据服务器性能调整并发数
const limitConcurrency = async (tasks, concurrency = 3) => {
  const results = [];
  let index = 0;
  const executeNext = async () => {
    if (index >= tasks.length) return;
    const currentIndex = index++;
    results[currentIndex] = await tasks[currentIndex]();
    await executeNext();
  };
  const workers = Array(concurrency).fill().map(executeNext);
  await Promise.all(workers);
  return results;
};

exports.getTicketData = async (req, res, next) => {
  res.connection.setTimeout(0);
  const { body } = req;
  const { token, organization_id, server, sideFilter } = body;
  const baseurl = 'url for server end to fetch data';

  if (!baseurl) {
    return res.status(400).json({ type:'error', msg:'please define server name' });
  }

  try {
    const limit = 3000;
    const count = await getCount(token, limit, organization_id, baseurl, sideFilter);
    // 每次请求新建独立的workbook和worksheet
    const workbook = new excel.Workbook();
    const worksheet = workbook.addWorksheet("My Sheet");
    worksheet.columns = [
      { header: "TicketId", key: "ticketId" },
      { header: "Email", key: 'user_email' },
      { header: "User",  key : 'user_name' },
      { header: "Subject", key: "subject" },
      // 其余表头
    ];

    // 生成任务列表,限并发执行
    const tasks = Array.from({length: count}, (_, i) => async () => {
      const page = i + 1;
      const response = await axios.post(baseurl+`/v2/get-export`, {
        page,
        organization_id,
        per_page: limit,
        filter: "",
        sorted:"",
        ...sideFilter
      },{ headers: {"Authorization" : `Bearer ${token}`} });
      const dataTemp = response.data.data.data.map(t => ({
        ...t,
        name: t.name,
        // 其余字段转换
      }));
      worksheet.addRows(dataTemp);
      // 手动释放无用变量,帮助GC
      response.data = null;
      return null;
    });

    await limitConcurrency(tasks, 3);

    // 设置响应头,直接返回Excel文件流
    res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
    res.setHeader('Content-Disposition', 'attachment; filename="export.xlsx"');
    // 直接写入响应流,无需缓存全量文件
    await workbook.xlsx.write(res);
    res.end();
  } catch (err) {
    console.error(err);
    return res.status(400).json({ type:'error', msg:'File not generated please contact support staff' });
  }
};

let getCount =  (token,limit, organization_id, baseurl, sideFilter) => {
  // 原有逻辑不变
}

3. 改造后React前端代码示例

exportLink = () => {
  const postData ={
    // 原有参数不变
  };
  return axios.post(`${baseurl}/api/ticketing/get-ticket`, postData, {
    // 指定返回类型为blob
    responseType: 'blob'
  }).then(function (response) {
    const downloadLink = document.createElement("a");
    const fileName = "export.xlsx";
    // 用返回的二进制流生成下载链接
    downloadLink.href = window.URL.createObjectURL(new Blob([response.data]));
    downloadLink.download = fileName;
    downloadLink.click();
    // 释放blob资源
    window.URL.revokeObjectURL(downloadLink.href);
  }).catch(function(error){
    throw error;
  });
}

4. 额外优化建议

  • 如果数据量超过20万条,可以拆分多个Sheet存储,单个Sheet最多存储5万行,避免单个Sheet过大导致的内存上涨。
  • 可以根据服务器配置适当调整并发数,并发数越高处理速度越快,但内存占用也会越高,建议压测后选择最优值。
  • 如果是部署在Docker等容器环境,需要确认容器的内存上限是否高于你设置的max_old_space_size,否则调整Node内存参数无效。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 01:36:01