Making rule engine execution order more deterministic #207
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Clara currently uses
clara.rules.platform/tuned-group-by
in a number of places. The grouping that this function does is unordered and often can vary from one JVM to the next. In particular I've noticed this happening in theclara.rules.engine/flush-updates
which was causing non-deterministic insertion order to happen across rules. This makes it harder for us to track bad performance cases since they keep fluctuating as we run in different processes.A side-effect of
flush-updates
not behaving deterministically was that insertion order across rules of the same salience group level were not deterministic and didn't necessarily respect rule load order as was implemented in #192. From my preliminary testing of this, I see a slight improvement in our execution times with this change in place. This is likely due to the rule order now also being the insertion order used. I've demonstrated this with a test that fails before this change and now passes.My proposal is to (on the Java CLJ side) introduce a new
clara.rules.platform/group-by-seq
function that replaces the usages ofplatform/tuned-group-by
. Currently, I've leftplatform/tuned-group-by
in case it ends up having some usages later. It is also used in one CLJS place still, which I'm not wanting to change with this.platform/group-by-seq
returns a seq of tuples, which is the same as seq'ing over the map returned byplatform/tuned-group-by
before. In all cases where we usedplatform/tuned-group-by
we didn't use the map, but rather a seq over it.I use a java.util.LinkedHashMap to maintain insertion order while building the groupings. Then I convert this into an immutable seq structure. If I tried to retain the usage of a map here, I'd have to return the java.util.LinkedHashMap or come up with some (expensive) way to port it into a Clojure sorted map. This was not worthwhile to me, since we do not need a map anyways. A java.util.LinkedHashMap would be harder to work with across the rest of the codebase since there are issues with the default Clojure seq implementations across mutable object iterators http://dev.clojure.org/jira/browse/CLJ-1738 that I see coming up when running this on JDK6 (oddly not on JDK8, which makes no sense and I want to investigate more for my own benefit).
Along with this, since I was messing with the
clara.rules.compiler/create-get-alphas-fn
already, I sorted the result of the ancestors-fn so that it would behave deterministically. This would be more of an edge case, but since we cache this result and infrequently should hit this path, I figured it'd still be worthwhile.