【问题标题】:CFSpreadSheet functions using up memory for large data setsCFSpreadSheet 函数占用大量数据集的内存
【发布时间】:2018-10-04 19:15:32
【问题描述】:

我们有一个 Coldfusion 应用程序,它正在运行一个大型查询(最多 100k 行),然后以 HTML 格式显示它。然后,用户界面提供了一个导出按钮,该按钮触发使用 cfspreadsheet 标记和电子表格函数将报告写入 .xlsx 格式的 Excel 电子表格,特别是用于构建行列值的电子表格SetCellValue、用于格式化的电子表格格式行和电子表格格式单元函数。然后使用以下命令将 ssObj 写入文件:

<cfheader name="Content-Disposition" value="attachment; filename=OES_#sel_rtype#_#Dateformat(now(),"MMM-DD-YYYY")#.xlsx">
<cfcontent type="application/vnd-ms.excel" variable="#ssObj#" reset="true">

其中 ssObj 是 SS 对象。我们看到文件大小约为 5-10 Mb。

但是...创建此报告和写入文件的内存使用量增加了大约 1GB。复杂的问题是,java GC 在导出完成后没有立即释放内存。当我们有多个用户运行并导出这种类型的报告时,内存会不断攀升并达到分配的堆大小,并且会扼杀服务器的性能,从而导致服务器停机。通常需要重新启动才能将其清除。

这是正常/预期的行为还是我们应该如何处理这个问题?是否可以在导出完成后按需轻松释放此操作的内存使用量,以便运行报表的其他人轻松访问为他们的报表释放的空间?对于 5-10Mb 的文件,这种类型的内存使用是否与 cfspreadsheet 函数和写出对象常见?

我们已尝试暂时删除昂贵的格式化功能,但创建和写入 .xlsx 文件的内存使用量仍然很大。我们也尝试过使用电子表格添加行方法和 cfspreadsheet action="write" query="queryname" 标记传入查询对象,但这也占用了大量内存。

为什么这些函数如此占用内存?在没有内存不足问题的情况下生成 Excel SS 文件的最佳方法是什么?

我应该添加服务器在 Windows 上的 Apache/Tomcat 容器中运行,并且我们使用的是 CF2016。

【问题讨论】:

  • 这很正常。我经常遇到这种情况。

标签: performance coldfusion coldfusion-2016 cfspreadsheet


【解决方案1】:
  • 您为 CF 实例分配了多少内存?
  • 您正在运行多少个实例?
  • 为什么允许任何人查看 HTML 中的 10 万条记录?
  • 为什么允许任何人即时导出这么多数据?

在我上一份工作中,我们遇到了这类问题(CF 和内存)。大文件上传消耗内存,大excel导出消耗内存,这就是会发生的。随着应用程序用户群的增长,您将遇到这些占用内存的请求会为其他用户杀死站点的地步。

从您的内存设置开始。通过将应用程序分配的内容增加一倍或三倍,您可能会得到全面提升。此外,请确保您使用的是最新版本的 CF 支持的 JDK。这也会产生巨大的影响。

大文件上传会影响发出请求的实例的性能。这意味着同一实例上执行正常请求的其他人正在不必要地等待这些资源。我们专门使用一个实例池来处理文件上传。特定的 URL 通过负载均衡器路由到这些实例,应用程序对此更加满意。

该应用还处理了大量的数据,用户一直想要“全部”。我们不得不强制搜索结果和某些数据集来减少屏幕上显示的数量。 DB 对这个决定非常满意。数据导出被移到队列中,因此他们可以在正常页面请求之外制作那些大型 Excel 文件。也许他们立即得到了他们的数据,也许等了一会儿才收到通知。无论哪种方式,该应用程序的整体表现都更好。

【讨论】:

  • 谢谢@Adrian-j-moreno。这是非常有用的信息!我喜欢排队的想法,并希望听到更多关于它的信息。就像您用来决定队列与现在生成的标准一样。此外,队列基本上是每 x 分钟安排一次报告,还是安排在稍后(晚上?)我们分配了 8GB。我们有一个单独的报告实例,以便它可以有更长的超时而不是对主网站服务器实例的请求的短超时,以保护它免受大型报告的运行。
  • 报告一般是根据员工人数,所以大公司有很多记录。我想我们可以尝试按以 A-C、D-F 等开头的姓氏对报告进行分区,并将每个报告放在自己的工作表上,一次运行一个工作表,然后将它们合并到一个 SS 文件中。我没有尝试使用多张纸生成 SS,也​​不知道这是否有助于内存使用。你知道在这种情况下这是否是一条有用的路径吗?
  • 所有内容都会进入队列,一次处理一个。创建导出的速度取决于每个请求的数据量。这就是我所说的“也许立即得到他们的数据”的意思。检查服务器有多少内存可用,看看是否可以增加内存,然后增加分配给每个实例的内存。在我目前的演出中,我为每个实例分配了 16 GB,只有几个实例。最后一个地方,我们每个实例至少有 16 GB,但我们每台服务器有 8 个实例 x 50 多台服务器。
【解决方案2】:

对于 OP 来说可能有点晚了,但既然我最终来到这里,其他人也可能会这样做。虽然这里的其他答案+cmets 中有很多与内存相关的一般性声音建议,但我怀疑 OP 实际上遇到了 genuine 内存泄漏错误,该错误已在 CF 电子表格函数中报告,从 CF11 到到 CF2018。

当生成一个电子表格对象并使用cfheader+cfcontent 提供它而不将其写入磁盘时,即使使用仔细的变量范围,内存也永远不会被垃圾收集。因此,如果您的应用使用此方法运行了足够多的 Excel 导出,那么它最终会耗尽内存,然后无限期地耗尽 CPU,需要重新启动 CF。

请参阅 https://tracker.adobe.com/#/view/CF-4199829 - 我不知道他是否在 SO,但感谢 Trevor Cotton 的错误报告和此解决方法:

  1. 将电子表格写入临时文件,
  2. 将电子表格从临时文件读回内存,
  3. 删除临时文件,
  4. 将电子表格从内存流式传输到 用户的浏览器。

因此,给定一个使用spreadsheetNew() 在内存中创建并且从未写入磁盘的电子表格对象,那么这会导致内存泄漏:

<cfheader name="Content-disposition" value="attachment;filename=#arguments.fileName#" />
<cfcontent type="application/vnd.ms-excel" variable = "#SpreadsheetReadBinary(arguments.theSheet)#" />

...但这不是:

<cfset local.tempFilePath = getTempDirectory()&CreateUUID()&arguments.filename />
<cfset spreadsheetWrite(arguments.theSheet, local.tempFilePath, "", true) />
<cfset local.theSheet = spreadsheetRead(local.tempFilePath) />
<cffile action="delete" file="#local.tempFilePath#" />
<cfheader name="Content-disposition" value="attachment;filename=#arguments.fileName#" />
<cfcontent type="application/vnd.ms-excel" variable = "#SpreadsheetReadBinary(local.theSheet)#" />

这应该没有必要,但 Adob​​e 似乎并不急于解决此问题,而且我已验证这在 CF2016 中对我有用。

【讨论】:

  • 非常有趣的发现。使用 cfcontent + file + deleteFile=true 代替 SpreadsheetReadBinary 怎么样?
  • 这是个好问题。我没想过要测试它,但下次我在那个盒子上时,我会试一试并更新结果。
猜你喜欢
  • 2013-02-01
  • 1970-01-01
  • 1970-01-01
  • 2020-09-09
  • 1970-01-01
  • 2014-05-11
  • 2011-02-27
  • 2013-12-13
  • 1970-01-01
相关资源
最近更新 更多