首页 > 解决方案 > Drools 查询不能很好地扩展

问题描述

我正在使用 Drools 在我的 OptaPlanner 项目中进行分数计算,在我开始使用查询将逻辑事实从工作内存检索到 Java 之后,我意识到随着输入大小的增加,查询需要更多时间才能完成(它们没有扩展出色地)。

我正在检索的逻辑事实包含从一堆约束中累积,我在应用程序的 Java 部分中使用它们,重点是重用 drools 已经计算的内容并在 O(1) 时间内检索这些值。

注意:我使用的 Drools 和 OptaPlanner 版本是 7.0.0-SNAPSHOT。

用一个简单的 Drools 应用程序复现问题

为了呈现我发现的缩放问题,我简化了一些事情并创建了一个单独的简单 drools 项目,该项目仅包含一个用于执行应用程序的主类和两个用于 drools 事实的其他类。这也明显避免了 OptaPlanner 增加的开销。

就像我在这个简单项目中提到的那样,有两个类用于事实,其中有一个属性“id”的Employee和具有“employee”和“ totalHours ”作为属性的 TotalHours。

public class Employee {
    public int id;
}

public class TotalHours {
    public Employee employee;
    public int totalHours;
}

此项目中的查询将调用中作为参数传入的员工与 TotalHours 对象的员工部分进行匹配。请注意,对于给定的员工,最多可以有一个 TotalHours 对象(零个或一个)。

query "get TotalHours for Employee"  (Employee e)
   totalHours : TotalHours(employee == e)
end

无论我是在 Java 部分中创建 TotalHours 对象并将它们与 Employee 对象一起插入到工作内存中,还是编写基于 Employee 对象将创建逻辑事实的规则,都存在缩放问题。就像我之前提到的 OptaPlanner 项目中的事实一样,查询检索到的事实是逻辑事实。

所以很明显,查询不会像例如当规则尝试将两个模式与它们的共同属性匹配时那样缩放,就像下面的例子一样。即使在 Employee 和 TotalHours 之间的关系是 1:N 的情况下,这意味着一个员工可以匹配多个 TotalHours 对象,无论员工规模如何增长,应用程序都将大致需要相同的时间来执行规则。由于散列,它将很好地扩展。

rule "Match Employee and TotalHours"
    when
        $employee : Employee()
        TotalHours(employee == $employee)
    then
end

绩效衡量

我正在做两种类型的测试来衡量简单的drools 应用程序的可扩展性。在这两种情况下,根据测试大小创建 N Employee 和 TotalHours 对象,插入工作内存并调用“fireAllRules”。然后在:

测试 1 – 随着 N 的增加,查询的速度被测试出来

  1. 对于随机选择的员工,查询被调用 1000 次

  2. 测量完成这 1000 个不同大小 N 的查询所花费的时间

    double totalTime = 0L;
    for (int i = 0; i < testSize; i++) {
        int randomIndex = random.nextInt(employees.size());
        Employee employee = employees.get(randomIndex);
        long startTime = System.currentTimeMillis();
        QueryResults queryResults = kSession.getQueryResults("get TotalHours for Employee", employee);
        long endTime = System.currentTimeMillis();
        TotalHours totalHours = (TotalHours) queryResults.iterator().next().get("totalHours");
        totalTime += (endTime - startTime);
    }
    

测试 2 – 随着 N 的增加,测试“匹配员工和总小时数”规则的速度

  1. TotalHours 中的属性“totalHours”被更新,fireAllRules 被调用 1000 次

  2. 测量更新变量并在不同大小的 N 中执行所花费的时间

    double totalTime = 0L;
    for (int i = 0; i < testSize; i++) {
        int randomIndex = random.nextInt(totalHoursList.size());
        TotalHours totalHours = totalHoursList.get(randomIndex);
        int randomTotalHours = random.nextInt(100000);
        long startTime = System.currentTimeMillis();
        totalHours.setTotalHours(randomTotalHours);
        FactHandle factHandle = kSession.getFactHandle(totalHours);
        kSession.update(factHandle, totalHours);
        kSession.fireAllRules();
        long endTime = System.currentTimeMillis();
        totalTime += (endTime - startTime);
    }
    

我测量了 1000、5000、10000、50000、100000 和 500000 个 Employee 和 TotalHours 对象插入到工作内存中。在下面的结果中,我们可以看到完成查询或执行规则所需的平均时间(以毫秒为单位)(1000 次随机试验的平均值)。

随着员工数量的增加,执行查询所需的平均时间稳步增加,而我们可以看到,将 Employee 与 TotalHours 匹配的规则正在根据结果递增地工作。 结果

下面从 Java Mission Control Flight Recording 截取的屏幕截图显示了大部分时间都花在了哪里,我们可以看到 99.63% 的时间花在方法“org.drools.core.phreak.PhreakJoinNode.doLeftInserts”中。 来自版本 7.0.0-SNAPSHOT 的飞行记录的调用树

顺便说一句,我确保垃圾收集不会分散这些测试的注意力,我只是将初始堆大小和最大堆大小设置为 6GB,这里完成的最大测试(500000 名员工)需要大约 700MB 才能运行。 CPU 和内存使用率

最新版本的 Drools 上仍然存在该问题(此时为 7.22.0.Final)

我很想知道这个问题是否在最新版本的 Drools 中得到了解决,并且根据我得到的结果,它仍然以相同的方式表现,它仍然不能很好地扩展。我所做的是下载最新版本的 Drools(此时为 7.22.0.Final)并执行上述相同的查询测试。

我们可以并排看到两个版本的结果。不要被新版本中平均时间的小幅增加所欺骗,因为运行测试时我的计算机上的开销更大。 查询测试 7.0.0-SNAPSHOT 与 7.22.0.Final 的结果

同样基于从 Java Mission Control Flight Recording 截取的截图,即使堆栈看起来略有不同,热点仍然是“org.drools.core.phreak.PhreakJoinNode.doLeftInserts”。 来自版本 7.22.0.Final 的飞行记录的调用树

除了所有的事情,我正在寻找一种有效的 O(1) 方法来检索 Drools 已经计算过的内容,并能够在应用程序的 Java 端使用该信息。

标签: drools

解决方案


推荐阅读