Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(aws-ddb): replicated tables can't have GSI's added to them. #26916

Closed
mbonig opened this issue Aug 28, 2023 · 3 comments
Closed

(aws-ddb): replicated tables can't have GSI's added to them. #26916

mbonig opened this issue Aug 28, 2023 · 3 comments
Labels
@aws-cdk/aws-autoscaling Related to Amazon EC2 Auto Scaling @aws-cdk/aws-dynamodb Related to Amazon DynamoDB bug This issue is a bug. response-requested Waiting on additional info and feedback. Will move to "closing-soon" in 7 days.

Comments

@mbonig
Copy link
Contributor

mbonig commented Aug 28, 2023

Describe the bug

When trying to add a GSI to a table that already exists, an error occurs.

Let's say I have defined a custom construct:

interface DynamoGlobalTableProps {
  autoScaleWriteCapacity?: {
    minCapacity: number;
    maxCapacity: number;
  };
  replicationRegions?: string[];
}

export class DynamoGlobalTable extends Table {
  constructor(scope: Construct, id: string, private props: DynamoGlobalTableProps) {
    super(scope, id, {
      partitionKey: {
        name: 'id',
        type: AttributeType.STRING,
      },
      pointInTimeRecovery: true,
      stream: StreamViewType.NEW_AND_OLD_IMAGES,
      replicationRegions: props.replicationRegions ?? ['us-west-2'],
      billingMode: BillingMode.PROVISIONED,
      ...props,
      removalPolicy: RemovalPolicy.DESTROY,
    });

    this.autoScaleWriteCapacity({
      maxCapacity: props.autoScaleWriteCapacity?.maxCapacity ?? 100,
      minCapacity: props.autoScaleWriteCapacity?.minCapacity ?? 1,
    }).scaleOnUtilization({
      targetUtilizationPercent: 80,
    });
  }

  /**
   * Adds a global secondary index to this table and additionally sets up autoscaling for write capacity to 100-1. This
   * method is extending the default addGlobalSecondaryIndex method from the Table construct. Anytime you're adding a
   * new global secondary index to a DynamoDB table, the autoscaling for write capacity units will be set up automatically.
   * @param props GlobalSecondaryIndexProps - The properties for the global secondary index.
   */
  addGlobalSecondaryIndex(props: GlobalSecondaryIndexProps) {
    super.addGlobalSecondaryIndex(props);
    // Explicitly set up autoscaling for write capacity units for the global secondary indexes.
    this.autoScaleGlobalSecondaryIndexWriteCapacity(props.indexName, {
      maxCapacity: this.props.autoScaleWriteCapacity?.maxCapacity ?? 100,
      minCapacity: this.props.autoScaleWriteCapacity?.minCapacity ?? 1,
    }).scaleOnUtilization({
      targetUtilizationPercent: 80,
    });
  }
}

And used this construct like so:

export class MyStack extends Stack {
  constructor(scope: Construct, id: string, props: StackProps = {}) {
    super(scope, id, props);

    const myTable = new DynamoGlobalTable(this, 'MyTable', {});
    myTable.addGlobalSecondaryIndex({
      indexName: 'myIndex',
      partitionKey: {
        name: 'myIndex',
        type: AttributeType.STRING,
      },
    });
  }
}

This will build and deploy as expected, with the table created, replicated to us-west-2, and set with autoscaling on the table and the GSI.

However, if a second index is added:

export class MyStack extends Stack {
  constructor(scope: Construct, id: string, props: StackProps = {}) {
    super(scope, id, props);

    const myTable = new DynamoGlobalTable(this, 'MyTable', {});
    myTable.addGlobalSecondaryIndex({
      indexName: 'myIndex',
      partitionKey: {
        name: 'myIndex',
        type: AttributeType.STRING,
      },
    });
    
    // added index:
    myTable.addGlobalSecondaryIndex({
      indexName: 'myIndex2',
      partitionKey: {
        name: 'myIndex2',
        type: AttributeType.STRING,
      },
    });
  }
}

This will error out with the following message from CFN:

UPDATE_ROLLBACK_COMPLETE: Resource handler returned message: "Resource of type 'AWS::ApplicationAutoScaling::ScalableTarget' with identifier 'table/testing-gsis-dev-MyTable794EDED1-125NEGNTPGUCN/index/myIndex2|dynamodb:index:WriteCapacityUnits|dynamodb' already exists." (RequestToken: 842ec7ee-6358-7ba1-6419-86bf54c86522, HandlerErrorCode: AlreadyExists)

