tl;dr
- terraform-provider-aws v6.32.0 以降でDynamoDBのテーブルにkey_schemaブロックを使ってGSIを追加すると、関係ないGSIが作り直しになってしまう
- 記事執筆時点の最新版であるterraform-provider-aws v6.38.0で未解決
前提
TerraformでDynamoDBのテーブルにGSIを追加するとき、terraform-provider-aws v6.31.0までは↓のように書いていたと思います*1。
resource "aws_dynamodb_table" "basic-dynamodb-table" { // 中略 global_secondary_index { name = "GameTitleIndex" hash_key = "GameTitle" range_key = "TopScore" write_capacity = 10 read_capacity = 10 projection_type = "INCLUDE" non_key_attributes = ["UserId"] } }
terraform-provider-aws v6.32.0 からは global_secondary_index ブロック中の hash_key range_key 属性がdeprecatedになり、代わりに key_schema ブロックを使って書くように案内されています*2。先述したGSIであれば以下のように書くことになるでしょう。
resource "aws_dynamodb_table" "basic-dynamodb-table" { // 中略 global_secondary_index { name = "GameTitleIndex" key_schema { name = "GameTitle" key_type = "HASH" } key_schema { name = "TopScore" key_type = "RANGE" } write_capacity = 10 read_capacity = 10 projection_type = "INCLUDE" non_key_attributes = ["UserId"] } }
起こったこと
以下のようなテーブル定義があったとして:
resource "aws_dynamodb_table" "test" { name = "gsi-test" billing_mode = "PAY_PER_REQUEST" attribute { name = "pk" type = "S" } attribute { name = "field1" type = "S" } hash_key = "pk" global_secondary_index { name = "field1" key_schema { attribute_name = "field1" key_type = "HASH" } projection_type = "ALL" } }
次のようにGSI field2 を追加する変更を加えました。
diff --git a/dynamodb-test.tf b/dynamodb-test.tf index 39d22c1..cec86ac 100644 --- a/dynamodb-test.tf +++ b/dynamodb-test.tf @@ -10,6 +10,10 @@ resource "aws_dynamodb_table" "test" { name = "field1" type = "S" } + attribute { + name = "field2" + type = "S" + } hash_key = "pk" @@ -23,4 +27,15 @@ resource "aws_dynamodb_table" "test" { projection_type = "ALL" } + + global_secondary_index { + name = "field2" + + key_schema { + attribute_name = "field2" + key_type = "HASH" + } + + projection_type = "ALL" + } }
この状態で terraform plan を実行すると、既存のGSI field1 が削除されてから作成されるような差分が出ます。そして、terraform apply を実行すると、plan結果通りに既存のGSIが作り直しになりました。アプリケーションでGSI field1 をに対するクエリを発行している場合はエラーが発生します。
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
~ update in-place
Terraform will perform the following actions:
# aws_dynamodb_table.test will be updated in-place
~ resource "aws_dynamodb_table" "test" {
id = "gsi-test"
name = "gsi-test"
tags = {}
# (13 unchanged attributes hidden)
+ attribute {
+ name = "field2"
+ type = "S"
}
- global_secondary_index {
- hash_key = "field1" -> null
- name = "field1" -> null
- non_key_attributes = [] -> null
- projection_type = "ALL" -> null
- read_capacity = 0 -> null
- write_capacity = 0 -> null
# (1 unchanged attribute hidden)
- key_schema {
- attribute_name = "field1" -> null
- key_type = "HASH" -> null
}
}
+ global_secondary_index {
+ hash_key = (known after apply)
+ name = "field1"
+ non_key_attributes = []
+ projection_type = "ALL"
+ read_capacity = (known after apply)
+ write_capacity = (known after apply)
# (1 unchanged attribute hidden)
+ key_schema {
+ attribute_name = "field1"
+ key_type = "HASH"
}
+ warm_throughput (known after apply)
}
+ global_secondary_index {
+ hash_key = (known after apply)
+ name = "field2"
+ non_key_attributes = []
+ projection_type = "ALL"
+ read_capacity = (known after apply)
+ write_capacity = (known after apply)
# (1 unchanged attribute hidden)
+ key_schema {
+ attribute_name = "field2"
+ key_type = "HASH"
}
+ warm_throughput (known after apply)
}
# (4 unchanged blocks hidden)
}
Plan: 0 to add, 1 to change, 0 to destroy.
考察・対策
おそらく以下のissueの事象が発生しているのではないか、と考えられます。
当面は手でGSIを追加したあとにtfstateを同期させる、(deprecatedだけど) hash_key range_key を使ってGSIを管理する、などで回避する必要がありそうです。マルチキーのGSIを追加したい場合は必然的に前者でしのぐしかない?
おわりに
Terraformの差分、たまに data resourceなどを経由することで実際には実害のない (applyしても何も起こらない) 差分が出ることもあるので油断しがちだけど、差分通りに作り直しが発生してしまう場合もあるのだ、と思い知りました。気をつけてください。本番環境に対して反映する前に気づけたので助かった……。