Autoscaling Application Block - reactive rule semantics

There are two versions of this post. If you are not interested in various approaches we have evaluated, please read the short version and cast your vote.

Short version

Please take a look at these two conditionals which are meant to trigger scaling actions.

Style 1 – “fluenty” XML

 <when>
  <all>
    <greater operand="Avg_CPU_45M_RoleAC" than="80"/>
    <greater operand="Avg_CPU_20M_RoleB"
             than="1.4*Avg_CPU_1H_RoleB"/>
    <any>
      <not>
        <less operand="QueueB_Length_10M_Avg" than="200"/>
      </not>
      <equals operand="Max_UnprocessedOrders" to="300"/>
    </any>
  </all>
</when>

Style 2 – “textual expression”

 <when expression="Avg_CPU_45M_RoleAC gt 80 AND 
                Avg_CPU_20M_RoleB gt 1.4*Avg_CPU_1H_RoleB AND
                (!QueueB_Length_10M_Avg lt 200 OR 
                  Max_UnprocessedOrders = 300)" />

If you needed to define new rules in an XML config file, which one would you prefer – style 1 or 2?

Long version

We’re making steady progress on the Windows Azure Integration Pack project, code named “WASABi” for Windows Azure Scaling Application Block infrastructure, which is the main and the most exciting addition to the family of application blocks (as a side note, I particularly like the mnemonic since it reflects the caution you should use when adding autoscaling to your app just as you should use caution when you add wasabi to your meal.)

As a reminder, there are two types of rules we’ll be supporting:

- Constraint rules (based on timetables)

- Reactive rules (based on conditions)

The constraint rules implementation is complete and we are now working on the reactive rules: their semantics have turned out to be somewhat challenging. If you read my first post, you’ll notice that I used KPIs in the rule definitions. The idea was to trigger an action when the KPI status was red. We thought about this further and made a decision to depart from the KPI-centric representation in the rules and instead focus on more condition-centric expressions. They seem to be more intuitive to write and interpret, especially when you are trying to define scale-down rules. This approach has been vetted by our advisors.

The next question is how we should express these conditions.

The table below summarizes several different approaches we have evaluated.

Strategy name

Condition expression

Notes

Nested XML nodes

<condition>

  <or>

    <compare operator="greater" threshold="70">

      <cpuRoleAverage roleName="RoleFoo" timespan="45"/>

    </compare>

    (…)

  </or>

</condition>

+ Easy for developers

– Not as easy for IT Pros

“Fluenty” XML

<when>

  <any>

    <greater operand="Avg_CPU_45M_RoleFoo" than="70"/>

    <greater operand="Avg_CPU_20M_RoleB" (…)

  </any>

</when>

o Variation of the above

+ More approachable

 

Expression with operands

"CpuUsageAverage45_RoleFoo gt 70 OR QueueLengthAverage30_QueueBar gt 200"

 

<operands>

  <performanceCounter name="CpuUsageAverage45_RoleFoo" timespan="45" performanceCounterName="\Processor(_Total)\% Processor Time" aggregateFunction="average" roleName="RoleFoo"/>

   (…)

</operands>

o Equality comparers may be expressed as “a > b” or “a gt b” or even “a greaterThan b”

+ Flexibility: clear delimitation between what to monitor (operands) and how to evaluate (conditions)

– Reusability: Source of data collection specified in the operand itself making it less reusable by other rules

Expression with pseudo functions

"CpuUsageAverage45(RoleFoo) gt 70 OR QueueLengthAverage30(QueueBar) gt 200"

"CpuUsageAverage(RoleFoo,45) gt 70 OR QueueLengthAverage(QueueBar,30) gt 200"

 

<functions>

  <performanceCounter name="CpuUsageAverage45" timespan="45" performanceCounterName="\Processor(_Total)\% Processor Time" aggregateFunction="average"/>

  (…)

</functions>

+ Reusability: function can be reused for different kinds of reactive rules with different sources

– Functions limited to receive a single parameter of string type (not always a good option for all different data sources or aggregate functions)

– Simplicity: Defining the functions may be harder than using operands

Textual expression

"RoleFoo.CpuUsageAverage gt 70 in 45 min span OR QueueBar.QueueLengthAverage gt 200 in 30 min span"

 

<operands>

  <performanceCounter alias="CpuUsageAverage" performanceCounterName="\Processor(_Total)\% Processor Time" aggregate="Average"/>

</operands>

o Half-way between a textual DSL and an XML expression tree

+ Readability: Much more readable since it's almost like natural language

– Need to specify a basic grammar for it

– Issue of dealing with multiple sources

The conditionals are meant to be a part of the following definition, where the actions are defined in the <do>

element and the operands for conditionals in the dedicated <operands> section:

 <reactiveRules>
  <rule name="CPU spike" rank="100">

    <!-- CONDITION -->

    <actions>
      <scale target="RoleB" factor="10" unit="Absolute"/>
      <scale target="ScaleGroupZ" factor="-200" unit="PercentChange"/>“
    </actions>
  </rule>
</reactiveRules>

<operands>
  <performanceCounter alias="Avg_CPU_45_RoleAC" source="S:MySub" 
                      performanceCounterName="\Processor(_Total)\% Processor Time" timespan="00:45:00" aggregate="Average"/>
  <performanceCounter alias="Avg_CPU_20_RoleB" source="RoleB"
                      performanceCounterName="\Processor(_Total)\% Processor Time" timespan="00:20:00" aggregate="Average"/>
  <performanceCounter alias="Avg_CPU_60_RoleB" source="RoleB" 
                      performanceCounterName="\Processor(_Total)\% Processor Time" timespan="01:00:00" aggregate="Average"/>
  <queueLength alias="QueueB_Length_10M_Avg" queue="QueueB" timespan="00:10:00" aggregate="Average"/>
  <unprocessedOrders xmlns="https://schemas.microsoft.com/practices/2011/entlib/autoscaling-demo/rules" 
                     alias="Max_UnprocessedOrders" aggregate="Max" connectionString="falskfdsf"/>
</operands>

We have now narrowed our choices down to these two options:

Style 1 – “fluenty” XML

 <when>
  <all>
    <greater operand="Avg_CPU_45M_RoleAC" than="80"/>
    <greater operand="Avg_CPU_20M_RoleB"
             than="1.4*Avg_CPU_1H_RoleB"/>
    <any>
      <not>
        <less operand="QueueB_Length_10M_Avg" than="200"/>
      </not>
      <equals operand="Max_UnprocessedOrders" to="300"/>
    </any>
  </all>
</when>

Style 2 – “textual expression”

 <when expression="Avg_CPU_45M_RoleAC gt 80 AND 
                  Avg_CPU_20M_RoleB gt 1.4*Avg_CPU_1H_RoleB AND
                  (!QueueB_Length_10M_Avg lt 200 OR 
                    Max_UnprocessedOrders = 300)" />

Which one do you prefer? Why?

Keep in mind, the idea is still to provide a layer on top of XML for people to manipulate the rules. We are envisioning a set of PowerShell commandlets and also including a sample UI in the reference implementation for people to see how they can implement their own rule editors and make those part of their application management consoles/portals.