There doesn't appear to be any combination of settings that will allow for the creation of indexes and the addition of them after the fact with the same code.

Expected Behavior

I would expect the second index to be added to the table without errors.

Current Behavior

An error occurs:

UPDATE_ROLLBACK_COMPLETE: Resource handler returned message: "Resource of type 'AWS::ApplicationAutoScaling::ScalableTarget' with identifier 'table/testing-gsis-dev-MyTable794EDED1-125NEGNTPGUCN/index/myIndex2|dynamodb:index:WriteCapacityUnits|dynamodb' already exists." (RequestToken: 842ec7ee-6358-7ba1-6419-86bf54c86522, HandlerErrorCode: AlreadyExists)

Reproduction Steps

Deploy this app:

import { App, RemovalPolicy, Stack, StackProps } from 'aws-cdk-lib';
import { AttributeType, BillingMode, GlobalSecondaryIndexProps, StreamViewType, Table } from 'aws-cdk-lib/aws-dynamodb';
import { Construct } from 'constructs';

interface DynamoGlobalTableProps {
  autoScaleWriteCapacity?: {
    minCapacity: number;
    maxCapacity: number;
  };
  replicationRegions?: string[];
}

export class DynamoGlobalTable extends Table {
  constructor(scope: Construct, id: string, private props: DynamoGlobalTableProps) {
    super(scope, id, {
      partitionKey: {
        name: 'id',
        type: AttributeType.STRING,
      },
      pointInTimeRecovery: true,
      stream: StreamViewType.NEW_AND_OLD_IMAGES,
      replicationRegions: props.replicationRegions ?? ['us-west-2'],
      billingMode: BillingMode.PROVISIONED,
      ...props,
      removalPolicy: RemovalPolicy.DESTROY,
    });

    this.autoScaleWriteCapacity({
      maxCapacity: props.autoScaleWriteCapacity?.maxCapacity ?? 100,
      minCapacity: props.autoScaleWriteCapacity?.minCapacity ?? 1,
    }).scaleOnUtilization({
      targetUtilizationPercent: 80,
    });
  }

  /**
   * Adds a global secondary index to this table and additionally sets up autoscaling for write capacity to 100-1. This
   * method is extending the default addGlobalSecondaryIndex method from the Table construct. Anytime you're adding a
   * new global secondary index to a DynamoDB table, the autoscaling for write capacity units will be set up automatically.
   * @param props GlobalSecondaryIndexProps - The properties for the global secondary index.
   */
  addGlobalSecondaryIndex(props: GlobalSecondaryIndexProps) {
    super.addGlobalSecondaryIndex(props);
    // Explicitly set up autoscaling for write capacity units for the global secondary indexes.
    this.autoScaleGlobalSecondaryIndexWriteCapacity(props.indexName, {
      maxCapacity: this.props.autoScaleWriteCapacity?.maxCapacity ?? 100,
      minCapacity: this.props.autoScaleWriteCapacity?.minCapacity ?? 1,
    }).scaleOnUtilization({
      targetUtilizationPercent: 80,
    });
  }
}

export class MyStack extends Stack {
  constructor(scope: Construct, id: string, props: StackProps = {}) {
    super(scope, id, props);

    const myTable = new DynamoGlobalTable(this, 'MyTable', {});
    myTable.addGlobalSecondaryIndex({
      indexName: 'myIndex',
      partitionKey: {
        name: 'myIndex',
        type: AttributeType.STRING,
      },
    });
  }
}

const app = new App();

new MyStack(app, 'testing-gsis-dev', {
  env: {
    account: '000011112222',
    region: 'us-east-1',
  },
});

app.synth();

Then update to add a second index:

import { App, RemovalPolicy, Stack, StackProps } from 'aws-cdk-lib';
import { AttributeType, BillingMode, GlobalSecondaryIndexProps, StreamViewType, Table } from 'aws-cdk-lib/aws-dynamodb';
import { Construct } from 'constructs';

interface DynamoGlobalTableProps {
  autoScaleWriteCapacity?: {
    minCapacity: number;
    maxCapacity: number;
  };
  replicationRegions?: string[];
}

