JBoss.orgCommunity Documentation

Chapter 2. Quick start

2.1. Cloud balancing tutorial
2.1.1. Problem statement
2.1.2. Problem size
2.1.3. Domain model diagram
2.1.4. Main method
2.1.5. Solver configuration
2.1.6. Domain model implementation
2.1.7. Score configuration
2.1.8. Beyond this tutorial

Suppose your company owns a number of cloud computers and needs to run a number of processes on those computers. Assign each process to a computer under the following 4 constraints.

Hard constraints which must be fulfilled:

Soft constraints which should be optimized:

How would you do that? This problem is a form of bin packing. Here's a simplified example where we assign 4 processes to 2 computers with 2 constraints (CPU and RAM) with a simple algorithm:

The simple algorithm used here is the First Fit Decreasing algorithm, which assigns the bigger processes first and assigns the smaller processes to the remaining space. As you can see, it's not optimal, because it does not leave enough room to assign the yellow process D.

OptaPlanner does find the more optimal solution fast, by using additional, smarter algorithms. And it scales too: both in data (more processes, more computers) and constraints (more hardware requirements, other constraints). So let's take a look how we can use Planner for this.

Try it yourself. Download and configure the examples in your favorite IDE. Run org.optaplanner.examples.cloudbalancing.app.CloudBalancingHelloWorld. By default, it is configured to run for 120 seconds. It will execute this code:


The code above does this:

  • Build the Solver based on a solver configuration (in this case an XML file).

            SolverFactory solverFactory = new XmlSolverFactory(
    
                    "/org/optaplanner/examples/cloudbalancing/solver/cloudBalancingSolverConfig.xml");
            Solver solver = solverFactory.buildSolver();
  • Load the problem. CloudBalancingGenerator generates a random problem: you'll replace this with a class that loads a real problem, for example from a database.

            CloudBalance unsolvedCloudBalance = new CloudBalancingGenerator().createCloudBalance(400, 1200);
  • Solve the problem.

            solver.setPlanningProblem(unsolvedCloudBalance);
    
            solver.solve();
            CloudBalance solvedCloudBalance = (CloudBalance) solver.getBestSolution();
  • Display the result.

            System.out.println("\nSolved cloudBalance with 400 computers and 1200 processes:\n"
    
                    + toDisplayString(solvedCloudBalance));

The only non-obvious part is building the Solver. Let's examine that.

Take a look at the solver configuration:


This solver configuration consists out of 3 parts:

  • Domain model configuration: What can Planner change? We need to make Planner aware of our domain classes:

    
      <solutionClass>org.optaplanner.examples.cloudbalancing.domain.CloudBalance</solutionClass>
      <planningEntityClass>org.optaplanner.examples.cloudbalancing.domain.CloudProcess</planningEntityClass>
  • Score configuration: How should Planner optimize the planning variables? Since we have hard and soft constraints, we use a HardSoftScore. But we also need to tell Planner how to calculate such the score, depending on our business requirements. Further down, we 'll look into 2 alternatives to calculate the score: using a simple Java implementation or using Drools DRL.

    
      <scoreDirectorFactory>
        <scoreDefinitionType>HARD_SOFT</scoreDefinitionType>
        <simpleScoreCalculatorClass>org.optaplanner.examples.cloudbalancing.solver.score.CloudBalancingSimpleScoreCalculator</simpleScoreCalculatorClass>
        <!--<scoreDrl>/org/optaplanner/examples/cloudbalancing/solver/cloudBalancingScoreRules.drl</scoreDrl>-->
      </scoreDirectorFactory>
  • Optimization algorithms configuration: How should Planner optimize it? Don't worry about this for now: this is a good default configuration that works on most planning problems. It will already surpass human planners and most in-house implementations. Using the Planner benchmark toolkit, you can tweak it to get even better results.

    
      <termination>
        <maximumSecondsSpend>120</maximumSecondsSpend>
      </termination>
      <constructionHeuristic>
        <constructionHeuristicType>FIRST_FIT_DECREASING</constructionHeuristicType>
        <!--forager-->
          <pickEarlyType>FIRST_NON_DETERIORATING_SCORE</pickEarlyType>
        <!--/forager-->
      </constructionHeuristic>
      <localSearch>
        <acceptor>
          <entityTabuSize>7</entityTabuSize>
        </acceptor>
        <forager>
          <acceptedCountLimit>1000</acceptedCountLimit>
        </forager>
      </localSearch>

