Skip to content

fix(glue-alpha): race condition when creating mutiple indices#38016

Open
Abogical wants to merge 25 commits into
mainfrom
glue-fix-partition-index
Open

fix(glue-alpha): race condition when creating mutiple indices#38016
Abogical wants to merge 25 commits into
mainfrom
glue-fix-partition-index

Conversation

@Abogical

@Abogical Abogical commented May 27, 2026

Copy link
Copy Markdown
Member

Issue # (if applicable)

Closes #24813.

Reason for this change

Creating multiple partition indexes on a Glue table fails with:

│ "Index index1 is in CREATING state. Only 1 index can be created or deleted simultaneously per table."

This happens because:

  1. The dependency direction between partition index custom resources was backwards — CloudFormation created
    them in reverse order.
  2. AwsCustomResource reports success immediately after the createPartitionIndex API returns HTTP 200, while
    the index is still in CREATING state. CloudFormation then starts the next index before the previous one
    finishes.

Description of changes

The main change was the creation of a new custom resource handler to replace the existing one, based on AwsCustomResource. This new handler is asynchronous; it has an isComplete method that polls the service to check whether the requested operation is complete. This ensures that the creation requests are serialized. There are some edge cases worth mentioning, though:

  • When the onEvent handler receives a "Create" event, it checks whether a partition index with those exact keys already exists. If it does, the handler does nothing and returns the same physical ID, to indicate that the operation was successful.
  • The Glue API does not have an update operation. If the user changes an explicitly named index, the handler has to perform the update itself, by deleting the existing index and creating a new one. The order is exactly this: delete-then-create, which goes against the usual pattern create-then-delete. This is because tables can have at most 3 partition indexes. So a create-then-delete would risk trying to create a 4th one and would fail.
  • Another departure from customary custom resource practices is that the isComplete has side effects. The update flow works like this: the onEvent, upon deciding that an update is necessary, sends a delete message to service. and returns. Then, on each subsequent poll, isComplete checks whether the index has been deleted. If the index is not there anymore, it creates the new index (see the sequence diagram below). It has to be done this way for the same reason that motivates this PR: the API is eventually consistent, so it may take an indefinite amount of time for the delete to be committed.
  • A special case for the update flow is when what changed in the custom resource is the table name or database name. This can happen if the table is renamed or the index starts pointing to another table, for example. Either way, the onCreate handler won't delete anything. If the index needs to be deleted (for example, the table was replaced), then CloudFormation itself will delete the table and, with it, the indexes.

In addition, this PR fixes the dependency direction so each new partition index custom resource depends on the previous one (was backwards before).

Describe any new or updated permissions being added

The partition index handler Lambda roles require:

  • glue:CreatePartitionIndex — to create indexes
  • glue:DeletePartitionIndex — to clean up on stack deletion
  • glue:GetPartitionIndexes — to poll index status in the isComplete handler
  • glue:GetTable — required by the GetPartitionIndexes API
  • glue:UpdateTable — required by the CreatePartitionIndex API

These are scoped to the specific table, database, and catalog ARNs for each table that uses partition indexes.

Custom resource sequence diagram

  sequenceDiagram
      participant CFN as CloudFormation
      participant OE as onEvent
      participant IC as isComplete (loop)
      participant Glue
  
      CFN->>OE: Update (keys changed)
      OE->>Glue: DeletePartitionIndex (old)
      OE-->>CFN: ack (async delete kicked off)
      loop every 10s until ACTIVE / timeout
          CFN->>IC: poll
          IC->>Glue: GetPartitionIndexes
          alt old index still present (ACTIVE-old or DELETING)
              IC-->>CFN: IsComplete=false
          else index gone
              IC->>Glue: CreatePartitionIndex (new keys)
              IC-->>CFN: IsComplete=false
          else new index present & CREATING
              IC-->>CFN: IsComplete=false
          else new index ACTIVE
              IC-->>CFN: IsComplete=true
          end
      end
Loading

Checklist


By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license

BREAKING CHANGE: Partition index custom resources will be replaced on stack update due to implementation change from AwsCustomResource to Provider-based CustomResource. The CloudFormation resource type changes from Custom::AWS to Custom::GluePartitionIndex. Partition indexes are not stateful so no data is lost, but users will see resource replacement during the first update after upgrading.

BREAKING CHANGE: Indexes with tokenized key names must have an explicitly provided index name now.

