eqixiacEquinix Infrastructure as Code

Metro Optimizer

Portfolio solver that selects the Equinix metros for a design. Returns a recommendation, a trade-off frontier, sensitivity analysis and workload placement.

Method#

The optimizer selects the set of metros for a DesignSpec. In 4.0 it evaluates sets of metros (portfolios). Release 3.0.0 scored each metro on its own, which cannot measure worst-case site latency, total cost or the share that survives a metro loss.

Steps of a run:

  1. Load the metro catalogue, the measured inter-metro latencies and the clouds reachable in each metro.
  2. Remove each metro that a constraint excludes on its own.
  3. Enumerate every admissible portfolio from the minimum to the maximum size.
  4. Check each portfolio against the constraints and measure it on each objective.
  5. Price each portfolio through the rate cards.
  6. Keep the Pareto-optimal portfolios (the frontier) and recommend the one that the weights rank first.
  7. Compute the sensitivity of the recommendation to the weights and to each constraint.
  8. Place the workloads on the recommended portfolio.

The search is exhaustive and exact for the given data, up to 2,000,000 portfolios. The size range is minMetros (or the minimum of the redundancy tier, or 1) to maxMetros (default 3).

Running#

From a document:

Java
import com.eqixiac.equinix.design.optimizer.MetroOptimizer;
import com.eqixiac.equinix.design.optimizer.model.OptimizationResult;
import com.eqixiac.equinix.design.spec.DesignSpec;
import com.eqixiac.equinix.design.spec.DesignSpecIO;

DesignSpec spec = DesignSpecIO.read(Path.of("designs/payments-platform.yaml"));
OptimizationResult result = MetroOptimizer.forSpec(fabric, spec).run();

With the fluent builder, which assembles the same document:

Java
import com.eqixiac.equinix.core.enums.MetroCode;
import com.eqixiac.equinix.design.optimizer.enums.ComplianceZone;
import com.eqixiac.equinix.design.optimizer.enums.OptimizationStrategy;
import com.eqixiac.equinix.design.optimizer.enums.RedundancyTier;
import com.eqixiac.equinix.design.optimizer.enums.SiteRole;
import com.eqixiac.equinix.design.optimizer.enums.WorkloadType;
import com.eqixiac.equinix.fabric.model.implementation.cloud.CloudProviderType;

OptimizationResult result = fabric.optimizeMetros()
    .name("payments-platform")
    .addSite("New York HQ").nearestMetro(MetroCode.NY).role(SiteRole.HEADQUARTERS).headcount(500).done()
    .addSite("London office").nearestMetro(MetroCode.LD).headcount(200).done()
    .addSite("Singapore office").coordinates(1.3521, 103.8198).headcount(150).done()
    .requireProvider(CloudProviderType.AWS).region("us-east-1").monthlyEgressGb(40_000).done()
    .requireProvider(CloudProviderType.AZURE).done()
    .preferProvider(CloudProviderType.GOOGLE_CLOUD).done()
    .addWorkload("card-authorisation")
        .type(WorkloadType.TRANSACTIONAL).bandwidthMbps(2_000).maxLatencyToleranceMs(25.0)
        .dependsOn(CloudProviderType.AWS).done()
    .addWorkload("settlement-dr")
        .type(WorkloadType.DISASTER_RECOVERY).bandwidthMbps(500)
        .dependsOn(CloudProviderType.AZURE).done()
    .constraints()
        .monthlyBudget(60_000)
        .redundancy(RedundancyTier.MULTI_REGION)
        .compliance(ComplianceZone.EU_GDPR)
        .maxLatencyMs(100)                       // round-trip, from every site to its nearest metro
        .excludeMetro(MetroCode.MX)
        .maxMetros(4)
        .done()
    .strategy(OptimizationStrategy.BALANCED)
    .optimize();

A cloud added with requireProvider must be reachable from the portfolio. A cloud added with preferProvider counts toward the coverage objective and is not mandatory. A provider can also be named by its service profile name, for a network or SaaS provider that is not one of the well-known clouds.

Objectives#

Each objective is measured in its own unit. Scores are normalized across the admissible portfolios before the weights are applied.