Let's examine the domain model classes and the score configuration.

The class CloudBalance implements the Solution interface. It holds a list of all computers and processes. We need to tell Planner how to retrieve the collection of process which it can change, so we need to annotate the getter getProcessList with @PlanningEntityCollectionProperty.

The CloudBalance class also has a property score which is the Score of that Solution instance in it's current state:


The method getProblemFacts() is only needed for score calculation with Drools. It's not needed for the other score calculation types.

Planner will search for the Solution with the highest Score. We're using a HardSoftScore, which means Planner will look for the solution with no hard constraints broken (fulfill hardware requirements) and as little as possible soft constraints broken (minimize maintenance cost).

Of course, Planner needs to be told about these domain-specific score constraints. There are several ways to implement such a score function:

Let's take a look at 2 different implementations:

One way to define a score function is to implement the interface SimpleScoreCalculator in plain Java.


  <scoreDirectorFactory>
    <scoreDefinitionType>HARD_SOFT</scoreDefinitionType>
    <simpleScoreCalculatorClass>org.optaplanner.examples.cloudbalancing.solver.score.CloudBalancingSimpleScoreCalculator</simpleScoreCalculatorClass>
  </scoreDirectorFactory>

Just implement the method calculateScore(Solution) to return a HardSoftScore instance.

Example 2.6. CloudBalancingSimpleScoreCalculator.java

public class CloudBalancingSimpleScoreCalculator implements SimpleScoreCalculator<CloudBalance> {


    /**
     * A very simple implementation. The double loop can easily be removed by using Maps as shown in
     * {@link CloudBalancingMapBasedSimpleScoreCalculator#calculateScore(CloudBalance)}.
     */
    public HardSoftScore calculateScore(CloudBalance cloudBalance) {
        int hardScore = 0;
        int softScore = 0;
        for (CloudComputer computer : cloudBalance.getComputerList()) {
            int cpuPowerUsage = 0;
            int memoryUsage = 0;
            int networkBandwidthUsage = 0;
            boolean used = false;
            // Calculate usage
            for (CloudProcess process : cloudBalance.getProcessList()) {
                if (computer.equals(process.getComputer())) {
                    cpuPowerUsage += process.getRequiredCpuPower();
                    memoryUsage += process.getRequiredMemory();
                    networkBandwidthUsage += process.getRequiredNetworkBandwidth();
                    used = true;
                }
            }
            
            // Hard constraints
            int cpuPowerAvailable = computer.getCpuPower() - cpuPowerUsage;
            if (cpuPowerAvailable < 0) {
                hardScore += cpuPowerAvailable;
            }
            int memoryAvailable = computer.getMemory() - memoryUsage;
            if (memoryAvailable < 0) {
                hardScore += memoryAvailable;
            }
            int networkBandwidthAvailable = computer.getNetworkBandwidth() - networkBandwidthUsage;
            if (networkBandwidthAvailable < 0) {
                hardScore += networkBandwidthAvailable;
            }
            
            // Soft constraints
            if (used) {
                softScore -= computer.getCost();
            }
        }
        return HardSoftScore.valueOf(hardScore, softScore);
    }
}

Even if we optimize the code above to use Maps to iterate through the processList only once, it is still slow because it doesn't do incremental score calculation. To fix that, either use an incremental Java score function or a Drools score function. Let's take a look at the latter.

To use the Drools rule engine as a score function, simply add a scoreDrl resource in the classpath:


  <scoreDirectorFactory>
    <scoreDefinitionType>HARD_SOFT</scoreDefinitionType>
    <scoreDrl>/org/optaplanner/examples/cloudbalancing/solver/cloudBalancingScoreRules.drl</scoreDrl>
  </scoreDirectorFactory>

First, we want to make sure that all computers have enough CPU, RAM and network bandwidth to support all their processes, so we make these hard constraints:


Next, if those constraints are met, we want to minimize the maintenance cost, so we add that as a soft constraint:


If you use the Drools rule engine for score calculation, you can integrate with other Drools technologies, such as decision tables (XLS or web based), the KIE Workbench rule repository, ...