export class DynamoGlobalTable extends Table {
  constructor(scope: Construct, id: string, private props: DynamoGlobalTableProps) {
    super(scope, id, {
      partitionKey: {
        name: 'id',
        type: AttributeType.STRING,
      },
      pointInTimeRecovery: true,
      stream: StreamViewType.NEW_AND_OLD_IMAGES,
      replicationRegions: props.replicationRegions ?? ['us-west-2'],
      billingMode: BillingMode.PROVISIONED,
      ...props,
      removalPolicy: RemovalPolicy.DESTROY,
    });

    this.autoScaleWriteCapacity({
      maxCapacity: props.autoScaleWriteCapacity?.maxCapacity ?? 100,
      minCapacity: props.autoScaleWriteCapacity?.minCapacity ?? 1,
    }).scaleOnUtilization({
      targetUtilizationPercent: 80,
    });
  }

  /**
   * Adds a global secondary index to this table and additionally sets up autoscaling for write capacity to 100-1. This
   * method is extending the default addGlobalSecondaryIndex method from the Table construct. Anytime you're adding a
   * new global secondary index to a DynamoDB table, the autoscaling for write capacity units will be set up automatically.
   * @param props GlobalSecondaryIndexProps - The properties for the global secondary index.
   */
  addGlobalSecondaryIndex(props: GlobalSecondaryIndexProps) {
    super.addGlobalSecondaryIndex(props);
    // Explicitly set up autoscaling for write capacity units for the global secondary indexes.
    this.autoScaleGlobalSecondaryIndexWriteCapacity(props.indexName, {
      maxCapacity: this.props.autoScaleWriteCapacity?.maxCapacity ?? 100,
      minCapacity: this.props.autoScaleWriteCapacity?.minCapacity ?? 1,
    }).scaleOnUtilization({
      targetUtilizationPercent: 80,
    });
  }
}

export class MyStack extends Stack {
  constructor(scope: Construct, id: string, props: StackProps = {}) {
    super(scope, id, props);

    const myTable = new DynamoGlobalTable(this, 'MyTable', {});
    myTable.addGlobalSecondaryIndex({
      indexName: 'myIndex',
      partitionKey: {
        name: 'myIndex',
        type: AttributeType.STRING,
      },
    });

    myTable.addGlobalSecondaryIndex({
      indexName: 'myIndex2',
      partitionKey: {
        name: 'myIndex2',
        type: AttributeType.STRING,
      },
    });
  }
}

const app = new App();

new MyStack(app, 'testing-gsis-dev', {
  env: {
    account: '000011112222',
    region: 'us-east-1',
  },
});

app.synth();

Possible Solution

🤷🏻

Additional Information/Context

No response

CDK CLI Version

2.93.0

Framework Version

2.93.0

Node.js Version

v18.17.0

OS

MacOS 13.4.1

Language

Typescript

Language Version

5.2.2

Other information

No response

@mbonig mbonig added bug This issue is a bug. needs-triage This issue or PR still needs to be triaged. labels Aug 28, 2023
@github-actions github-actions bot added the @aws-cdk/aws-autoscaling Related to Amazon EC2 Auto Scaling label Aug 28, 2023
@indrora indrora added @aws-cdk/aws-dynamodb Related to Amazon DynamoDB and removed needs-triage This issue or PR still needs to be triaged. labels Aug 28, 2023
@indrora
Copy link
Contributor

indrora commented Aug 28, 2023

Thank you for opening an issue.

This looks eerily similar to a previous issue that resolved to it being outside of CDK's control due to a problem with DynamoDB: #19083

can you confirm this is unique?

@indrora indrora added the response-requested Waiting on additional info and feedback. Will move to "closing-soon" in 7 days. label Aug 28, 2023
@mbonig
Copy link
Contributor Author

mbonig commented Aug 29, 2023

Yes, my apologies, I searched for issues before opening this and didn't see it. Closing this issue as a duplicate.

@mbonig mbonig closed this as completed Aug 29, 2023
@github-actions
Copy link

⚠️COMMENT VISIBILITY WARNING⚠️

Comments on closed issues are hard for our team to see.
If you need more assistance, please either tag a team member or open a new issue that references this one.
If you wish to keep having a conversation with other community members under this issue feel free to do so.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
@aws-cdk/aws-autoscaling Related to Amazon EC2 Auto Scaling @aws-cdk/aws-dynamodb Related to Amazon DynamoDB bug This issue is a bug. response-requested Waiting on additional info and feedback. Will move to "closing-soon" in 7 days.
Projects
None yet
Development

No branches or pull requests

2 participants