Last active
January 10, 2025 09:21
-
-
Save bugcloud/d806ded557d1558b0cec637aa08832b8 to your computer and use it in GitHub Desktop.
DynamoDB copy tool
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
DynamoDBClient, | |
DescribeTableCommand, | |
CreateTableCommand, | |
ScanCommand, | |
PutItemCommand, | |
ListTablesCommand, | |
waitUntilTableExists, | |
ScanCommandOutput, | |
UpdateTableCommand, | |
} from "@aws-sdk/client-dynamodb"; | |
import { WaiterConfiguration } from "@smithy/util-waiter"; | |
// コピー元のAWSアカウント | |
const sourceClient = new DynamoDBClient({ | |
profile: process.env.SRC_AWS_PROFILE, | |
region: "ap-northeast-1", | |
}); | |
// コピー先のAWSアカウント | |
const destinationClient = new DynamoDBClient({ | |
profile: process.env.DEST_AWS_PROFILE, | |
region: "ap-northeast-1", | |
}); | |
async function copyTable( | |
sourceTableName: string, | |
destinationTableName: string | |
) { | |
// テーブルのスキーマを取得 | |
const sourceTable = await sourceClient.send( | |
new DescribeTableCommand({ TableName: sourceTableName }) | |
); | |
const tableSchema = sourceTable.Table; | |
if (!tableSchema) return; | |
// 新しいテーブルを作成 | |
await destinationClient.send( | |
new CreateTableCommand({ | |
TableName: destinationTableName, | |
KeySchema: tableSchema.KeySchema, | |
AttributeDefinitions: | |
tableSchema.KeySchema?.length === | |
tableSchema.AttributeDefinitions?.length | |
? tableSchema.AttributeDefinitions | |
: [{ AttributeName: "id", AttributeType: "S" }], | |
BillingMode: "PAY_PER_REQUEST", | |
OnDemandThroughput: { | |
MaxReadRequestUnits: 1000, | |
MaxWriteRequestUnits: 1000, | |
}, | |
}) | |
); | |
// テーブルがアクティブになるまで待機 | |
const waiterConfig: WaiterConfiguration<DynamoDBClient> = { | |
client: destinationClient, | |
maxWaitTime: 120, | |
}; | |
await waitUntilTableExists(waiterConfig, { TableName: destinationTableName }); | |
// データをコピー | |
let lastEvaluatedKey: Record<string, any> | undefined = undefined; | |
do { | |
const response: ScanCommandOutput = await sourceClient.send( | |
new ScanCommand({ | |
TableName: sourceTableName, | |
ExclusiveStartKey: lastEvaluatedKey, | |
}) | |
); | |
const items = response.Items || []; | |
lastEvaluatedKey = response.LastEvaluatedKey; | |
for (const item of items) { | |
await destinationClient.send( | |
new PutItemCommand({ | |
TableName: destinationTableName, | |
Item: item, | |
}) | |
); | |
} | |
} while (lastEvaluatedKey); | |
} | |
async function modifyTable( | |
sourceTableName: string, | |
destinationTableName: string | |
) { | |
// テーブルのスキーマを取得 | |
const sourceTable = await sourceClient.send( | |
new DescribeTableCommand({ TableName: sourceTableName }) | |
); | |
const tableSchema = sourceTable.Table; | |
if (!tableSchema) return; | |
await destinationClient.send( | |
new UpdateTableCommand({ | |
TableName: destinationTableName, | |
BillingMode: tableSchema.BillingModeSummary?.BillingMode || "PROVISIONED", | |
OnDemandThroughput: | |
tableSchema.OnDemandThroughput && | |
tableSchema.OnDemandThroughput.MaxWriteRequestUnits && | |
tableSchema.OnDemandThroughput.MaxWriteRequestUnits > 0 | |
? { | |
MaxReadRequestUnits: | |
tableSchema.OnDemandThroughput.MaxReadRequestUnits, | |
MaxWriteRequestUnits: | |
tableSchema.OnDemandThroughput.MaxWriteRequestUnits, | |
} | |
: undefined, | |
ProvisionedThroughput: | |
tableSchema.ProvisionedThroughput && | |
tableSchema.ProvisionedThroughput.WriteCapacityUnits && | |
tableSchema.ProvisionedThroughput.WriteCapacityUnits > 0 | |
? { | |
ReadCapacityUnits: | |
tableSchema.ProvisionedThroughput.ReadCapacityUnits, | |
WriteCapacityUnits: | |
tableSchema.ProvisionedThroughput.WriteCapacityUnits, | |
} | |
: undefined, | |
}) | |
); | |
} | |
async function verifyTableData( | |
sourceTableName: string, | |
destinationTableName: string | |
): Promise<boolean> { | |
const sourceCount = await getTableItemCount(sourceClient, sourceTableName); | |
const destinationCount = await getTableItemCount( | |
destinationClient, | |
destinationTableName | |
); | |
console.log(`Source table (${sourceTableName}) item count: ${sourceCount}`); | |
console.log( | |
`Destination table (${destinationTableName}) item count: ${destinationCount}` | |
); | |
return sourceCount === destinationCount; | |
} | |
async function getTableItemCount( | |
client: DynamoDBClient, | |
tableName: string | |
): Promise<number> { | |
let itemCount = 0; | |
let lastEvaluatedKey: Record<string, any> | undefined = undefined; | |
do { | |
const response: ScanCommandOutput = await client.send( | |
new ScanCommand({ | |
TableName: tableName, | |
Select: "COUNT", | |
ExclusiveStartKey: lastEvaluatedKey, | |
}) | |
); | |
itemCount += response.Count || 0; | |
lastEvaluatedKey = response.LastEvaluatedKey; | |
} while (lastEvaluatedKey); | |
return itemCount; | |
} | |
async function main() { | |
try { | |
// ソースアカウントのテーブルリストを取得 | |
const response = await sourceClient.send(new ListTablesCommand({})); | |
const tableNames = response.TableNames || []; | |
for (const tableName of tableNames.filter((name) => name !== "Customer")) { | |
console.log(`Copying table ${tableName}...`); | |
await copyTable(tableName, tableName); // 同じ名前でコピー | |
// データサイズが小さいテーブルのみの場合はコメントアウトを解除 | |
// const isDataVerified = await verifyTableData(tableName, tableName); | |
// if (isDataVerified) { | |
// console.log( | |
// `Table ${tableName} copied successfully and data verified.` | |
// ); | |
// } else { | |
// console.error(`Data verification failed for table ${tableName}.`); | |
// } | |
await modifyTable(tableName, tableName); // キャパシティモードを更新 | |
} | |
console.log("All tables copied and verified successfully."); | |
} catch (e) { | |
console.error(e); | |
} | |
} | |
main(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment