amplify-cli icon indicating copy to clipboard operation
amplify-cli copied to clipboard

dynamodb mocking reports cannot create preexisting table if there are more than 100 tables.

Open pacteraou opened this issue 2 months ago • 1 comments

How did you install the Amplify CLI?

npm

If applicable, what version of Node.js are you using?

22

Amplify CLI Version

13, 14 and others

What operating system are you using?

Mac OS

Did you make any manual changes to the cloud resources managed by Amplify? Please describe the changes made.

nothing

Describe the bug

  1. define more than 100 tables in schema graphql file like
        type Table000 @model {
        id: String
        }
        
        type Table001 @model {
        id: String
        }
...
        type Table101 @model {
        id: String
        }
 
  1. start mock server with command amplify mock api
  2. shut down mock server after server up with tables created.
  3. start mock server again with command amplify mock api. it will fails with error
Creating new table XXXTable
Failed to start API Mocking. Running cleanup tasks.
Reason: Cannot create preexisting table

Expected behavior

it should not create new tables because it's already created.

Reproduction steps

  1. define more than 100tables in schema graphql file.
  2. start mock server with command amplify mock api
  3. shut down mock server after tables created.
  4. start mock server again. it will fails with error Reason: Cannot create preexisting table

Project Identifier

No response

Log output

# Put your logs below this line

Creating new table XXTable
Failed to start API Mocking. Running cleanup tasks.
Reason: Cannot create preexisting table

Additional information

The cause is that DynamoDB Java Client's listTables method is limited to return 100 tables by default, the Local Dynamodb doesnt implement to list all tables that cause it trying to create exiting tables at mock server starting.

Before submitting, please confirm:

  • [x] I have done my best to include a minimal, self-contained set of instructions for consistently reproducing the issue.
  • [x] I have removed any sensitive information from my code snippets and submission.

pacteraou avatar Oct 02 '25 01:10 pacteraou

Current vs Expected Behavior

Current Behavior:
┌─────────────────────────────────────────────────────────────┐
│ First Start: 101 Tables Defined in Schema                  │
└─────────────────────────────────────────────────────────────┘
                          |
                          v
              ┌───────────────────────┐
              │  listTables() call    │
              └───────────────────────┘
                          |
                          v
              ┌───────────────────────┐
              │ Returns: 0 tables     │
              │ (empty database)      │
              └───────────────────────┘
                          |
                          v
              ┌───────────────────────┐
              │ Create all 101 tables │
              └───────────────────────┘
                          |
                          v
                    ✓ SUCCESS ✓


┌─────────────────────────────────────────────────────────────┐
│ Second Start: 101 Tables Already Exist                     │
└─────────────────────────────────────────────────────────────┘
                          |
                          v
              ┌───────────────────────┐
              │  listTables() call    │
              └───────────────────────┘
                          |
                          v
              ┌───────────────────────┐
              │ Returns: ONLY 100     │  ← BUG: Missing pagination
              │ tables (limit reached)│
              └───────────────────────┘
                          |
                          v
              ┌───────────────────────┐
              │ Table #101 not found  │
              │ in returned list      │
              └───────────────────────┘
                          |
                          v
              ┌───────────────────────┐
              │ Attempt to create     │
              │ Table #101            │
              └───────────────────────┘
                          |
                          v
              ┌───────────────────────┐
              │ DynamoDB Local Error: │
              │ "Cannot create        │
              │  preexisting table"   │
              └───────────────────────┘
                          |
                          v
                    ✗ FAILURE ✗


Expected Behavior:
┌─────────────────────────────────────────────────────────────┐
│ Second Start: 101 Tables Already Exist                     │
└─────────────────────────────────────────────────────────────┘
                          |
                          v
              ┌───────────────────────┐
              │  listTables() call    │
              │  with pagination      │
              └───────────────────────┘
                          |
                          v
              ┌───────────────────────┐
              │ Page 1: Tables 1-100  │
              └───────────────────────┘
                          |
                          v
              ┌───────────────────────┐
              │ LastEvaluatedTableName│
              │ exists? YES           │
              └───────────────────────┘
                          |
                          v
              ┌───────────────────────┐
              │ Page 2: Table 101     │
              └───────────────────────┘
                          |
                          v
              ┌───────────────────────┐
              │ All 101 tables found  │
              │ in complete list      │
              └───────────────────────┘
                          |
                          v
              ┌───────────────────────┐
              │ Skip table creation   │
              │ (all exist)           │
              └───────────────────────┘
                          |
                          v
                    ✓ SUCCESS ✓

Root Cause Analysis

The bug appears to be in packages/amplify-util-mock/src/utils/dynamo-db/index.ts:

export async function createAndUpdateTable(dynamoDbClient: DynamoDB, config: MockDynamoDBConfig): Promise<MockDynamoDBConfig> {
  const tables = config.tables.map((table) => table.Properties);
  const existingTables = await dynamoDbClient.listTables().promise();  // ← BUG HERE
  const existingTablesWithDetails = await describeTables(dynamoDbClient, existingTables.TableNames);
  const tablesToCreate = tables.filter((t) => {
    const tableName = t.TableName;
    return !existingTables.TableNames.includes(tableName);  // ← Incomplete list
  });
  // ...
}

The Problem:

The AWS SDK's listTables() method returns a maximum of 100 table names per call. According to AWS DynamoDB API documentation, the response includes: • TableNames: Array of table names (max 100) • LastEvaluatedTableName: Pagination token if more tables exist

The code doesn't handle pagination, so: • Tables 1-100: Correctly detected as existing • Tables 101+: Not in the list, incorrectly treated as non-existent • Attempt to create table 101 fails because it already exists

Why it works on first run:

On the first run, no tables exist yet, so listTables() returns an empty array. All 101 tables are created successfully. The error only appears on subsequent restarts.

Document Evidence

AWS SDK DynamoDB.listTables() documentation - Documents the 100-item limit and pagination • DynamoDB ListTables API Reference - Explains LastEvaluatedTableName pagination • Source code: index.ts - The problematic code

Possible Fixes/Solutions

The fix requires implementing pagination in the createAndUpdateTable function. The solution would involve:

  1. Replace single listTables() call with paginated loop that continues until LastEvaluatedTableName is undefined
  2. Accumulate all table names across pagination calls
  3. Use complete list for existence checking

The implementation pattern should follow AWS SDK pagination best practices, similar to how it's done elsewhere in the codebase (e.g., in DynamoDBService.ts which already handles pagination correctly).

Workaround for users:

Until fixed, users can manually delete the local DynamoDB data directory before each restart, though this loses all mock data.

Making this a bug. I'll bring this up to the team for review.

pahud avatar Oct 02 '25 17:10 pahud