Home » Articles » Unique Product Features » Compiled Rule Sets

Compiled Rule Sets

By Mark Joseph - September 17, 2008 @ 7:51 pm

Compile once and evaluate multiple times

When a developer wishes to use the P6R rule engine (reference An XPath Enabled Rule Engine), he can simply create a single instance of the rule engine component, load it with a set of rules (defined in XML), load the stated facts (in XML or JSON), and generate inferred facts by calling the evaluate() method one or more times. This particular use case will be fine for many applications, but what about applications that need to evaluate the same rule set concurrently (e.g., adserving)?

The approach we have taken to support multiple, concurrently, evaluating rule engine instances is that of “Compiled Rule Sets”. (This is similar to the approach we have taken to improve performance for XSLT, described in the article: Compiled Templates in XSLT 2.0.)

Compiled Rule Sets as objects

The P6R rule engine (p6IRuleEngine) can be used in a simple mode of execution where a set of rules are first loaded (via the modifyRules() method) and then evaluated to generate inferred facts (via the evaluate() method). For some applications this mode of execution will be acceptable.

However, for high performance applications the same set of rules will need to be executed concurrently by several threads. Compiling the same set of rules over and over again for each thread is slow and wasteful, thus we support compiling a rule set into a data structure that is a P6R component on its own. Two additional interfaces support this complex mode of execution. First, the p6IRuleSets interface allows the caller to extract out (as well as to load in) a compiled set of rules from a running rule engine instance. Second, the compiled rules are represented by the p6IRuleCompiled interface (which allows the compiled rules to be marked with a name for caching purposes). The compiled set of rules contains almost no execution state and thus can be shared with multiple instances of the p6IRuleEngine component at the same time.

A compiled rule set contains all XML elements (see reference above for a description of the XML language defining rules) in a tree of nodes along with compiled, embedded XPath expressions, and compiled forms of any regular expressions that might appear in XPath expressions (i.e., the Non-deterministic Finite State Automation that represents the regex is its compiled form). Only compiled regular expressions contain run-time state in a compiled rule set and each is protected with its own lock. During run time all other rule engine execution state (e.g., global variables, facts tree) are all associated with the instance of the rule engine component and not with the compiled rule set object.

The simple mode of execution involves the following sequence of statements:

p6IDataStream* pOutStream;  // output results to a stream (a file or socket)
p6IRuleEngine* pRules;  // instance of the rule engine

// call class factory to get a rule engine instance
p6CreateInstance(... IID_p6IRuleEngine, &pRules );
// . . . .
// first compile a set of rules
pRules->modifyRules( <buffer containing XML rules> );

// facts can be streamed in via one or more calls
pRules->startFacts( <buffer containing XML or JSON stated facts> );

// generate inferred facts by evaluating all the relevant rules over the given stated facts
pRules->evaluate( <rule set name to start eval> );

// output the results
pRules->outputInferredFacts( pOutStream );
pRules->release();

In the complex mode of execution, one or more “compiled” rule set objects are generated by performing the following sequence of statements:

p6IDataStream* pOutStream;  // output results or error messages
p6IRuleEngine* pRules;  // instance of the rule engine
p6IRuleEngine* pAnotherRules;
p6IRuleEngine* pThirdRules;
p6IRuleSets* pSets1; // compiled rule set interface
p6IRuleSets* pSets2;
p6IRuleSets* pSets3;
p6IRuleCompiled* pCompiled; // compiled rule set object

// call class factory to get rule engine instances
p6CreateInstance(... IID_p6IRuleEngine, &pRules );
p6CreateInstance(... IID_p6IRuleEngine, &pAnotherRules );
p6CreateInstance(... IID_p6IRuleEngine, &pThirdRules );

// get interfaces that support compiled rule set objects
pRules->queryInterface( p6IRuleSets, &pSets1 );
pAnotherRules->queryInterface( p6IRuleSets, &pSets2 );
pThirdRules->queryInterface( p6IRuleSets, &pSets3 );

// extract the compiled rule sets from a running rule engine instance
pRules->modifyRules( <buffer containing XML rules> );
pSets1->getRules( &pCompiled1 );
. . .
// load the compiled rule sets into other instances of the rule engine for execution
pSets2->setRules( pCompiled );
pAnotherRules->startFacts( <buffer containing XML or JSON stated facts> );
pAnotherRules->evaluate( <rule set name to start eval> );
pAnotherRules->outputInferredFacts( pOutStream );  // a separate set of inferred fact results

pSets3->setRules( pCompiled );
pThirdRules->startFacts( <buffer containing XML or JSON stated facts> );
pThirdRules->evaluate( <rule set name to start eval> );
pCompiled1->release();
// . . .

Now what happens if any one of the threads sharing the same compiled rules object invoke the “modifyRules” method with a group of commands that add/delete/modify the existing rule set? The p6IRuleCompiled object is protected by a P6R reader/writer lock, thus ensuring that the rules are modified in a consistent way. Just to be clear, if one of the threads modify the shared rule set, then the rules change for all the using threads. When a compiled set of rules is no longer needed the caller invokes the standard “release()” method on the component to free it.

Caching the stated facts

P6R’s rule engine has two main interfaces to load the stated facts. The first interface allows the facts to be streamed in (as XML or JSON) via repeaded calls to the methods: p6IRuleEngine::startFacts() and p6IRuleEngine::continueFacts() methods. The second interface requires that the caller first parses the facts document into a DOM tree and then loads that DOM tree directly into the rule engine via the p6IRuleEngine::startFactsWithDOM() method. Clearly once the facts document has been parsed into a P6R DOM tree component (i.e., p6IDOMXML interface),
it can be cached in memory and used over and over again without repeaded parsing thus allowing for faster execution.

Summary

P6R’s compiled rule sets are aimed at providing increase performance for highly threaded applicaitons.
With our rule engine a developer can either use our compiled rules or just use the rule engine in a simple mode of operation with a easy to use API. Our goal is to provide a flexible and powerful rule engine solution to all developers.

"Compiled Rule Sets" was published on September 17th, 2008 and is listed in Unique Product Features.

Follow comments via the RSS Feed | Leave a comment | Trackback URL


Leave Your Comment