You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@unomi.apache.org by sh...@apache.org on 2021/07/05 13:33:34 UTC
[unomi] 01/01: UNOMI-188 Rule event type optimization - New
optimization for rules : rule condition are parsed to determine the event
types they handle. This is done using a new ParserHelper method that
navigates the tree of conditions to resolve which eventTypeCondition are
used. - Settings to deactivate the new optimization in case it causes
issues - Integration tests to validate that the parsing of conditions is
behaving as expected - Performance tests to validate the performance
improvement of the op [...]
This is an automated email from the ASF dual-hosted git repository.
shuber pushed a commit to branch UNOMI-188-rule-event-type-optimization
in repository https://gitbox.apache.org/repos/asf/unomi.git
commit 07b3b7eafe7d17c7a39ff52e9263e33130d29c74
Author: Serge Huber <sh...@jahia.com>
AuthorDate: Mon Jul 5 15:33:25 2021 +0200
UNOMI-188 Rule event type optimization
- New optimization for rules : rule condition are parsed to determine the event types they handle. This is done using a new ParserHelper method that navigates the tree of conditions to resolve which eventTypeCondition are used.
- Settings to deactivate the new optimization in case it causes issues
- Integration tests to validate that the parsing of conditions is behaving as expected
- Performance tests to validate the performance improvement of the optimization
---
.../apache/unomi/api/services/RulesService.java | 34 +++++++
.../test/java/org/apache/unomi/itests/AllITs.java | 1 +
.../org/apache/unomi/itests/ConditionBuilder.java | 6 +-
.../org/apache/unomi/itests/RuleServiceIT.java | 109 ++++++++++++++++++++-
.../main/resources/etc/custom.system.properties | 4 +
.../apache/unomi/services/impl/ParserHelper.java | 57 +++++++++--
.../services/impl/rules/RulesServiceImpl.java | 66 +++++++++++--
.../resources/OSGI-INF/blueprint/blueprint.xml | 2 +
.../main/resources/org.apache.unomi.services.cfg | 5 +
9 files changed, 264 insertions(+), 20 deletions(-)
diff --git a/api/src/main/java/org/apache/unomi/api/services/RulesService.java b/api/src/main/java/org/apache/unomi/api/services/RulesService.java
index 786ecfd..144a604 100644
--- a/api/src/main/java/org/apache/unomi/api/services/RulesService.java
+++ b/api/src/main/java/org/apache/unomi/api/services/RulesService.java
@@ -17,6 +17,7 @@
package org.apache.unomi.api.services;
+import org.apache.unomi.api.Event;
import org.apache.unomi.api.Item;
import org.apache.unomi.api.Metadata;
import org.apache.unomi.api.PartialList;
@@ -104,4 +105,37 @@ public interface RulesService {
* @return the Set of tracked conditions for the specified item
*/
Set<Condition> getTrackedConditions(Item item);
+
+ /**
+ * Retrieves all the matching rules for a specific event
+ * @param event the event we want to retrieve all the matching rules for
+ * @return a set of rules that match the event passed in the parameters
+ */
+ public Set<Rule> getMatchingRules(Event event);
+
+ /**
+ * Refresh the rules for this instance by reloading them from the persistence backend
+ */
+ public void refreshRules();
+
+ /**
+ * Set settings of the persistence service
+ *
+ * @param fieldName name of the field to set
+ * @param value value of the field to set
+ * @throws NoSuchFieldException if the field does not exist
+ * @throws IllegalAccessException field is not accessible to be changed
+ */
+ void setSetting(String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException;
+
+ /**
+ * Get settings of the persistence service
+ *
+ * @param fieldName name of the field to get
+ * @return an object corresponding to the field that was accessed
+ * @throws NoSuchFieldException if the field does not exist
+ * @throws IllegalAccessException field is not accessible to be changed
+ */
+ Object getSetting(String fieldName) throws NoSuchFieldException, IllegalAccessException;
+
}
diff --git a/itests/src/test/java/org/apache/unomi/itests/AllITs.java b/itests/src/test/java/org/apache/unomi/itests/AllITs.java
index 9dd9910..4642094 100644
--- a/itests/src/test/java/org/apache/unomi/itests/AllITs.java
+++ b/itests/src/test/java/org/apache/unomi/itests/AllITs.java
@@ -50,6 +50,7 @@ import org.junit.runners.Suite.SuiteClasses;
PatchIT.class,
ContextServletIT.class,
SecurityIT.class,
+ RuleServiceIT.class,
GraphQLEventIT.class,
GraphQLListIT.class,
GraphQLProfileIT.class,
diff --git a/itests/src/test/java/org/apache/unomi/itests/ConditionBuilder.java b/itests/src/test/java/org/apache/unomi/itests/ConditionBuilder.java
index 2420aed..2d01f97 100644
--- a/itests/src/test/java/org/apache/unomi/itests/ConditionBuilder.java
+++ b/itests/src/test/java/org/apache/unomi/itests/ConditionBuilder.java
@@ -63,6 +63,10 @@ public class ConditionBuilder {
return new PropertyCondition(conditionTypeId, propertyName, definitionsService);
}
+ public ConditionItem condition(String conditionTypeId) {
+ return new ConditionItem(conditionTypeId, definitionsService);
+ }
+
public abstract class ComparisonCondition extends ConditionItem {
ComparisonCondition(String conditionTypeId, DefinitionsService definitionsService) {
@@ -287,7 +291,7 @@ public class ConditionBuilder {
}
}
- public abstract class ConditionItem {
+ public class ConditionItem {
protected Condition condition;
diff --git a/itests/src/test/java/org/apache/unomi/itests/RuleServiceIT.java b/itests/src/test/java/org/apache/unomi/itests/RuleServiceIT.java
index ee25a09..917bea3 100644
--- a/itests/src/test/java/org/apache/unomi/itests/RuleServiceIT.java
+++ b/itests/src/test/java/org/apache/unomi/itests/RuleServiceIT.java
@@ -16,25 +16,33 @@
*/
package org.apache.unomi.itests;
-import org.apache.unomi.api.Metadata;
+import org.apache.unomi.api.*;
import org.apache.unomi.api.rules.Rule;
import org.apache.unomi.api.services.DefinitionsService;
+import org.apache.unomi.api.services.EventService;
import org.apache.unomi.api.services.RulesService;
import org.apache.unomi.persistence.spi.PersistenceService;
import org.junit.Before;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
+import org.ops4j.pax.exam.spi.reactors.PerSuite;
import org.ops4j.pax.exam.util.Filter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
+import java.util.*;
+
+import static org.junit.Assert.*;
/**
* Integration tests for the Unomi rule service.
*/
+@RunWith(PaxExam.class)
+@ExamReactorStrategy(PerSuite.class)
public class RuleServiceIT extends BaseIT {
private final static Logger LOGGER = LoggerFactory.getLogger(RuleServiceIT.class);
@@ -48,6 +56,10 @@ public class RuleServiceIT extends BaseIT {
@Inject
@Filter(timeout = 600000)
+ protected EventService eventService;
+
+ @Inject
+ @Filter(timeout = 600000)
protected PersistenceService persistenceService;
@Inject
@@ -74,5 +86,96 @@ public class RuleServiceIT extends BaseIT {
assertNull("Expected rule actions to be null", nullRule.getActions());
assertNull("Expected rule condition to be null", nullRule.getCondition());
assertEquals("Invalid rule name", TEST_RULE_ID + "_name", nullRule.getMetadata().getName());
+ rulesService.removeRule(TEST_RULE_ID);
+ refreshPersistence();
+ rulesService.refreshRules();
+ }
+
+ @Test
+ public void testRuleEventTypeOptimization() throws InterruptedException {
+
+ ConditionBuilder builder = new ConditionBuilder(definitionsService);
+ Rule simpleEventTypeRule = new Rule(new Metadata(TEST_SCOPE, "simple-event-type-rule", "Simple event type rule", "A rule with a simple condition to match an event type"));
+ simpleEventTypeRule.setCondition(builder.condition("eventTypeCondition").parameter("eventTypeId", "view").build());
+ rulesService.setRule(simpleEventTypeRule);
+ Rule complexEventTypeRule = new Rule(new Metadata(TEST_SCOPE, "complex-event-type-rule", "Complex event type rule", "A rule with a complex condition to match multiple event types with negations"));
+ complexEventTypeRule.setCondition(
+ builder.not(
+ builder.or(
+ builder.condition("eventTypeCondition").parameter( "eventTypeId", "view"),
+ builder.condition("eventTypeCondition").parameter("eventTypeId", "form")
+ )
+ ).build()
+ );
+ rulesService.setRule(complexEventTypeRule);
+
+ refreshPersistence();
+ rulesService.refreshRules();
+
+ Profile profile = new Profile(UUID.randomUUID().toString());
+ Session session = new Session(UUID.randomUUID().toString(), profile, new Date(), TEST_SCOPE);
+ Event viewEvent = new Event(UUID.randomUUID().toString(), "view", session, profile, TEST_SCOPE, null, null, new Date());
+ Set<Rule> matchingRules = rulesService.getMatchingRules(viewEvent);
+
+ assertTrue("Simple rule should be matched", matchingRules.contains(simpleEventTypeRule));
+ assertFalse("Complex rule should NOT be matched", matchingRules.contains(complexEventTypeRule));
+
+ rulesService.removeRule(simpleEventTypeRule.getItemId());
+ rulesService.removeRule(complexEventTypeRule.getItemId());
+ refreshPersistence();
+ rulesService.refreshRules();
+ }
+
+ @Test
+ public void testRuleOptimizationPerf() throws NoSuchFieldException, IllegalAccessException {
+ Profile profile = new Profile(UUID.randomUUID().toString());
+ Session session = new Session(UUID.randomUUID().toString(), profile, new Date(), TEST_SCOPE);
+
+ rulesService.setSetting("optimizedRulesActivated", false);
+
+ LOGGER.info("Running unoptimized rules performance test...");
+ long unoptimizedRunTime = runEventTest(profile, session);
+
+ rulesService.setSetting("optimizedRulesActivated", true);
+
+ LOGGER.info("Running optimized rules performance test...");
+ long optimizedRunTime = runEventTest(profile, session);
+
+ LOGGER.info("Unoptimized run time = {}ms, optimized run time = {}ms. Improvement={}x", unoptimizedRunTime, optimizedRunTime, ((double) unoptimizedRunTime) / ((double) optimizedRunTime));
+ assertTrue("Optimized run time should be smaller than unoptimized", unoptimizedRunTime > optimizedRunTime);
+ }
+
+ private long runEventTest(Profile profile, Session session) {
+ Event viewEvent = generateViewEvent(session, profile);
+ int loopCount = 0;
+ long startTime = System.currentTimeMillis();
+ while (loopCount < 500) {
+ eventService.send(viewEvent);
+ viewEvent = generateViewEvent(session, profile);
+ loopCount++;
+ }
+ return System.currentTimeMillis() - startTime;
+ }
+
+ private Event generateViewEvent(Session session, Profile profile) {
+ CustomItem sourceItem = new CustomItem();
+ sourceItem.setScope(TEST_SCOPE);
+
+ CustomItem targetItem = new CustomItem();
+ targetItem.setScope(TEST_SCOPE);
+ Map<String,Object> targetProperties = new HashMap<>();
+
+ Map<String,Object> pageInfo = new HashMap<>();
+ pageInfo.put("language", "en");
+ pageInfo.put("destinationURL", "https://www.acme.com/test-page.html");
+ pageInfo.put("referringURL", "https://unomi.apache.org");
+ pageInfo.put("pageID", "ITEM_ID_PAGE");
+ pageInfo.put("pagePath", "/test-page.html");
+ pageInfo.put("pageName", "Test page");
+
+ targetProperties.put("pageInfo", pageInfo);
+
+ targetItem.setProperties(targetProperties);
+ return new Event(UUID.randomUUID().toString(), "view", session, profile, TEST_SCOPE, sourceItem, targetItem, new Date());
}
}
diff --git a/package/src/main/resources/etc/custom.system.properties b/package/src/main/resources/etc/custom.system.properties
index 5538ada..19e4824 100644
--- a/package/src/main/resources/etc/custom.system.properties
+++ b/package/src/main/resources/etc/custom.system.properties
@@ -177,6 +177,10 @@ org.apache.unomi.segment.recalculate.period=${env:UNOMI_SEGMENT_RECALCULATE_PERI
org.apache.unomi.rules.refresh.interval=${env:UNOMI_RULES_REFRESH_INTERVAL:-1000}
# The interval in milliseconds to use to reload the rules statistics
org.apache.unomi.rules.statistics.refresh.interval=${env:UNOMI_RULES_STATISTICS_REFRESH_INTERVAL:-10000}
+# If this setting is active, the rules engine will try to classify the events by event type internally which makes
+# rules execution a lot faster. If there are any problems detected with rules execution, you might want to try to turn
+# off the optimization and file a bug report if this fixed the problem.
+org.apache.unomi.rules.optimizationActivated=${env:UNOMI_RULES_OPTIMIZATION_ACTIVATED:-true}
#######################################################################################################################
## Third Party server settings ##
diff --git a/services/src/main/java/org/apache/unomi/services/impl/ParserHelper.java b/services/src/main/java/org/apache/unomi/services/impl/ParserHelper.java
index ef9b9de..45016cb 100644
--- a/services/src/main/java/org/apache/unomi/services/impl/ParserHelper.java
+++ b/services/src/main/java/org/apache/unomi/services/impl/ParserHelper.java
@@ -48,7 +48,7 @@ public class ParserHelper {
final List<String> result = new ArrayList<String>();
visitConditions(rootCondition, new ConditionVisitor() {
@Override
- public void visit(Condition condition) {
+ public void visit(Condition condition, Stack<String> conditionTypeStack) {
if (condition.getConditionType() == null) {
ConditionType conditionType = definitionsService.getConditionType(condition.getConditionTypeId());
if (conditionType != null) {
@@ -63,7 +63,7 @@ public class ParserHelper {
}
}
}
- });
+ }, new Stack<>());
return result.isEmpty();
}
@@ -71,31 +71,33 @@ public class ParserHelper {
final List<String> result = new ArrayList<String>();
visitConditions(rootCondition, new ConditionVisitor() {
@Override
- public void visit(Condition condition) {
+ public void visit(Condition condition, Stack<String> conditionTypeStack) {
result.add(condition.getConditionTypeId());
}
- });
+ }, new Stack<>());
return result;
}
- private static void visitConditions(Condition rootCondition, ConditionVisitor visitor) {
- visitor.visit(rootCondition);
+ private static void visitConditions(Condition rootCondition, ConditionVisitor visitor, Stack<String> conditionTypeStack) {
+ visitor.visit(rootCondition, conditionTypeStack);
+ conditionTypeStack.push(rootCondition.getConditionTypeId());
// recursive call for sub-conditions as parameters
for (Object parameterValue : rootCondition.getParameterValues().values()) {
if (parameterValue instanceof Condition) {
Condition parameterValueCondition = (Condition) parameterValue;
- visitConditions(parameterValueCondition, visitor);
+ visitConditions(parameterValueCondition, visitor, conditionTypeStack);
} else if (parameterValue instanceof Collection) {
@SuppressWarnings("unchecked")
Collection<Object> valueList = (Collection<Object>) parameterValue;
for (Object value : valueList) {
if (value instanceof Condition) {
Condition valueCondition = (Condition) value;
- visitConditions(valueCondition, visitor);
+ visitConditions(valueCondition, visitor, conditionTypeStack);
}
}
}
}
+ conditionTypeStack.pop();
}
public static boolean resolveActionTypes(DefinitionsService definitionsService, Rule rule) {
@@ -144,6 +146,43 @@ public class ParserHelper {
}
interface ConditionVisitor {
- void visit(Condition condition);
+ void visit(Condition condition, Stack<String> conditionTypeStack);
+ }
+
+ public static Set<String> resolveConditionEventTypes(Condition rootCondition) {
+ if (rootCondition == null) {
+ return new HashSet<>();
+ }
+ EventTypeConditionVisitor eventTypeConditionVisitor = new EventTypeConditionVisitor();
+ visitConditions(rootCondition, eventTypeConditionVisitor, new Stack<>());
+ return eventTypeConditionVisitor.getEventTypeIds();
+ }
+
+ static class EventTypeConditionVisitor implements ConditionVisitor {
+
+ private Set<String> eventTypeIds = new HashSet<>();
+
+ public void visit(Condition condition, Stack<String> conditionTypeStack) {
+ if ("eventTypeCondition".equals(condition.getConditionTypeId())) {
+ String eventTypeId = (String) condition.getParameter("eventTypeId");
+ if (eventTypeId == null) {
+ logger.warn("Null eventTypeId found!");
+ } else {
+ // we must now check the stack to see how many notConditions we have in the parent stack
+ if (conditionTypeStack.contains("notCondition")) {
+ logger.warn("Found complex negative event type condition, will always evaluate rule");
+ eventTypeIds.add("*");
+ } else {
+ eventTypeIds.add(eventTypeId);
+ }
+ }
+ } else if (condition.getConditionType().getParentCondition() != null) {
+ visitConditions(condition.getConditionType().getParentCondition(), this, conditionTypeStack);
+ }
+ }
+
+ public Set<String> getEventTypeIds() {
+ return eventTypeIds;
+ }
}
}
diff --git a/services/src/main/java/org/apache/unomi/services/impl/rules/RulesServiceImpl.java b/services/src/main/java/org/apache/unomi/services/impl/rules/RulesServiceImpl.java
index 4c601d2..1422eeb 100644
--- a/services/src/main/java/org/apache/unomi/services/impl/rules/RulesServiceImpl.java
+++ b/services/src/main/java/org/apache/unomi/services/impl/rules/RulesServiceImpl.java
@@ -36,6 +36,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
+import java.lang.reflect.Field;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@@ -64,6 +65,9 @@ public class RulesServiceImpl implements RulesService, EventListenerService, Syn
private List<RuleListenerService> ruleListeners = new CopyOnWriteArrayList<RuleListenerService>();
+ private Map<String,Set<Rule>> rulesByEventType = new HashMap<>();
+ private Boolean optimizedRulesActivated = true;
+
public void setBundleContext(BundleContext bundleContext) {
this.bundleContext = bundleContext;
}
@@ -96,6 +100,10 @@ public class RulesServiceImpl implements RulesService, EventListenerService, Syn
this.rulesStatisticsRefreshInterval = rulesStatisticsRefreshInterval;
}
+ public void setOptimizedRulesActivated(Boolean optimizedRulesActivated) {
+ this.optimizedRulesActivated = optimizedRulesActivated;
+ }
+
public void postConstruct() {
logger.debug("postConstruct {" + bundleContext.getBundle() + "}");
@@ -157,9 +165,20 @@ public class RulesServiceImpl implements RulesService, EventListenerService, Syn
Boolean hasEventAlreadyBeenRaisedForSession = null;
Boolean hasEventAlreadyBeenRaisedForProfile = null;
- List<Rule> allItems = allRules;
+ Set<Rule> eventTypeRules = new HashSet<>(allRules); // local copy to avoid concurrency issues
+ if (optimizedRulesActivated) {
+ eventTypeRules = rulesByEventType.get(event.getEventType());
+ if (eventTypeRules == null || eventTypeRules.isEmpty()) {
+ return matchedRules;
+ }
+ eventTypeRules = new HashSet<>(eventTypeRules);
+ Set<Rule> allEventRules = rulesByEventType.get("*");
+ if (allEventRules != null && !allEventRules.isEmpty()) {
+ eventTypeRules.addAll(allEventRules); // retrieve rules that should always be evaluated.
+ }
+ }
- for (Rule rule : allItems) {
+ for (Rule rule : eventTypeRules) {
if (!rule.getMetadata().isEnabled()) {
continue;
}
@@ -240,12 +259,23 @@ public class RulesServiceImpl implements RulesService, EventListenerService, Syn
allRuleStatistics.put(ruleStatistics.getItemId(), ruleStatistics);
}
+ public void refreshRules() {
+ try {
+ allRules = getAllRules();
+ } catch (Throwable t) {
+ logger.error("Error loading rules from persistence back-end", t);
+ }
+ }
+
private List<Rule> getAllRules() {
List<Rule> allItems = persistenceService.getAllItems(Rule.class, 0, -1, "priority").getList();
+ Map<String,Set<Rule>> newRulesByEventType = new HashMap<>();
for (Rule rule : allItems) {
ParserHelper.resolveConditionType(definitionsService, rule.getCondition(), "rule " + rule.getItemId());
+ updateRulesByEventType(newRulesByEventType, rule);
ParserHelper.resolveActionTypes(definitionsService, rule);
}
+ this.rulesByEventType = newRulesByEventType;
return allItems;
}
@@ -335,6 +365,7 @@ public class RulesServiceImpl implements RulesService, EventListenerService, Syn
Rule rule = persistenceService.load(ruleId, Rule.class);
if (rule != null) {
ParserHelper.resolveConditionType(definitionsService, rule.getCondition(), "rule " + rule.getItemId());
+ updateRulesByEventType(rulesByEventType, rule);
ParserHelper.resolveActionTypes(definitionsService, rule);
}
return rule;
@@ -348,6 +379,7 @@ public class RulesServiceImpl implements RulesService, EventListenerService, Syn
if (condition != null) {
if (rule.getMetadata().isEnabled() && !rule.getMetadata().isMissingPlugins()) {
ParserHelper.resolveConditionType(definitionsService, condition, "rule " + rule.getItemId());
+ updateRulesByEventType(rulesByEventType, rule);
definitionsService.extractConditionBySystemTag(condition, "eventCondition");
}
}
@@ -381,15 +413,23 @@ public class RulesServiceImpl implements RulesService, EventListenerService, Syn
persistenceService.remove(ruleId, Rule.class);
}
+ @Override
+ public void setSetting(String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException {
+ Field field = this.getClass().getDeclaredField(fieldName);
+ field.set(this, value);
+ }
+
+ @Override
+ public Object getSetting(String fieldName) throws NoSuchFieldException, IllegalAccessException {
+ Field field = this.getClass().getDeclaredField(fieldName);
+ return field.get(this);
+ }
+
private void initializeTimers() {
TimerTask task = new TimerTask() {
@Override
public void run() {
- try {
- allRules = getAllRules();
- } catch (Throwable t) {
- logger.error("Error loading rules from persistence back-end", t);
- }
+ refreshRules();
}
};
schedulerService.getScheduleExecutorService().scheduleWithFixedDelay(task, 0,rulesRefreshInterval, TimeUnit.MILLISECONDS);
@@ -507,4 +547,16 @@ public class RulesServiceImpl implements RulesService, EventListenerService, Syn
}
}
+ private void updateRulesByEventType(Map<String,Set<Rule>> rulesByEventType, Rule rule) {
+ Set<String> eventTypeIds = ParserHelper.resolveConditionEventTypes(rule.getCondition());
+ for (String eventTypeId : eventTypeIds) {
+ Set<Rule> rules = rulesByEventType.get(eventTypeId);
+ if (rules == null) {
+ rules = new HashSet<>();
+ }
+ rules.add(rule);
+ rulesByEventType.put(eventTypeId, rules);
+ }
+ }
+
}
diff --git a/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml
index d302661..55c41cb 100644
--- a/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+++ b/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml
@@ -43,6 +43,7 @@
<cm:property name="rules.refresh.interval" value="1000"/>
<cm:property name="rules.statistics.refresh.interval" value="10000"/>
<cm:property name="events.shouldBeCheckedEventSourceId" value="false"/>
+ <cm:property name="rules.optimizationActivated" value="true"/>
</cm:default-properties>
</cm:property-placeholder>
@@ -165,6 +166,7 @@
<property name="schedulerService" ref="schedulerServiceImpl"/>
<property name="rulesRefreshInterval" value="${services.rules.refresh.interval}"/>
<property name="rulesStatisticsRefreshInterval" value="${services.rules.statistics.refresh.interval}"/>
+ <property name="optimizedRulesActivated" value="${services.rules.optimizationActivated}"/>
</bean>
<service id="rulesService" ref="rulesServiceImpl">
<interfaces>
diff --git a/services/src/main/resources/org.apache.unomi.services.cfg b/services/src/main/resources/org.apache.unomi.services.cfg
index a1535b4..ee23a4c 100644
--- a/services/src/main/resources/org.apache.unomi.services.cfg
+++ b/services/src/main/resources/org.apache.unomi.services.cfg
@@ -69,3 +69,8 @@ rules.statistics.refresh.interval=${org.apache.unomi.rules.statistics.refresh.in
# The indicator should be checked is there a sourceId in the system or not
events.shouldBeCheckedEventSourceId=${org.apache.unomi.events.shouldBeCheckedEventSourceId:-false}
+
+# If this setting is active, the rules engine will try to classify the events by event type internally which makes
+# rules execution a lot faster. If there are any problems detected with rules execution, you might want to try to turn
+# off the optimization and file a bug report if this fixed the problem.
+rules.optimizationActivated=${org.apache.unomi.rules.optimizationActivated:-true}
\ No newline at end of file