Red Hat Decision Manager - ルール再評価でProperty Reactiveがデフォルトに


Red Hat Decision Manager 7から、ルールの再評価において「Property Reactive」がデフォルトになりました。
どういうことかといいますと、
あるファクトのプロパティに変更を加えて、再評価を指示した際、今まではそのファクトのクラスが条件に含まれている全ルールを対象として、再評価が走っていました。(いわゆるClass Reactive)
しかし、「Property Reactive」は変更のあったプロパティが条件に含まれているルールのみを対象として再評価を行います。
これにより、再評価対象のルールが絞り込まれるため、無駄がなくなり、再評価の性能が上がると思われます。

そこで、今回は、Property Reactiveの再評価の動きについて以前との違いを簡単に説明します。

そもそもルールの再評価とは

ルールエンジンの基本動作については、以下の過去記事を参照ください。
ルールエンジンの基本動作

ルールの「評価」とは

ファクトがKieSessionにインサートされ、ルール実行を指示すると、ルールエンジンは、KieSession内の全ファクトについてルール条件と一致するかどうかを確認します。これが最初の「評価(evaluate)」です。
評価によって一致したファクトとルールの組み合わせは、アクティベーションとしてアジェンダに登録され、順次実行されていきます。アジェンダのアクティベーションが無くなったら、ルール実行は終了します。

ルールの「再評価」とは

ルールの評価は、何も指示しない限り、最初の実行時の一回だけです。しかし、ルールAの実行により、ファクトの値が変更された場合に、変更後の値で再度評価をしたいという場合があります。
それを可能にするのが、「再評価」です。
ファクトの状態が変わったことをルールエンジンに通知し、再度評価を実行することができます。

具体的には、modifyを使ってそれを通知します。

rule "60歳以上は10%OFF"
when
    $customer : 顧客( 年齢 >= 60 )
then
    modify($customer) { set割引率( 0.1 ) };
end

上記ルールが実行されると、顧客ファクトの割引率プロパティには、0.1がセットされ、modifyによってその変化がルールエンジンに通知され、ルールエンジンは再度ルールの評価を行います。

modifyの他に、delete,insert,updateキーワードがあった場合も、ルールは再評価を実行します。

この再評価の仕組みにより、Red Hat Decision Managerのルールエンジンは推論を可能にしています。

Property Reactive による再評価の動きの違いを確認してみる

Hello Worldサンプルで、Property ReactiveのON/OFFでどう変わるか見てみます。
使用したRed Hat Decision Managerのバージョンは、7.2.1です。
runtime.versionはこうなります。
<runtime.version>7.14.0.Final-redhat-00004</runtime.version>

以下のファクトとルールで試します。

ファクト

public class Message {

    public static final int HELLO = 0;
    public static final int GOODBYE = 1;

    private String message;
    private int status;
    ・・・
}

ルール


rule "test1"
    when
        m : Message( message == "Hello World", myMessage : message )
    then
        System.out.println( "test1:" + myMessage );
        modify( m ) {setStatus(Message.GOODBYE)};
end

rule "test2"
    when
        Message( status == Message.GOODBYE, myMessage : message )
    then
        System.out.println( "test2:" + myMessage );
end

実行コード

   public static final void main(String[] args) {
        try {
            // load up the knowledge base
            KieServices ks = KieServices.Factory.get();
            KieContainer kContainer = ks.getKieClasspathContainer();
            KieSession kSession = kContainer.newKieSession("ksession-rules");

            // go !
            Message message = new Message();
            message.setMessage("Hello World");
            message.setStatus(Message.HELLO);
            kSession.insert(message);
            kSession.fireAllRules(10);

        } catch (Throwable t) {
            t.printStackTrace();
        }
    }

Hello Worldサンプルほぼそのままですが、無限ループするため、fireAllRulesの実行回数制限を入れています。
kSession.fireAllRules(10);

Property Reactive ON (バージョン7のデフォルト)

まずは、バージョン7系のデフォルト、Property Reactiveで実行してみます。

上記の実行コードを実行し、結果を確認します。

test1:Hello World
test2:Hello World

test1ルールが最初に動いて、test2ルールが次に動いて終了していますね。

Property Reactive OFF (バージョン6までのデフォルト)

次に以前のClass Reactiveでの動きを見てみます。
バージョン7系では、デフォルトがProperty Reactiveのため、まずはそれを変更します。

1.実行コードで、KieSession生成前にSystemPropertyの値をセットして(以下の1行を挿入)実行します。

  System.setProperty("drools.propertySpecific", "ALLOWED");

  ※ちなみに、drools.propertySpecificの値は3種類。

- DISABLED => Property Reactiveにはならない
- ALLOWED => @PropertyReactiveアノテーションが明示的についていない限り、Property Reactiveにはならない。
- ALWAYS => すべてのクラスがProperty Reactive。バージョン7系のデフォルト。

つまり、"ALLOWED"にした場合でも、クラスに@PropertyReactiveアノテーションをつけると、そのクラスはPropertyReactiveになるということですね。
"DISABLED"の場合は、アノテーションも無視される。

2.実行結果を確認します。

test1:Hello World
test1:Hello World
test1:Hello World
test1:Hello World
test1:Hello World
test1:Hello World
test1:Hello World
test1:Hello World
test1:Hello World
test1:Hello World

test1ルールが、10回実行されています。kSession.fireAllRules(10);で10を指定したため、10回で止まっていますが、指定無しであれば、無限ループします。

Property Reactive ON/OFFでの違い

では、なぜ動きが異なったのかを解説します。

ここでは、まずはじめに、KieSessionにMessageファクトが1つ渡され、ルール実行しています。

            Message message = new Message();
            message.setMessage("Hello World");
            message.setStatus(Message.HELLO);
            kSession.insert(message);
            kSession.fireAllRules(10);

上記のMessageファクトは、"test1"ルールに一致します。"test2"には一致しません。

rule "test1"
    when
        m : Message( message == "Hello World", myMessage : message )

そのため、まずは"test1"ルールが実行されます。

    then
        System.out.println( "test1:" + myMessage );
        modify( m ) {setStatus(Message.GOODBYE)};
    end

"test1"ルールのthen節のログが出力されます。

test1:Hello World

ここまでは、Property ReactiveのON/OFFにかかわらず、同じ動作です。
次に、"test1"ルールのthen節では、Messageオブジェクトのstatusプロパティに値がセットされ、再評価を促すmodifyが指定されているため、再評価が行われます。

この再評価の対象となるルールが、Property ReactiveのON/OFFによって変わることがあり、ここではそれが結果に影響を及ぼしています。

2つのルールの条件(when節)に注目します。

"test1"
m : Message( message == "Hello World", myMessage : message )
Messageクラスのmessageプロパティについての条件

"test2"
Message( status == Message.GOODBYE, myMessage : message )
Messageクラスのstatusプロパティについての条件

Property ReactiveがONの場合は、変更のあったプロパティに対する条件文が含まれるルールのみ再評価の対象となるため、messageプロパティについての条件しかない"test1"は再評価対象とならず、statusプロパティについての条件式がある"test2"が再評価の対象となり、条件が一致するため実行されて終了となります。

Property ReactiveがOFFの場合は、プロパティが更新されたクラスに対する条件文が含まれる全ルールが再評価対象となるため、"test1"も再評価対象となり、"test1"は再び条件に一致するため、"test1"が無限に実行され続ける結果となっています。("test2"も一致するため、アクティベーションは作成されているはずですが、ここでは"test1"が先に実行されたために、このような結果になっていると思われます)

まとめ

Property Reactiveがデフォルトではなかった以前のバージョンでは、この例のような無限ループを防ぐために、no-loop=trueにしたり、または条件を追加して再評価はされても一致しないようにするなどの必要がありました。

ですが、Property Reactiveによって、そのような条件を省くことができるようになります。また再評価の対象が絞られることで、性能アップが期待できます。

バージョン6系で正しく動作していたルールについて、基本はバージョン7でも同じ結果になるはずですし、再評価の効率があがるため、高速化が期待できます。

ただし、以前のバージョンで、Class Reactiveの動作を期待してわざと再評価をさせて動かしていたルールがあったとしたら、そこはバージョン7に上げることで、再評価対象とならず、実行結果が変わる可能性があるため、注意が必要です。