Skip to content

Improve OptimizelyConfig instantiation performance #817

Closed
@alexZielonko

Description

@alexZielonko

How would the enhancement work?

Reduce the number of times that the OptimizelyConfig's instantiation iterates over the datafile's feature flag list.

Current State Summary

  1. The OptimizelyConfig instance iterates over each feature flag in a datafile when the constructor calls OptimizelyConfig.getFeaturesMap
  2. getDeliveryRules is called during each iteration, which in turn calls getVariableIdMap
  3. getVariableIdMap iterates over each feature flag and each feature flag's variables

That is, each feature flag iteration in getFeaturesMap incurs another complete iteration of the feature flags when creating the variablesIdMap.

Due to the nested loop, creating an OptimizelyConfig instance with a hypothetical datafile containing 100 feature flags iterates over the entire feature flag list over 10,000 times.

👈 For reference, here's the complete logic flow

1. Constructor calls OptimizelyConfig.getFeaturesMap

this.featuresMap = OptimizelyConfig.getFeaturesMap(configObj, featureIdVariablesMap, experimentsMapById);

2. getFeaturesMap iterates over each feature flag

static getFeaturesMap(
configObj: ProjectConfig,
featureVariableIdMap: FeatureVariablesMap,
experimentsMapById: OptimizelyExperimentsMap
): OptimizelyFeaturesMap {
const featuresMap: OptimizelyFeaturesMap = {};
configObj.featureFlags.forEach((featureFlag) => {

3. OptimizelyConfig.getDeliveryRules is called during each feature flag iteration

const rollout = configObj.rolloutIdMap[featureFlag.rolloutId];
if (rollout) {
deliveryRules = OptimizelyConfig.getDeliveryRules(

4. getDeliveryRules calls OptimizelyConfig.getVariableIdMap

const variableIdMap = OptimizelyConfig.getVariableIdMap(configObj);

5. getVariableIdMap iterates over all of the feature flags again & each of the feature flag's variables

static getVariableIdMap(configObj: ProjectConfig): { [id: string]: FeatureVariable } {
let variablesIdMap: { [id: string]: FeatureVariable } = {};
variablesIdMap = (configObj.featureFlags || []).reduce((resultMap: { [id: string]: FeatureVariable }, feature) => {
feature.variables.forEach((variable) => {
resultMap[variable.id] = variable;
});
return resultMap;
}, {});
return variablesIdMap;
}


Proposed State

My familiarity with the optimizely-sdk's internals is limited. Based on this narrow understanding, I see a couple of ways to reduce the number of times that the OptimizelyConfig iterates over the feature flag list.

👈 One option - Create a single variablesIdMap in the constructor

The constructor iterates over the features flags to generate the featureIdVariablesMap. This reducer's callback could be extended to simultaneously create a single variablesIdMap. The variablesIdMap can then be passed to the getFeaturesMap call, considerably reduceing the variablesIdMap function's overhead.

const featureIdVariablesMap = (configObj.featureFlags || []).reduce((resultMap: FeatureVariablesMap, feature) => {
resultMap[feature.id] = feature.variables;
return resultMap;
}, {});

👈 Another option - Use the featureVariableIdMap to pass a specific feature's variables directly to getVariableIdMap

As getDeliveryRules has both the featureVariableIdMap and featureId, the feature's variables can be passed directly to getVariableIdMap. This removes the need to iterate over the entire feature flag list.

static getDeliveryRules(
configObj: ProjectConfig,
featureVariableIdMap: FeatureVariablesMap,
featureId: string,

This enhancement might look something like,

  static getDeliveryRules(
    configObj: ProjectConfig,
    featureVariableIdMap: FeatureVariablesMap,
    featureId: string,
    experiments: Experiment[]
  ): OptimizelyExperiment[] {
    // Call `getVariableIdMap` with `featureVariableIdMap[featureId]`, an array of the feature's variables
    const variableIdMap = OptimizelyConfig.getVariableIdMap(featureVariableIdMap[featureId]);
  static getVariableIdMap(featureVariables: FeatureVariable[]): { [id: string]: FeatureVariable } {
    let variablesIdMap: { [id: string]: FeatureVariable } = {};

    // Iterate over the `featureVariables` rather than the entire datafile's feature flag list
    variablesIdMap = (featureVariables || []).reduce((resultMap: { [id: string]: FeatureVariable }, variable) => {
      resultMap[variable.id] = variable;
      return resultMap;
    }, {});

    return variablesIdMap;
  }

When would the enhancement be useful?

This is a useful performance enhancement for all @optimizely/javascript-sdk users, but particularly those with a large number of feature flags in their datafile.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions