在一个 Mendix 应用程序,大多数操作都是单线程的,例如,您定义的操作将从开头开始,在结尾结束,中间将按照您指定的顺序执行您指定的操作。这让事情变得简单而美好,因为您不必太担心您定义的操作是否会按预期发生,如果您的操作中有依赖项,您可以清楚地看到它们是什么。

另一方面,队列对您的应用来说很有用,因为它们可用于将进程的执行分散到多个线程和节点。如果您有一项特定的工作要做,队列可以帮助您,允许您同时执行工作的不同部分,并且总体而言,该过程花费的时间更少。那么您该如何做到这一点呢?
这是关于效率的系列博客文章的第三篇 Mendix 应用程序。在本系列的第一篇中(健康与效率 Mendix),我重点介绍了一些可以提高低代码效率的简单方法,在第二部分(In Mendix 字符串有多长?),我展示了如何使用 Java 操作来帮助提高性能。这次我想说明如何 Mendix 任务队列 可用于使您的应用程序更高效。
任务队列
Mendix 任务队列 被引入 Mendix 9 作为 Process Queue 市场模块的现代替代品,其功能有据可查。在这篇文章中,我将举一个特定的简单用例,并展示如何 任务队列 可 用于显著减少执行所需过程所需的时间.
有关于 任务队列页面 涵盖了你可以做的事情 任务队列以及如何执行此操作,包括新功能,例如自动重试失败的任务和安排任务在特定时间开始执行。需要记住的一点是,除非您自己管理,否则您必须小心不要在流程中的“子任务”之间建立依赖关系 - 此处显示的示例用例具有简单的依赖关系,我已经设计了一种控制它的方法。
大量删除

我的应用程序用于从定义的外部数据源提取数据 当用户请求时。这些数据将经过一些简单的分析,以便用户可以决定如何使用这些数据。当用户感到满意并完成手头的工作时, 数据需要被删除。
测试应用程序已组合在一起来说明如何 任务队列 可以加速删除过程: GitHub – Adrian-Preston/QueueingCanBeAGoodThing

在初始设置中,域模型已配置为自动删除,因此删除源对象将自动沿树向下级联,删除所有关联对象(域模型中用红色边框突出显示的关联框)。这是一个安全的选项,因为它将防止留下“孤立”对象,这也意味着开发人员只需删除源,其他一切都会随之删除。但是,由于这是单线程操作,如果树中有大量数据,则这可能需要一些时间。

由于这是一个相对简单的域模型,因此很容易看出我们可以同时安全地删除某些实体的对象。因此 ItemValue、ItemAttachment、ItemLink、AnalysedValue 和 AnalysedAttachment(一套) 可以同时安全地删除特定源。同样,Item 和 AnalysedItem (第二套)可以同时删除,但只能在 一套 已全部删除。最后,Source、DocumentType 和 Document 必须按正确顺序删除,之后 一套 和 第二套 已被删除。这些就是我之前提到的依赖项。
那么如何才能做到这一点呢?
在应用程序 UI 上,有一个页面列出了当前已加载的源。从那里,用户可以识别要删除的源并点击该行上的“智能删除源”按钮。

这会调用一个名为“ACT_SmartDeletion”的纳米流,它有两个主要任务:首先在后台启动删除过程;其次等待源记录从数据库中消失,表明任务已完成。


纳米流调用名为“SUB_StartSmartDeletion”的微流,后者为每种实体类型调用一个子微流 一套 但这些任务都是通过放入任务队列来调用的,这意味着它们不是直接执行的,而是排队在后台运行。我们还为每个子微流创建了一个特殊的 DeletionControl 对象以供接收 — 下文将对此进行详细介绍。当此微流完成后,它将返回到纳米流。

然后,nanoflow 进入循环,查看源记录是否仍在数据库中,当源记录还在时,nanoflow 会暂停一小段时间,然后再次检查。当源记录不再存在于数据库中时,nanoflow 会通知用户并完成。
五个子微流都相同(除了一个子微流有一些额外的代码)。子微流删除特定类型的实体的源的所有记录,然后最终删除上面给出的 DeletionControl 对象。


