In Mendix 字符串有多长? | Mendix

跳到主要内容

In Mendix 字符串有多长?

In Mendix 字符串有多长?

这是我关于效率的系列博客中的第二篇 Mendix 应用程序。在本系列的第一篇中(健康与效率 Mendix),我重点介绍了一些可以提高低代码效率的简单方法。现在我将尝试解决一些更棘手的问题。

在过去的五年中,我曾两次遇到这样的要求:浏览所有这些数据并从中构建一个文本文件。

第一个是生成一个制表符分隔的文本文件,表示一组数据 Mendix 应用程序。第二个需要从应用程序中构建的一组数据创建一个 Typescript 文件。这两个 需要支持创建非常长的文本文件 因为数据集可能非常大。

如今,市场上有很多很棒的模块(例如 CSV 您可以使用 .NET Framework 模块来帮助生成 CSV/TSV 文件,但更多任意的文本输出需要替代解决方案。

测试练习

为了收集统计数据进行比较,我使用了一款在 Mendix 9.15.1,部署在 中等大小 环境 (最多 2 个 CPU、最多 2GB 内存、Postgres 数据库)在运行 AWS EKS 私有云 Mendix 云 包括 格拉法纳 监控。

每个练习都是 跑五次. 在进行每组五项练习之前应用程序已停止并启动 尽量减少可能 缓存 对结果的影响。 最好和最坏的结果都被丢弃其他三个取平均值. 练习不一定按照这里介绍的顺序进行。

所用的应用程序可在 GitHub 上找到 开始.

起点

我们有一份清单 Mendix 对象,我们有一个微流,它将从其中一个对象生成我们想要的文本。为了简单起见,我只使用单个数据实体,但在实际场景中可能会涉及大量对象树。 OutputDocument 是 FileDocument 的专业化,用于接收字符串生成过程的结果.

领域模型中的业务实体和输出文档
为 BusinessEntity 实例生成文本的 GetEntityToString 微流程

为了生成输出文件,我们以 2,500 条记录为一批抽取数据记录,传递对象列表,并将为每个记录生成的文本附加到大小不断增加的集合字符串中。当列表用尽时,我们积累的字符串将写入 OutputDocument。创建的 OutputDocument 还记录了使用的算法、处理的记录数、运行测试所花费的时间以及生成文件的哈希值。生成并保存哈希值,以便我们可以确认用于生成相同输出的所有方法都来自相同的源数据。

StartingPointBuildString 微流程

因此让我们运行它。

我用 25,000条记录 使用“随机”文本字符串 每个 500 个字符 和随机整数值,然后运行上述微流。

平均下来 78.81 几秒钟即可构建字符串并将其保存到 FileDocument 中。现在让我们 数据加倍 大小到 50,000 记录并重新运行微流。我想我们可能预计它会花费不到 200 秒的时间。我们至少应该假设现在批量检索会更慢,因为记录量已经增加,尽管我们在 Key 上有索引。

哇哦!原来如此 平均 334.05 秒我不会尝试真正庞大的数据集,除非我有一两部电影可以在播放时观看……

那么,为什么数据量增加一倍就会导致耗时增加四倍呢?

好吧,如果不使用分析工具,我们就无法完全确定,但我们可以对罪魁祸首做出明智的猜测。

TheStartingPointBuildString 摘录

更改变量操作将 GetEntityToString 子微流返回的字符串附加到已存储在输出变量中的先前结果中。但它并没有完全做到这一点。 In Mendix 字符串是不可变的 为了给输出变量创建新值, Mendix 必须创建一个新的字符串原件复印件 输出附加了 EntityOutput 的副本,并将新字符串保存在先前的输出值的位置,该输出值将被丢弃。

随着输出中的值越来越长,文本复制量也会增加,并且该过程变得越来越耗费时间和资源。

让我们尝试一下重新设计 使用一些 Java 代码 看看是否可以避免重复复制长字符串并缩短执行时间。

内存缓冲区

而不是在 Mendix 变量字符串,这里我们将在存储在当前用户操作上下文中的缓冲区中构建字符串,然后在最后将缓冲区存储到 FileDocument 中。上下文只能从 Java Action 访问,因此我们必须在 Java 中构建它。

我们使用的微流与原始微流非常相似,但它调用一个 Java 操作将下一个字符串附加到存储在上下文内存中的缓冲区上,并调用另一个 Java 操作将完成的字符串移动到最后的 FileDocument 中。

MemoryBufferBuildString 微流程

这两个 Java 操作看起来是这样的。我们使用 ByteArrayOutputStream 来存储数据,然后将其转换为 ByteArrayInputStream 以将结果移动到 FileDocument 中。

AppendStringToMemoryBuffer Java 操作
MoveMemoryBufferToDocument Java 操作

那么当我们运行这个时我们得到什么?

好为 25,000条记录 我们得到的平均数是 1.90秒.

为了 50,000条记录 我们得到的平均数是 2.94秒.

我想你会同意这比原始算法有了显著的改进(334 秒对 3 秒)。这似乎表明我们走在正确的道路上。

但我们可以进一步改进吗?虽然速度大大提高,但我们在缓冲区(即应用程序的内存)中存储了大量文本,最终可能会给应用程序带来压力。 Mendix 运行时内存。

文件缓冲区

另一种方法可能会减轻潜在的内存使用问题,并且性能仍然比原始方法更好。此版本的过程将生成的文本写入临时文件,这样我们就不必将其保存在 Mendix 运行时内存。

此选项更复杂,需要使用两个微流和两个 Java 操作。第一个微流调用第一个 Java 操作,后者又调用第二个微流,后者又调用第二个 Java 操作。稍后将解释这样做的原因。

要启动 FileBufferBuildString 微流,请先进行设置,然后调用 BuildStringInFileBuffer Java 操作,最后将补充结果(哈希、记录数和时间)保存在文档中。

FileBufferBuildString 微流程

BuildStringDocumentInFileBuffer Java 操作采用 FileDocument 和微流指针(该微流的可选参数),在 Java 临时文件位置创建临时文件,将打开的文件详细信息存储在上下文内存中,然后调用指针中给出的微流。当该微流返回时,它会将临​​时文件的内容读入 FileDocument 并进行清理,删除文件和上下文对象。

BuildStringDocumentInFileBuffer Java 操作

SUB_FileBufferBuildString 微流程由 BuildStringDocumentInFileBuffer Java 操作调用。它运行循环,读取一批记录,为每个记录生成字符串,然后调用 AppendStringToFileBuffer Java 操作来保存它们。

SUB_FileBufferBuildString 微流程

AppendStringtoFileBuffer Java 操作从上下文中提取临时文件信息(由 BuildStringDocumentInFileBuffer 保存在那里),并将单个记录的字符串写入临时文件的末尾。

代码截图
AppendStringToFileBuffer Java 操作

好的,那么这种安排(microflow-调用-java-调用-microflow-调用-java)有点复杂,可能被视为晦涩难懂,违背了我在之前的博客文章中所说的(可读性与可维护性), 所以它应该为以后的开发人员提供良好的文档.

这种方法有一个很好的理由——它很安全。如果在此过程中出现任何问题,第一个 Java 操作可以清理临时文件和打开的文件描述符,然后再返回到第一个微流,因此整个应用程序受到损害的可能性较小。本博客附带的应用程序中还有一种替代方案(称为 TempStorage)——请参阅 GitHub 上的字符串有多长 -  它不使用微流和 Java 操作的嵌套,但确实要求调用者在出现问题时更加小心地进行清理。

那么结果如何?对于 25,000条记录 花了 1.23 秒,以及 50,000条记录 花了 2.75秒.

内存缓冲区文件缓冲区 性能非常接近,但我们现在可以看到原来的 起点 算法的性能是三者中最低的,并且就本练习而言,我们不再需要使用它。我们可以在另外两个算法之间进行选择吗?

附加赛

我提到内存使用量是比较这些解决方案时的另一个因素,所以我也应该获得一些关于内存使用的统计数据。为了提高清晰度,我们使用更大的测试数据样本—— 500,000条记录.

设置好人口后,重新启动应用程序, 内存缓冲区 进程运行一次。使用 格拉法纳 在这个私有云上设置,我们可以得到一些关于资源使用情况的信息:

哎哟! 内存缓冲区 不够强大 处理这个大小的作业,并且应用程序在填充 AppendStringToMemoryBuffer 中的缓冲区时耗尽了内存。将 文件缓冲区 做得更好?应用程序已重新启动, 文件缓冲区 运行:

等等!那么 文件缓冲区 也失败了,但由于内存不足而失败 FileDocument 已成功创建并填充,在读取生成的整个文件(CommunityCommons StringFromFile)以计算数据的哈希值的过程中。文件太大,无法执行此操作。因此,我禁用了读取字符串并调用哈希操作的代码(使用常量),重新运行该过程,它成功了。

是的,它完成了,而且我们似乎有了一个赢家!

为了保险起见,我设立了一个 1,000,000条记录 数据集并运行 文件缓冲区 再次,这也完成了。

您的里程可能会有所不同

当然,所有这些在现实世界中的表现取决于很多超出本文范围的因素,但我希望这篇文章能够帮助您展示如何在特殊情况下提高字符串生成的弹性和性能,以及如何在一般情况下使用 Java 来提高性能。

在我的下一篇关于效率的文章中,我计划展示如何利用内置的 Mendix 任务队列功能。

致谢

感谢 Arjen Wisse 提供的宝贵建议,以及 Arjen Lammers 提供的本文开头提到的很酷的 CSV 模块,我毫无保留地从中借鉴了一些想法。

选择你的语言