Objective idMeasuresUnitUnknown when
latencySite-weighted round-trip latency from each site to its nearest metro in the portfolioms, lower is betterNo site latency is known. A site with no known latency is left out and named.
costExpected monthly total from the cost modelCurrency, lower is betterAny item is unpriced, or the lines span currencies. An understated total would reward missing data.
provider_coverageShare of the spec's clouds reachable privately from at least one metro; a required cloud counts twice0 to 100Never. 100 when the spec names no cloud.
resilienceWhat survives the worst single-metro loss: the mean of the surviving shares of site weight, required clouds and placed workloads0 to 100Nothing to measure. A one-metro design scores 0.
complianceShare of metro and zone pairs that satisfy the stated zones0 to 100Never. 100 when the spec states no zone.

The weight profiles are listed under DesignSpec. .strategy(OptimizationStrategy.LATENCY_FIRST) selects a profile and .weights(Map) sets explicit weights.

Constraints#

A portfolio that violates a constraint is infeasible, whatever its score.

ConstraintInput
Required and excluded metrosrequireMetro, excludeMetro
Required and excluded regionsrequiredRegions, excludedRegions
Compliance zones, geo zonescompliance, geoZones
Minimum redundancyredundancy(RedundancyTier)
Metro countminMetros, maxMetros
Maximum site latencymaxLatencyMs: every site must have a metro within the limit. A site with unknown latency does not exclude a metro.
Workload latencymaxLatencyToleranceMs per workload
Pinned workloadspinnedMetro
Required cloudsEvery required cloud must be reachable from the portfolio
BudgetmonthlyBudget
Every metro usedA portfolio must not contain a metro that serves nothing

Latency data#

All latencies are round-trip. Each figure comes from the first of the following three sources that applies.

  1. Measured: connectedMetros.avgLatency from the Fabric Metros API.
  2. Estimated: the round-trip fibre floor over the great-circle distance, multiplied by a route factor. The factor is the median ratio of measured round trip to fibre floor over the metro pairs that Fabric measures, fitted for the whole catalogue and per region pair where enough pairs exist (getLatencyCalibration()).
  3. Unknown: no measurement and no coordinates.

Release 3.0.0 mixed a one-way estimate with round-trip measurements and assumed 150 ms where it had no data. In 4.0 every figure is round-trip, and the latency matrix marks estimated values and prints unknown ones as ?.

Reading the result#

Java
import com.eqixiac.equinix.design.solve.SensitivityReport;
import com.eqixiac.equinix.design.spi.ConstraintVerdict;
import com.eqixiac.equinix.design.spi.CostLine;
import com.eqixiac.equinix.design.spi.DesignOption;

System.out.println(result.toSummary());

DesignOption recommended = result.getRecommended();
System.out.println("Metros: " + recommended.portfolio().metros());
recommended.values().forEach((objective, value) ->
    System.out.println(objective + " = " + value.value() + " (" + value.explanation() + ")"));

for (CostLine line : recommended.cost().lines()) {
    System.out.println(line.label() + " " + line.monthly() + " " + line.currency()
        + " [" + line.source() + "] range " + line.monthlyLow() + " to " + line.monthlyHigh());
}
recommended.cost().unpriced().forEach(item -> System.out.println("unpriced: " + item));

for (ConstraintVerdict verdict : result.getConstraintVerdicts()) {
    System.out.println(verdict.constraintId() + " " + (verdict.satisfied() ? "ok" : "FAILED") + " " + verdict.reason());
}

for (DesignOption option : result.getFrontier()) {       // every Pareto-optimal alternative
    System.out.println(option.portfolio().key() + " score " + option.weightedScore());
}

SensitivityReport sensitivity = result.getSensitivity();
for (SensitivityReport.WeightRange range : sensitivity.weightRanges()) {
    System.out.println(range.objective() + " weight " + range.weight()
        + " keeps the recommendation between " + range.low() + " and " + range.high());
}
sensitivity.constraints().forEach(c ->
    System.out.println(c.constraint() + (c.binding() ? " is binding: " : " is not binding: ") + c.explanation()));

Files.writeString(Path.of("metro-optimization.md"), result.toMarkdown());
String json = result.toJson();

run() and optimize() always return a result. When no portfolio is feasible, the result says which constraints rejected the candidates, and the sensitivity report names the constraint whose relaxation would admit an alternative.

The result also keeps the per-metro views of earlier releases: getRecommendations(), primaryMetro(), getLatencyMatrix(), getTopology(), getRiskAssessment() and getCostEstimate().

Cost ranges#

Each cost line carries a range that depends on the source of its price.

Price sourceRange
EQUINIX_LIVE, CUSTOM, PROVIDER_APIExact
REFERENCE, COMPOSITEPlus or minus 25%
ESTIMATEPlus or minus 50%

The two percentages are fixed values chosen by judgment and have no statistical basis.

Sensitivity#

  • Per objective: the weight range over which the recommendation stays the same, and the portfolio recommended outside that range.
  • The nearest alternative and its difference on each objective.
  • Per constraint: whether it is binding, the number of portfolios it alone rejected, and the alternative admitted without it.

Narrow weight ranges mean that a small change of a weight changes the recommendation. Compare the frontier alternatives in that case.

Placement#

result.getPlacement() assigns each workload a primary metro, and a standby where the design has one, and lists the cloud legs per metro. The Deployment Wizard plans connections from this placement.

Workload types#

WorkloadType sets defaults for a workload, chiefly its latency sensitivity.

TypeDefault latency sensitivity
REALTIME_COLLABORATION, EDGE_COMPUTECritical
AI_ML_INFERENCE, TRANSACTIONALHigh
AI_ML_TRAINING, GENERAL_COMPUTE, BIG_DATA_ANALYTICSMedium
DISASTER_RECOVERY, COLD_BACKUPLow

latencySensitivity(...) and maxLatencyToleranceMs(...) override the default. The 3.0.0 builder also took power-density and liquid-cooling flags, which the 4.0 builder does not accept. requireMetro or pinnedMetro holds a workload in a metro whose facility is known to meet such a requirement.

Extension#

Objectives, constraints, the cost model and the solver are interfaces in design.spi.

Java
import com.eqixiac.equinix.design.spi.Constraint;
import com.eqixiac.equinix.design.spi.DesignContext;
import com.eqixiac.equinix.design.spi.DesignPlugin;
import com.eqixiac.equinix.design.spi.MetroPortfolio;

public class NoSingleCountryPlugin implements DesignPlugin {
    @Override public String name() { return "no-single-country"; }

    @Override public List<Constraint> constraints() {
        return List.of(new Constraint() {
            @Override public String id() { return "no_single_country"; }
            @Override public ConstraintVerdict check(MetroPortfolio portfolio, DesignContext context) {
                long countries = portfolio.metros().stream()
                    .map(id -> context.metro(id).map(m -> m.country()).orElse("?"))
                    .distinct().count();
                return countries > 1
                    ? ConstraintVerdict.pass(id(), countries + " countries")
                    : ConstraintVerdict.fail(id(), "all metros are in one country");
            }
        });
    }
}
Java
OptimizationResult withPlugin = MetroOptimizer.forSpec(fabric, DesignSpecIO.read(Path.of("designs/payments-platform.yaml")))
    .plugin(new NoSingleCountryPlugin())
    .run();

Plug-ins can also be discovered through java.util.ServiceLoader. rateCard(...) and costModel(...) on the optimizer replace the pricing.

Limits#

  • The optimizer chooses metros. It does not choose an IBX within a metro and has no data on cage availability or power.
  • The optimizer treats a cloud as reachable in a metro when a service profile of that provider is offered there. It does not check capacity or the provider's own regional constraints.
  • The measured latency is the Fabric inter-metro figure. Latency from a site to a metro over a carrier circuit is an estimate from distance.
  • Cost uses the rate-card chain and inherits its limits, see Cost and value.
  • The number of portfolios grows combinatorially with maxMetros. A high maxMetros on the full catalogue can reach the limit of 2,000,000 portfolios, and the solver notes of the result then state that the answer is not exact.