@github-actions github-actions Bot added bug This issue is a bug. effort/medium Medium work item – several days of effort p1 labels May 27, 2026
@github-actions github-actions Bot added the distinguished-contributor [Pilot] contributed 50+ PRs to the CDK label May 27, 2026
@mergify mergify Bot added the contribution/core This is a PR that came from AWS. label May 27, 2026
@mergify
mergify Bot temporarily deployed to automation May 27, 2026 16:16 Inactive
@mergify
mergify Bot temporarily deployed to automation May 27, 2026 16:17 Inactive
@Abogical
Abogical force-pushed the glue-fix-partition-index branch from 310ef5b to f1cd643 Compare May 27, 2026 16:18
@aws-cdk-automation aws-cdk-automation added the pr/needs-further-review PR requires additional review from our team specialists due to the scope or complexity of changes. label May 27, 2026
@Abogical
Abogical force-pushed the glue-fix-partition-index branch from f1cd643 to 1c5bb14 Compare May 27, 2026 16:38
@Abogical Abogical added the pr/needs-integration-tests-deployment Requires the PR to deploy the integration test snapshots. label May 27, 2026
@Abogical
Abogical had a problem deploying to deployment-integ-test May 27, 2026 16:41 — with GitHub Actions Failure
@Abogical Abogical changed the title fix(glue-alpha): fix partition index race condition when creating mutiple indices fix(glue-alpha): partition index race condition when creating mutiple indices May 27, 2026
@Abogical Abogical changed the title fix(glue-alpha): partition index race condition when creating mutiple indices fix(glue-alpha): race condition when creating mutiple indices May 28, 2026
@Abogical
Abogical force-pushed the glue-fix-partition-index branch from 1c5bb14 to 16bcea7 Compare May 28, 2026 09:30
@Abogical
Abogical had a problem deploying to deployment-integ-test May 28, 2026 09:30 — with GitHub Actions Failure
GitHub Actions and others added 4 commits June 3, 2026 15:50
…tiple indexes

Replace AwsCustomResource with a CDK Provider that uses an isComplete
handler to poll GetPartitionIndexes until the index reaches ACTIVE state
before reporting success to CloudFormation.

Previously, the custom resource reported success immediately after the
createPartitionIndex API returned, while the index was still in CREATING
state. This caused subsequent index creations to fail with:
"Only 1 index can be created or deleted simultaneously per table."

Also fixes the dependency direction so each new partition index depends
on the previous one (was backwards before).

Closes #24813
Comment thread packages/@aws-cdk/aws-glue-alpha/lib/partition-index-handler/index.ts Outdated
Comment thread packages/@aws-cdk/aws-glue-alpha/lib/partition-index-handler/index.ts Outdated
Comment thread packages/@aws-cdk/aws-glue-alpha/lib/table-base.ts Outdated
Comment on lines +61 to +82
if (event.RequestType === 'Create') {
try {
await createPartitionIndex(DatabaseName, TableName, IndexName, Keys);
} catch (e: any) {
// The index may already exist if it was created out-of-band or by a previous
// resource being replaced (CloudFormation creates the replacement before deleting
// the old resource). Treat this as success and let isComplete verify its state.
if (e.name === 'AlreadyExistsException') {
const existing = await findPartitionIndex(DatabaseName, TableName, IndexName);
const existingKeys = existing?.Keys?.map((k: any) => k.Name);
if (!existing || JSON.stringify(existingKeys) !== JSON.stringify(Keys)) {
throw new PartitionIndexError(
`Partition index ${IndexName} already exists on ${DatabaseName}.${TableName} with different or reordered keys ` +
`(existing: ${JSON.stringify(existingKeys)}, requested: ${JSON.stringify(Keys)}). Delete the existing index first.`,
);
}
console.log(`Partition index ${IndexName} already exists with matching keys - reusing`);
} else {
throw e;
}
}
return { PhysicalResourceId: IndexName };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before this goes in, can we confirm the upgrade path was actually tested? Deploy an app on the current released aws-glue-alpha with a couple of partition indexes, then deploy this branch on top, and confirm the indexes survive and the stack updates cleanly.

What I specifically want covered is the create-before-delete overlap: the resource type changes from Custom::AWS to Custom::GluePartitionIndex, so CloudFormation creates the new resource (which adopts the existing index on AlreadyExists here) and then deletes the old Custom::AWS resource. I want to be sure that trailing delete of the old resource does not remove the index the new one just adopted. Every existing user hits this on their first upgrade, so the blast radius is large if it is wrong.

I do not think we need this as a permanent integ test, but I do want the manual test done and a short note of the result in the PR.

onEventHandler: this.onEventHandler,
isCompleteHandler: this.isCompleteHandler,
queryInterval: Duration.seconds(10),
totalTimeout: Duration.hours(2),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: why 2 hours here? Partition index creation usually finishes in minutes, and a 2 hour totalTimeout means a stuck index keeps a deploy polling for a long time before it fails, which also surprises anyone with a shorter CI/CD step timeout. If there is a real backfill case that needs this long, a one line comment saying so would help. Otherwise something like 30-60 minutes seems closer to reality.

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

Labels

bug This issue is a bug. contribution/core This is a PR that came from AWS. distinguished-contributor [Pilot] contributed 50+ PRs to the CDK effort/medium Medium work item – several days of effort p1 pr/needs-further-review PR requires additional review from our team specialists due to the scope or complexity of changes.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

(glue-alpha): cannot create 2 partitionIndexes simultaneously

4 participants