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

NetSuite处理75万条库存记录生成CSV遇阻,求解决方案

75万条库存CSV导出问题解决方案

针对UI中Saved Search无法加载、MapReduce脚本getInputData停滞15小时的问题,核心原因是超大数据集下一次性加载所有数据引发的性能瓶颈,以下是具体优化方案:

一、修复getInputData停滞:改用分页搜索

直接返回已保存搜索对象会让NetSuite在getInputData阶段尝试一次性加载75万条数据,必然导致超时或停滞。改为分页获取搜索结果,让数据分批流入Map阶段:

function getInputData() {
  const savedSearch = search.load({
    id: 'customsearch_st_itemsaved_search_report_' 
  });
  // 每页加载1000条(NetSuite单页最大限制),避免一次性加载全部数据
  return savedSearch.createPagedData({pageSize: 1000});
}

二、Map阶段稳定性与效率优化

原代码字段提取未处理关联字段的value/text属性,且CSV转义逻辑冗余,优化后提升稳定性和处理速度:

function map(context) {
  const result = JSON.parse(context.value);
  
  // 安全提取字段值,兼容关联字段的结构
  const getFieldValue = (fieldId) => {
    const fieldData = result.values[fieldId];
    if (!fieldData) return '';
    return typeof fieldData === 'object' ? (fieldData.value || fieldData.text || '') : fieldData.toString();
  };

  const row = [
    getFieldValue('itemid'),
    getFieldValue('custitem_pricefrom'),
    getFieldValue('custitemwine_owner'),
    getFieldValue('tranid.transaction'),
    getFieldValue('rate.transaction'),
    getFieldValue('formulanumeric'),
    getFieldValue('statusref.transaction'),
    getFieldValue('inventorynumber.inventoryDetail')
  ];

  // 仅在必要时添加CSV引号,减少字符串处理开销
  const csvSafeRow = row.map(val => {
    const strVal = val.toString();
    return strVal.includes(',') || strVal.includes('"') || strVal.includes('\n') 
      ? `"${strVal.replace(/"/g, '""')}"` 
      : strVal;
  });

  // 按批次拆分key,避免单个Reduce节点处理过多数据
  const pageIndex = parseInt(context.key.split('_')[1]);
  const batchKey = `csv_batch_${Math.floor(pageIndex / 100)}`; // 每100页(10万条)一个批次

  context.write({
    key: batchKey,
    value: csvSafeRow.join(',')
  });
  log.debug('Processed Row', `Page ${pageIndex}, Row ${context.key}`);
}

三、Reduce阶段内存优化

原代码将所有行存入数组再分批保存,易引发内存溢出。优化后按批次生成文件,同时避免多Reduce节点生成重复文件名:

function reduce(context) {
  const chunkSize = ROW_LIMIT;
  const header = HEADER.join(',');
  let lines = [header];
  let part = 1;
  // 从key中提取批次编号,确保文件名唯一
  const batchNum = context.key.split('_')[2];
  const filePrefix = `ItemSearch_Export_Batch${batchNum}_`;

  context.values.forEach((line, index) => {
    lines.push(line);

    if ((index + 1) % chunkSize === 0) {
      saveCSV(lines, filePrefix + part);
      lines = [header];
      part++;
    }
  });

  // 保存剩余未达批次量的数据
  if (lines.length > 1) {
    saveCSV(lines, filePrefix + part);
  }
  log.audit('Batch Completed', `Batch ${batchNum} processed ${context.values.length} rows`);
}

四、额外优化建议

  • 精简已保存搜索:移除搜索中不必要的关联字段、复杂公式,仅保留所需的8个字段;若业务允许,按日期/仓库等维度拆分搜索,分批次导出后合并文件。
  • 调整脚本部署配置:在脚本部署页面设置并发数为4-8,启用“允许并发执行”选项,提升整体处理速度。
  • 日志监控:保留关键步骤的日志输出,方便排查执行过程中的异常节点。

修改后的完整脚本

/**
* @NApiVersion 2.1
* @NScriptType MapReduceScript
*/
define(['N/search', 'N/file', 'N/log'], function(search, file, log) {

  const ROW_LIMIT = 100000;
  const HEADER = [
    'Item ID',
    'Price From',
    'Wine Owner',
    'Document Number',
    'Item Rate',
    'Price - Rate Diff',
    'Status',
    'Inventory Number'
  ];

  function getInputData() {
    const savedSearch = search.load({
      id: 'customsearch_st_itemsaved_search_report_' 
    });
    return savedSearch.createPagedData({pageSize: 1000});
  }

  function map(context) {
    const result = JSON.parse(context.value);
    
    const getFieldValue = (fieldId) => {
      const fieldData = result.values[fieldId];
      if (!fieldData) return '';
      return typeof fieldData === 'object' ? (fieldData.value || fieldData.text || '') : fieldData.toString();
    };

    const row = [
      getFieldValue('itemid'),
      getFieldValue('custitem_pricefrom'),
      getFieldValue('custitemwine_owner'),
      getFieldValue('tranid.transaction'),
      getFieldValue('rate.transaction'),
      getFieldValue('formulanumeric'),
      getFieldValue('statusref.transaction'),
      getFieldValue('inventorynumber.inventoryDetail')
    ];

    const csvSafeRow = row.map(val => {
      const strVal = val.toString();
      return strVal.includes(',') || strVal.includes('"') || strVal.includes('\n') 
        ? `"${strVal.replace(/"/g, '""')}"` 
        : strVal;
    });

    const pageIndex = parseInt(context.key.split('_')[1]);
    const batchKey = `csv_batch_${Math.floor(pageIndex / 100)}`;

    context.write({
      key: batchKey,
      value: csvSafeRow.join(',')
    });
    log.debug('Processed Row', `Page ${pageIndex}, Row ${context.key}`);
  }

  function reduce(context) {
    const chunkSize = ROW_LIMIT;
    const header = HEADER.join(',');
    let lines = [header];
    let part = 1;
    const batchNum = context.key.split('_')[2];
    const filePrefix = `ItemSearch_Export_Batch${batchNum}_`;

    context.values.forEach((line, index) => {
      lines.push(line);

      if ((index + 1) % chunkSize === 0) {
        saveCSV(lines, filePrefix + part);
        lines = [header];
        part++;
      }
    });

    if (lines.length > 1) {
      saveCSV(lines, filePrefix + part);
    }
    log.audit('Batch Completed', `Batch ${batchNum} processed ${context.values.length} rows`);
  }

  function saveCSV(lines, part) {
    const fileName = `${part}.csv`;
    const csvFile = file.create({
      name: fileName,
      fileType: file.Type.CSV,
      contents: lines.join('\n'),
      folder: 194669 
    });

    const fileId = csvFile.save();
    log.audit(`Saved File ${fileName}`, `File ID: ${fileId}`);
  }

  return {
    getInputData,
    map,
    reduce
  };
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 12:57:02