'SUB_DeleteItemValue' 中的附加代码会等待所有 一套 is 完成(通过检查所有 DeletionControl 对象是否已被删除),然后启动同一个 任务队列 运行删除实体 第二套,所以什么时候 一套 完成了 第二套 使用相同机制自动开始删除。

类似地,'SUB_DeleteItem',当它删除了所有项目时,等待 第二套 完成并最终删除源记录,从而完成该过程。由于 DocumentType 和 Document 记录数量很少,我们只需使用域模型“删除时”行为即可删除它们。

它们之间如何比较?
测试应用程序在源上还有一个“简单删除源”按钮,它只是直接删除源并留下域模型以确保依赖对象也被删除。因此,我们可以对一组测试数据运行简单或智能删除。此外,该应用程序还能够创建一组新的测试数据、导出一组测试数据并重新导入一组测试数据。这样, 该应用程序将允许创建新的集合,并可以导出/导入它们,以便简单和智能删除选项可以重复用于相同的数据.
我有一个名为“Source-36e63c07–9a8a-4c94–8f87–0fbf9b7dd39f”的测试数据集,它包含在应用程序的资源目录中,这是我的机器上用来比较删除的数据集。您可以使用它或自己制作。
我运行了测试应用程序 Mendix 9.18.0,配置为访问本地 Postgres 10 数据库。在测试每种类型的删除之前,我从头开始启动应用程序。然后我导入测试数据集并运行删除五次。我忽略了五个结果中的最佳和最差结果,并取剩余三个时间的平均值。
那么结果如何?简单删除选项平均需要 163.9 秒。智能删除选项平均需要 29.4 秒 — 不到五分之一 简单删除所用时间。现在,如果用户正在等待删除完成,那么这听起来像是值得节省的。
还有其他方法可以改善此操作的用户体验 - 例如,您可以通过在源记录上放置布尔标志将源标记为已删除,然后通过单独的定期计划事件流程删除被标记的源记录及其依赖项。您的问题永远不会只有一个解决方案。
此外,应该认识到,为一个用户让多个线程努力工作可能会减慢其他用户的应用程序速度,因此应该充分理解和平衡精确用例的需求和解决方案的效果。
不要忘记,如果你有一个水平扩展的生产环境,那么 任务队列 将分布在集群中的可用节点上,这可能会为您节省额外的时间(尽管这里介绍的场景集中于始终是共享资源的数据库)。
还有一件事
目前,我们为用户节省了大量时间,希望这能改善他们使用该应用的体验。但有一件事被忽略了。
在域模型中,Item、ItemValue、ItemAttachment、ItemLink、AnaysedItem、AnalysedValue 和 AnalysedAttachment 之间的关联仍然配置了自动删除选项。现在,当使用智能删除选项时,域模型中这些对象的自动删除实际上不会删除任何内容,因为目标数据已被删除。但是, Mendix 运行时仍需要查看是否有任何记录需要删除,这需要时间。
所以最后我们可以从域模型中删除自动删除选项并重新运行智能删除以查看其效果。

进行此更改后,像以前一样运行智能删除五次,平均耗时为 10.0 秒相比 29.4 秒。因此,我们现在已经减少了 163 秒降至 10 秒。这听起来像是一场胜利,但请注意,删除源将不再级联依赖项,因此如果应用程序中的其他地方必须删除此数据,那么您也需要为此设计一个解决方案。
综上所述
运用 任务队列 明智地应用于适当的用例可以显著提高运行时间性能。在本例中,测试结果显示用户节省了大量成本。

您的里程可能会有所不同
我可能不需要说这个,但使用这种技术的好处(对于任何类型的过程,而不仅仅是大量删除)将有很大差异,具体取决于所执行操作和域模型的复杂性、您的环境中有多少备用资源以及您希望模型有多复杂。再次,我回顾了我之前博客中关于保持代码可读性和可维护性的评论。
此外,如果两个或多个用户同时删除源记录,则队列资源将被共享,并且每个用户的保存可能会较少。
我曾在实际生产环境中使用过类似这样的技术(这是预先Mendix 9,因此它使用了 ProcessQueue Marketplace 模块)并实现了性能提升,这让我感到惊讶,也让产品负责人感到高兴。所以一定要创建一个分支并尝试一下。
排队愉快!