aws / aws-sdk-js-v3

Modularized AWS SDK for JavaScript.
Apache License 2.0
2.96k stars 557 forks source link

DynamoDBClient waitUntilTableExists throws exception: ResourceNotFoundException #6105

Closed Ra100NZ closed 1 month ago

Ra100NZ commented 1 month ago

Checkboxes for prior research

Describe the bug

    const results = await waitUntilTableExists({client: this.client, maxWaitTime: 30, minDelay: 1, maxDelay: 3 }, {
        TableName: TableName
    });

after 6-9 seconds results in exception:

{
    "name": "ResourceNotFoundException",
    "$fault": "client",
    "$metadata": {
        "httpStatusCode": 400,
        "requestId": "BU5JS6RAMTBAGO2PFKM2MG5K6VVV4KQNSO5AEMVJF66Q9ASUAAJG",
        "attempts": 1,
        "totalRetryDelay": 0
    },
    "__type": "com.amazonaws.dynamodb.v20120810#ResourceNotFoundException"
}

TableName is correct, the createTable command is executed before the waitUntilTableExists.

SDK version number

@aws-sdk/client-dynamodb

Which JavaScript Runtime is this issue in?

Node.js

Details of the browser/Node.js/ReactNative version

v16.16.0

Reproduction Steps

    const results = await waitUntilTableExists({client: this.client, maxWaitTime: 30, minDelay: 1, maxDelay: 3 }, {
        TableName: TableName
    });

Observed Behavior

Waiter does not seem to be waiting

Expected Behavior

Waiter should wait until the table is created (or max 30 seconds)

Possible Solution

No response

Additional Information/Context

No response

RanVaknin commented 1 month ago

Hi @Ra100NZ ,

Can you share your entire code snippet?

It works fine for me:

import { DynamoDBClient, CreateTableCommand, waitUntilTableExists } from '@aws-sdk/client-dynamodb';

const client = new DynamoDBClient({ region: "us-east-1" });

try {
  await client.send(new CreateTableCommand({
    TableName: "6105",
    AttributeDefinitions: [
      { AttributeName: "id", AttributeType: "S" }
    ],
    KeySchema: [
      { AttributeName: "id", KeyType: "HASH" }
    ],
    ProvisionedThroughput: {
      ReadCapacityUnits: 5,
      WriteCapacityUnits: 5
    }
  }));

  const results = await waitUntilTableExists({
    client: client,
    maxWaitTime: 30,
    minDelay: 1,
    maxDelay: 3
  }, {
    TableName: "6105"
  });

  console.log(results);
} catch (error) {
  console.log(error);
}

/*
{
  state: 'SUCCESS',
  reason: {
    '$metadata': {
      httpStatusCode: 200,
      requestId: 'REDACTED',
      extendedRequestId: undefined,
      cfId: undefined,
      attempts: 1,
      totalRetryDelay: 0
    },
    Table: {
      AttributeDefinitions: [Array],
      CreationDateTime: 2024-05-16T18:58:36.265Z,
      DeletionProtectionEnabled: false,
      ItemCount: 0,
      KeySchema: [Array],
      ProvisionedThroughput: [Object],
      TableArn: 'REDACTED/6105',
      TableId: 'REDACTED',
      TableName: '6105',
      TableSizeBytes: 0,
      TableStatus: 'ACTIVE'
    }
  }
}
*/

Thanks, Ran~

Ra100NZ commented 1 month ago

client is set within the constructor -> this.client = new DynamoDBClient();

public async TableCreate(TableName: string, HashKeyField: string, Fields: string[]): Promise<void> {
        Logger.info(`CustomTablesRepository.TableCreate("${TableName}", "${HashKeyField}", [${Fields}])`);
        // Create table
        const command = new CreateTableCommand({
            TableName: TableName,
            AttributeDefinitions: [ { AttributeName: HashKeyField, AttributeType: 'S' } ],
            KeySchema: [ { AttributeName: HashKeyField, KeyType: 'HASH' } ],
            BillingMode: 'PROVISIONED',
            ProvisionedThroughput: {
                ReadCapacityUnits: (process.env.aws_db_read_capacity) ? Number(process.env.aws_db_read_capacity) : 5,
                WriteCapacityUnits: (process.env.aws_db_write_capacity) ? Number(process.env.aws_db_write_capacity) : 5
            }
        });
        try {
            const res = await this.client.send(command);
            Logger.info(JSON.stringify(res));
            // Wait for table being created
            const results = await waitUntilTableExists({client: this.client, maxWaitTime: 30, minDelay: 1, maxDelay: 3 }, {
                TableName: TableName
            });
            if (results.state === 'SUCCESS') {
                // Put Dummy record
                let dymmyRecord = {} as Record<string, string>;
                dymmyRecord[HashKeyField] = this.dummyRecordKey;
                Fields.forEach((field) => {
                    dymmyRecord[field] = field;
                });
                const input = {
                    TableName: TableName,
                    Item: dymmyRecord
                };
                const docClient = DynamoDBDocumentClient.from(this.client);
                const command = new PutCommand(input);
                await docClient.send(command);
            } else {
                Logger.error(`${results.state} ${results.reason}`);
            }
        } catch (e) {
            Logger.error(JSON.stringify(e));
        }
    }
RanVaknin commented 1 month ago

Hi @Ra100NZ ,

Your code looks fine, I'm not sure why you are seeing it. I even expanded my example to mimic your code:

import { DynamoDBClient, CreateTableCommand, waitUntilTableExists } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, PutCommand } from "@aws-sdk/lib-dynamodb";

const client = new DynamoDBClient({ region: "us-east-1" });
const TableName = "6105-b"; 
const HashKeyField = "id"; 
const dummyRecordKey = "exampleKey"; 
const Fields = ["additionalField1", "additionalField2"];

async function main() {
    try {
        await client.send(new CreateTableCommand({
            TableName,
            AttributeDefinitions: [
                { AttributeName: HashKeyField, AttributeType: "S" }
            ],
            KeySchema: [
                { AttributeName: HashKeyField, KeyType: "HASH" }
            ],
            ProvisionedThroughput: {
                ReadCapacityUnits: 5,
                WriteCapacityUnits: 5
            }
        }));

        const results = await waitUntilTableExists({
            client,
            maxWaitTime: 30,
            minDelay: 1,
            maxDelay: 3
        }, {
            TableName
        });

        if (results.state === 'SUCCESS') {
            let dummyRecord = {};
            dummyRecord[HashKeyField] = dummyRecordKey;
            Fields.forEach(field => {
                dummyRecord[field] = field; 
            });

            const docClient = DynamoDBDocumentClient.from(client);
            const putCommand = new PutCommand({
                TableName,
                Item: dummyRecord
            });
            await docClient.send(putCommand);
            console.log("Record inserted successfully.");
        }
    } catch (error) {
        console.error("An error occurred:", error);
    }
}

main();

// Record inserted successfully.

The waiter code should be synchronous and blocking. It seems like your doc client API call is being fired before the waiter is finished.

Since you have logging in different parts of your code, can you tell me what is the order of logs that you are seeing?

  1. Is the table creation fired successfully? ie this line fires Logger.info(JSON.stringify(res));
  2. is the ResourceNotFoundException thrown from you the putItem() call?

My other guess is that because you have logging enabled (perhaps on your client code) you are seeing the underlying error thrown from the polling of the waiter. The CreateTable call might be firing, and the waiter polls right away and leads to an underlying ResourceNotFoundException, which is expected. The idea behind the waiter is that it polls over and over until the resource is not only created, but ready. You might be able to circumvent this by setting a longer initial delay like minDelay: 5

Thanks, Ran~

Ra100NZ commented 1 month ago

Hi @RanVaknin,

Yes, the table creation fired successfully. No, the ResourceNotFoundException is not coming from putItem(). It is coming from waitUntilTableExists.

I changed the minDelay and maxDelay based on your suggestion:

const results = await waitUntilTableExists({client: this.client, maxWaitTime: 30, minDelay: 5, maxDelay: 8 }, {
                TableName: TableName
            });

and it works now. But I am not sure why :)

Btw, we have as well the delete table action, and waitUntilTableNotExists works minDelay:1

Anyway, thanks for your suggestion and help.

github-actions[bot] commented 3 weeks ago

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs and link to relevant comments in this thread.