DynamoDB GSI変更デプロイ失敗の原因と解決策

DynamoDB GSI変更デプロイ失敗の原因と解決策

2026.05.13

DynamoDB の GSI 変更を含むデプロイが失敗してハマった話

こんにちは。クラスメソッドオペレーションズの山本です。

DynamoDB テーブルの GSI を変更したところ、本番環境へのデプロイが失敗してしまいました。意外とハマりやすいポイントだと思うので同じハマりを防ぐために経緯と対処法を共有します。

前提

  • AWS CDK v2(TypeScript)
  • Amazon DynamoDB
  • デプロイ: CloudFormation(CDK経由)

経緯

DynamoDBのあるテーブルの既存のGSI-Aを削除し
新規に別のGSI-Bを追加する必要がありました。
まとめると、変更点としては以下の2つの操作が必要でした。

  • GSI-Aの削除
  • GSI-Bの追加

その操作を同時に行い、本番環境にデプロイするとエラーが発生した流れです。
その原因と対処を共有します。

やりたかったこと

  • 既存の DynamoDB テーブルに対して GSI の変更(追加・削除)を行いたかった

何が起きたか

  • cdk deploy を実行したところ、CloudFormation スタックの更新が失敗
  • スタックがロールバックされ、変更が適用されなかった

実際のエラーメッセージ

Cannot perform more than one GSI creation or deletion in a single update

原因

そもそも、DynamoDB の GSI 変更には CloudFormation 上の以下の制約があります。

GSI追加と削除を同時に行うことはできない

If you do both in the same update (for example, by changing the index's logical ID), the update fails.

参考: AWS::DynamoDB::Table - CloudFormation

そのため、今回はGSI-AとBを同時に変更した上でデプロイをしたため失敗しました。

解決策

GSI の変更を分割デプロイする

複数の GSI 変更がある場合、1回のデプロイにつき1つの変更に分割する。

1回目のデプロイ: GSI-A・GSI-B を同時に保持する

const ordersTable = new cdk.aws_dynamodb.Table(construct, 'ordersTable', {
  tableName: 'orders',
  partitionKey: { name: 'id', type: cdk.aws_dynamodb.AttributeType.STRING },
  sortKey: { name: 'createdAtAndUserId', type: cdk.aws_dynamodb.AttributeType.STRING },
  pointInTimeRecovery: true,
  billingMode: cdk.aws_dynamodb.BillingMode.PAY_PER_REQUEST,
  removalPolicy: cdk.RemovalPolicy.RETAIN,
});

// GSI-A: status で絞り込むためのインデックス(削除したい)
const gsiAProps: cdk.aws_dynamodb.GlobalSecondaryIndexProps = {
  indexName: 'gsiA-statusCreatedAtIndex',
  partitionKey: { name: 'status', type: cdk.aws_dynamodb.AttributeType.STRING },
  sortKey: { name: 'createdAt', type: cdk.aws_dynamodb.AttributeType.STRING },
};
ordersTable.addGlobalSecondaryIndex(gsiAProps);

+ // GSI-B: userId で絞り込むためのインデックス(追加したい)
+ const gsiBProps: cdk.aws_dynamodb.GlobalSecondaryIndexProps = {
+   indexName: 'gsiB-userIdCreatedAtIndex',
+   partitionKey: { name: 'userId', type: cdk.aws_dynamodb.AttributeType.STRING },
+   sortKey: { name: 'createdAt', type: cdk.aws_dynamodb.AttributeType.STRING },
+ };
+ ordersTable.addGlobalSecondaryIndex(gsiBProps);

2回目のデプロイ: GSI-A を削除

1回目のデプロイが完了してから、GSI-A の削除を適用する。

const ordersTable = new cdk.aws_dynamodb.Table(construct, 'ordersTable', {
  tableName: 'orders',
  partitionKey: { name: 'id', type: cdk.aws_dynamodb.AttributeType.STRING },
  sortKey: { name: 'createdAtAndUserId', type: cdk.aws_dynamodb.AttributeType.STRING },
  pointInTimeRecovery: true,
  billingMode: cdk.aws_dynamodb.BillingMode.PAY_PER_REQUEST,
  removalPolicy: cdk.RemovalPolicy.RETAIN,
});

- // GSI-A: status で絞り込むためのインデックス(削除したい)
- const gsiAProps: cdk.aws_dynamodb.GlobalSecondaryIndexProps = {
-   indexName: 'gsiA-statusCreatedAtIndex',
-   partitionKey: { name: 'status', type: cdk.aws_dynamodb.AttributeType.STRING },
-   sortKey: { name: 'createdAt', type: cdk.aws_dynamodb.AttributeType.STRING },
- };
- ordersTable.addGlobalSecondaryIndex(gsiAProps);

// GSI-B: userId で絞り込むためのインデックス(追加したい)
const gsiBProps: cdk.aws_dynamodb.GlobalSecondaryIndexProps = {
  indexName: 'gsiB-userIdCreatedAtIndex',
  partitionKey: { name: 'userId', type: cdk.aws_dynamodb.AttributeType.STRING },
  sortKey: { name: 'createdAt', type: cdk.aws_dynamodb.AttributeType.STRING },
};
ordersTable.addGlobalSecondaryIndex(gsiBProps);

ハマりポイント

  • エラーメッセージだけでは原因が分かりにくい
  • ロールバックが走ると、スタックが UPDATE_ROLLBACK_COMPLETE になるまで再デプロイできず待ち時間が発生する

まとめ

DynamoDB の GSI 変更は CloudFormation の制約に注意が必要です。複数の GSI を同時に変更しようとすると失敗するので、1変更ずつ分割デプロイするのが安全です。本番で焦らないために、事前に CloudFormation の変更セット(Change Set)で確認する習慣をつけておくのがおすすめです。

クラスメソッドオペレーションズ株式会社について

クラスメソッドグループのオペレーション企業です。

運用・保守開発・サポート・情シス・バックオフィスの専門チームが、IT・AIをフル活用した「しくみ」を通じて、お客様の業務代行から課題解決や高付加価値サービスまでを提供するエキスパート集団です。

当社は様々な職種でメンバーを募集しています。

「オペレーション・エクセレンス」と「らしく働く、らしく生きる」を共に実現するカルチャー・しくみ・働き方にご興味がある方は、クラスメソッドオペレーションズ株式会社 コーポレートサイト をぜひご覧ください。※2026年1月 アノテーション㈱から社名変更しました

この記事をシェアする

関連記事