Skip to content

Improve cdk diff Performance with Parallel Change Set Creation #1946

Description

@sonal-aws

Describe the feature

cdk diff does not provide an option to configure parallelism for creating change sets. Unlike cdk deploy, which supports the --concurrency option, change-set creation during cdk diff cannot be configured to run in parallel. As a result, repositories with more than 100 stacks can take a significant amount of time to generate the change sets, impacting the overall cdk diff execution time.

Use Case

If we have 100+ stacks in the repository, cdk diff take significant amount of time to generate the change sets

Proposed Solution

No response

Other Information

No response

Acknowledgements

  • I may be able to implement this feature request
  • This feature might incur a breaking change

AWS CDK Library version (aws-cdk-lib)

2.263.0

AWS CDK CLI version

2.1135.0

Environment details (OS name and version, etc.)

macOS 26.6.2

Activity

  1. pahud commented on Sep 4, 2026

    @pahud
    Contributor

    Hi @sonal-aws,

    ┌────────────────────────────────────────────────────────────┐
    │ 🔴 Reported State                                          │
    │ Diff change sets run one stack at a time                   │
    └─────────────────────────────┬──────────────────────────────┘
                                  │
                                  ▼
    ┌────────────────────────────────────────────────────────────┐
    │ 🔍 Root Cause                                              │
    │ Diff has no concurrency option or bounded parallel path    │
    └─────────────────────────────┬──────────────────────────────┘
                                  │
                                  ▼
    ┌────────────────────────────────────────────────────────────┐
    │ 🟢 Suggested Fix                                           │
    │ Add opt-in bounded concurrency with stable output          │
    └────────────────────────────────────────────────────────────┘
    

    I was able to reproduce the configuration gap on macOS with the reported AWS CDK CLI 2.1135.0 and aws-cdk-lib 2.263.0. I then repeated the same check with the latest releases, AWS CDK CLI 2.1140.0 and aws-cdk-lib 2.268.0, with the same result: cdk diff --help has no concurrency option, and passing --concurrency 2 prints that the option is unknown and ignored before diffing both stacks and exiting successfully. I used --change-set=false for these local reproductions, so I did not create cloud resources or benchmark the reported 100-plus-stack latency.

    It appears the request matches the current implementation. CdkToolkit.diff() iterates over stacks and awaits each deployed-template read and diff change-set creation before advancing:

    // Compare N stacks against deployed templates
    for (const stack of stacks.stackArtifacts) {
    const templateWithNestedStacks = await this.props.deployments.readCurrentTemplateWithNestedStacks(
    stack,
    options.compareAgainstProcessedTemplate,
    );
    const currentTemplate = templateWithNestedStacks.deployedRootTemplate;
    const nestedStacks = templateWithNestedStacks.nestedStacks;
    const migrator = new ResourceMigrator({
    deployments: this.props.deployments,
    ioHelper: asIoHelper(this.ioHost, 'diff'),
    });
    const resourcesToImport = await migrator.tryGetResources(await this.props.deployments.resolveEnvironment(stack));
    if (resourcesToImport) {
    removeNonImportResources(stack);
    }
    const changeSet = (options.method !== 'template')
    ? (await this.tryCreateDiffChangeSet(stack, options, parameterMap, resourcesToImport, quiet))?.changeSet
    : undefined;
    const mappings = allMappings.find(m =>
    m.environment.region === stack.environment.region && m.environment.account === stack.environment.account,
    )?.mappings ?? {};
    const formatter = new DiffFormatter({
    templateInfo: {
    oldTemplate: currentTemplate,
    newTemplate: stack,
    changeSet,
    isImport: !!resourcesToImport,
    nestedStacks,
    mappings,
    },
    });
    if (options.securityOnly) {
    const securityDiff = formatter.formatSecurityDiff({ quiet });
    // Warn, count, and display the diff only if the reported changes are broadening permissions
    if (securityDiff.permissionChangeType === PermissionChangeType.BROADENING) {
    await this.ioHost.asIoHelper().defaults.warn('This deployment will make potentially sensitive changes according to your current security approval level.\nPlease confirm you intend to make the following modifications:\n');
    await this.ioHost.asIoHelper().defaults.info(securityDiff.formattedDiff);
    diffs += securityDiff.numStacksWithChanges;
    }
    } else {
    const diff = formatter.formatStackDiff({
    strict,
    contextLines,
    quiet,
    });
    await this.ioHost.asIoHelper().defaults.info(diff.formattedDiff);
    diffs += diff.numStacksWithChanges;
    }
    }

    Current toolkit-lib retains the same sequential shape in cfnDiff(), and latest CLI 2.1140.0 still does not expose diff concurrency:

    async function cfnDiff(
    ioHelper: IoHelper,
    stacks: StackCollection,
    deployments: Deployments,
    options: DiffOptions,
    sdkProvider: SdkProvider,
    includeChangeSet: boolean,
    ): Promise<TemplateInfo[]> {
    const templateInfos = [];
    const methodOptions = (options.method?.options ?? {}) as ChangeSetDiffOptions;
    const allMappings = options.includeMoves
    ? await mappingsByEnvironment(stacks.stackArtifacts, sdkProvider, true)
    : [];
    // Compare N stacks against deployed templates
    for (const stack of stacks.stackArtifacts) {
    const templateWithNestedStacks = await deployments.readCurrentTemplateWithNestedStacks(
    stack,
    methodOptions.compareAgainstProcessedTemplate,
    );
    const currentTemplate = templateWithNestedStacks.deployedRootTemplate;
    const nestedStacks = templateWithNestedStacks.nestedStacks;
    const environment = await deployments.resolveEnvironment(stack);
    const migrator = new ResourceMigrator({ deployments, ioHelper });
    const resourcesToImport = await migrator.tryGetResources(environment);
    if (resourcesToImport) {
    removeNonImportResources(stack);
    }
    const changeSet = includeChangeSet ? (await cfnApi.createDiffChangeSet(ioHelper, {
    deployments,
    stack,
    sdkProvider,
    resourcesToImport,
    parameters: methodOptions.parameters ?? {},
    failOnError: !(methodOptions.fallbackToTemplate ?? true),
    importExistingResources: methodOptions.importExistingResources,
    uuid: randomUUID(),
    }))?.changeSet : undefined;
    // If the changeset includes nested stacks, describe each nested changeset
    // and attach it to the corresponding entry in nestedStacks.
    if (changeSet) {
    await attachNestedChangeSetData(ioHelper, deployments, stack, changeSet, nestedStacks);
    }
    const mappings = allMappings.find(m =>
    m.environment.region === stack.environment.region && m.environment.account === stack.environment.account,
    )?.mappings ?? {};
    templateInfos.push({
    oldTemplate: currentTemplate,
    newTemplate: stack,
    isImport: !!resourcesToImport,
    nestedStacks,
    changeSet,
    mappings,
    environment,
    });
    }
    return templateInfos;

    If lower-fidelity replacement analysis is acceptable, cdk diff --method=template is available today and is documented as faster but less accurate. If you require change-set accuracy, that workaround does not cover the use case.

    From my view, this is a valid unresolved feature request. A compatible design would add an opt-in diff concurrency value defaulting to 1, perform bounded parallel stack preparation, and preserve deterministic output plus cleanup/fallback behavior. Because the implementation is in aws/aws-cdk-cli, I am transferring this issue there. Contributions would be welcome, particularly with tests for concurrency bounds, CloudFormation throttling/failures, output ordering, nested stacks, and change-set cleanup.

  2. transferred this issue fromaws/aws-cdkon Sep 4, 2026
  3. added theissue type on Sep 11, 2026
  4. added and removed on Sep 21, 2026
  5. proton0210 commented on Oct 8, 2026

    @proton0210
    Contributor

    Implemented the bounded opt-in concurrency path in draft PR #2031, for both CLI diff and toolkit-lib. Default concurrency remains 1; concurrent preparation preserves ordered notifications/results, stops queued work after failure and waits for in-flight cleanup. Local validation passed 92 toolkit tests and 108 CLI tests, compilation, API Extractor and scoped lint. Tests include a simulated 125-stack workload, throttling fallback/strict failure, nested stacks, ordering and cleanup. A live concurrent independent/nested-stack integration test is included but not yet run; no AWS latency benchmark is claimed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions