C3 AI Documentation Home

Customize UI Bundling Configurations for C3 AI Release Management

For some C3 AI packages, the UI bundling step takes a long time when you don’t optimize the hardware configuration. Vertically scaling the UI bundling step by itself can help speed up performance significantly in these cases, allowing faster build times.

Overwriting the UI bundling configuration is most useful when UI bundling is the longest step in your build pipeline or when bundling retries occur due to insufficient memory or node eviction.

You configure UI bundling in two places:

  • The start environment spec, which specifies the desired hardware profile and availability for the bundling step.
  • The UI bundling config, which specifies the concurrency level for the bundling batch job.

Pre-requisite: Determine the right hardware profile for UI bundling

Before increasing maxConcurrencyPerNode, verify that your environment has enough CPU and memory to support additional parallel bundlers. You can inspect the hardware profile used for UI bundling by checking the node pool configuration:

JavaScript
// View all node pools in the environment
C3.app().nodePools();

// Inspect the configuration for the node pool used by UI bundling
// (typically "singlenode" or "task", depending on your deployment)
C3.app().nodePool('<node-pool-name>').config();

The hardware profile lists available CPU cores, memory (memoryMb), and maxConcurrentComputes. Each UI bundler process typically requires 8–12 GB of memory, depending on the size of the UI codebase.

To estimate whether your node has enough resources to support parallel bundlers, consider two configuration values:

  • Execute UiSdlConfig.inst().infrastructure in C3 AI Console and view the bundlerMaxMem field to see the maximum memory each bundler process may use.
  • Execute UiBundlerConfig.inst().configValue('maxConcurrencyPerNode') to view the number of bundler processes allowed to run in parallel.

These values combine to determine the total memory required for UI bundling:

Text
required memory = bundlerMaxMem * maxConcurrencyPerNode

This required memory must fit within the free memory on the node, after accounting for memory already reserved by c3server and other services. See the example below.

Example

If a node has 32 GB total memory, and approximately 50% is reserved for c3server and other services, then about 16 GB is available for UI bundling.

With:

  • bundlerMaxMem: 8192 (8 GB per bundler)
  • maxConcurrencyPerNode: 2

Total required memory:

Text
8 GB * 2 processes = 16 GB

Two bundlers may reach the node’s memory limit and cause retries or degraded performance. One bundler would be safer.

Rule of thumb

Scale concurrency linearly only when bundlerMaxMem * maxConcurrencyPerNode <= free memory on the node.

Approximate guidelines:

  • maxConcurrencyPerNode: 1: 8–12 GB.
  • maxConcurrencyPerNode: 2: 16–24 GB.
  • maxConcurrencyPerNode: 4: 32–48 GB or more.

Long webpack times, repeated retries, or memory-related failures usually indicate that the node pool's memory or CPU capacity is under-provisioned for the configured concurrency.

Overwrite the UI bundling step

See Customize the Build Pipeline in C3 AI Release Management for instructions on how to override and re-author a step in C3 AI Release Management.

In the default pipeline, UI bundling step uses a default spec. To override it, create a generateUiBundles.js file at the jarvisFilePath and define the new hardware configuration in the custom lambda.

Below is an example of how the generateUiBundles step can be overwritten:

JavaScript
data = {
  name: "generateUiBundles",
  value: (step) => {
    var idSuffix = 'highConcurrencyBundling';
    var shouldCreateNewBundlingStep = !step.id.includes(idSuffix);
    if (shouldCreateNewBundlingStep) {
      var startEnvSpec = JSON.stringify({
        // example of an optimized hardware profile for ui bundling
        {
          "singleNode": true,
          "nodePoolConfig": [
            {
              "name": "singlenode",
              "roles": [ "*" ],
              "hardwareProfile": {
                "cpu": 16,
                "gpu": 0,
                "diskGb": 100,
                "memoryMb": 64000,
                "hardwareAvailability": "balanced"
              },
              "autoScale": {
                "enabled": false
              },
              "sharedStorage": {
                "enabled": false
              },
              "jvm": {
                "jvmMaxMemoryPct": 0.25
              },
              "targetNodeCount": 1,
              "minNodeCount": 1,
              "maxNodeCount": 1,
              "queues": {
                "maxCpu": 0,
                "maxFullGcCount": 0,
                "maxFullGcTimeMillis": 0,
                "minFreeDatastoreDiskSpacePct": 0,
                "maxDatastoreFragmentationPct": 0,
                "maxDatastoreQueuedTaskCount": 0,
                "maxConcurrentComputes": 6
              }
            }
          ]
        }
      });

      // Resubmit the modified step with a unique id and make sure to have it
      // block the same steps as the original step (via inheritEdges "true")
      Jarvis.addSteps([step.withStartEnvSpec(startEnvSpec).withId(step.id + '-' + idSuffix)], true);
      return Jarvis.Step.Result.make({ step: step, status: Jarvis.Step.Status.SUCCESS });
    } else {
      // This is already a modified step, call the original logic
      return JarvisExecutor.Helper.generateUiBundles(step);
    }
  }
}

The platform terminates the original generateUiBundles step and starts a new one with the specified hardware profile.

Create the config file to modify UI bundling configs

See Create and View Build Configurations for C3 AI Release Management to learn how to define build configs.

In general, increase maxConcurrencyPerNode only when the hardware profile provides enough CPU and memory to support additional parallel bundlers. If concurrency is increased without sufficient resources, the bundling step may slow down instead of speeding up.

In config.js, define new UI bundler and UI SDL configs for the bundling step:

JavaScript
data = {
  configValues: {
    // Modifying `UiBundlerConfig` for dev bundling
    devUiBundlerConfig: { maxConcurrencyPerNode: 4 },

    // Modifying `UiBundlerConfig` for prod bundling
    prodUiBundlerConfig: { maxConcurrencyPerNode: 4, developmentBatchSize: 0 },

    // Modifying `UiSdlConfig` for dev bundling
    devUiSdlConfig: {
      infrastructure: {
        bundlerMaxMem: 12288
      }
    },

    // Modifying `UiSdlConfig` for prod bundling
    prodUiSdlConfig: {
      infrastructure: {
        bundlerMaxMem: 12288
      }
    },
  }
}

The generateUiBundles step reads these bundling config values and uses them to start bundling jobs.

If you increase maxConcurrencyPerNode in UiBundlerConfig, also increase maxConcurrentCompute in startEnvSpec field App.NodePool.Config.queues.maxConcurrentComputes to match or exceed it. The hardware profile config sets the upper limit for concurrent compute using the maxConcurrentCompute field.

Was this page helpful?