[DynamoDB] put, batchWrite, transactWriteの速度比較
Amazon DynamoDBの書き込み操作 put
batchWrite
transactWrite
はそれぞれどれくらい速度に差があるのか検証してみました(この中で transactWrite
は速度より一貫性を重視している操作ではありますが、それはそれで他の操作と比べてどれくらい差があるのか気になったため)。
オンデマンドモードのテーブルを用意し、それぞれの操作で1万件投入するスクリプトを書いてみます。
環境
- M1 Mac
- node 16.15.0
- typescript 4.7.4
- @aws-sdk/{client,lib}-dynamodb 3.121.0
- lodash 4.17.21
- @faker-js/faker 7.3.0
- uuid 8.3.2
- esbuild 0.14.48
- esbuild-register 3.3.3
テーブル作成
- テーブル名:
testItems
- オンデマンドモード
- 主キー:
- ハッシュキー:
id: S
- ソートキー: なし
- ハッシュキー:
としてテーブル作成します。
検証補助スクリプト
検証作業の補助用途として次のようなスクリプトを用意しています。
全件数確認スクリプト
テーブルの全件数を確認するスクリプトです。各操作後にきっちり1万件入ったかどうか確認するのに使います。
import { DynamoDB } from "@aws-sdk/client-dynamodb"
import { DynamoDBDocument } from "@aws-sdk/lib-dynamodb"
const main = async () => {
// DynamoDB Document Client初期化
const ddbDoc = DynamoDBDocument.from(
new DynamoDB({ region: "ap-northeast-1" })
)
let exclusiveStartKey: any | null | undefined = null
let count = 0
while (exclusiveStartKey !== undefined) {
const result = await ddbDoc.scan({
TableName: "testItems",
ExclusiveStartKey: exclusiveStartKey ?? undefined,
})
exclusiveStartKey = result.LastEvaluatedKey
count += result.Items?.length ?? 0
}
console.log({ count })
}
main()
全件削除スクリプト
テーブルの全アイテムをスキャンしながら削除するスクリプトです。条件を揃えるため、各操作前にテーブルを0件にします。
テーブル自体を削除して再作成する手もありますが、今回はこちらを選択しました。
import { DynamoDB } from "@aws-sdk/client-dynamodb"
import { DynamoDBDocument } from "@aws-sdk/lib-dynamodb"
import { chunk } from "lodash"
const main = async () => {
const ddbDoc = DynamoDBDocument.from(
new DynamoDB({ region: "ap-northeast-1" })
)
let items =
(
await ddbDoc.scan({
TableName: "testItems",
})
).Items ?? []
while (items.length > 0) {
const chunkedItems = chunk(items, 25)
for (const items of chunkedItems) {
await ddbDoc.batchWrite({
RequestItems: {
testItems: items.map((item) => ({
DeleteRequest: {
Key: {
id: item.id,
},
},
})),
},
})
}
items =
(
await ddbDoc.scan({
TableName: "testItems",
})
).Items ?? []
}
}
main()
比較
計測方法
/usr/bin/time -l
を使い、下記のように実行して計測します。
/usr/bin/time -l node -r esbuild-register "path/to/file.ts"
put
まずは put
です。直列に1万件投入します。
import { DynamoDB } from "@aws-sdk/client-dynamodb"
import { DynamoDBDocument } from "@aws-sdk/lib-dynamodb"
import * as uuid from "uuid"
import { faker } from "@faker-js/faker"
const main = async () => {
const items = Array.from({ length: 10000 }).map(() => ({
id: uuid.v4(),
name: faker.name.findName(),
age: Number(faker.random.numeric(2)),
content: faker.lorem.sentences(),
createdAt: faker.date.recent().toISOString(),
updatedAt: faker.date.recent().toISOString(),
}))
const ddbDoc = DynamoDBDocument.from(
new DynamoDB({ region: "ap-northeast-1" })
)
for (const item of items) {
await ddbDoc.put({
TableName: "testItems",
Item: item,
})
}
}
main()
293.38 real 20.25 user 1.99 sys
151207936 maximum resident set size
293.38秒かかるようです。
batchWrite
続いて batchWrite
で25件ずつ投入してみます。
import { DynamoDB } from "@aws-sdk/client-dynamodb"
import { DynamoDBDocument } from "@aws-sdk/lib-dynamodb"
import * as uuid from "uuid"
import { faker } from "@faker-js/faker"
import { chunk } from "lodash"
const main = async () => {
const allItems = Array.from({ length: 10000 }).map(() => ({
id: uuid.v4(),
name: faker.name.findName(),
age: Number(faker.random.numeric(2)),
content: faker.lorem.sentences(),
createdAt: faker.date.recent().toISOString(),
updatedAt: faker.date.recent().toISOString(),
}))
const ddbDoc = DynamoDBDocument.from(
new DynamoDB({ region: "ap-northeast-1" })
)
// 10000件のデータを作成
const chunkedAllItems = chunk(allItems, 25)
for (const items of chunkedAllItems) {
const result = await ddbDoc.batchWrite({
RequestItems: {
testItems: items.map((item) => ({
PutRequest: {
Item: item,
},
})),
},
})
}
}
main()
14.45 real 2.91 user 0.31 sys
155615232 maximum resident set size
14.45秒でした。これだけ見ると相当速くなる印象です。
put + Promise.all
put
でも並列にリクエストを投げれば batchWrite
に近い速度が出るのでは?ということで Promise.all
で25件ずつ投入します。
import { DynamoDB } from "@aws-sdk/client-dynamodb"
import { DynamoDBDocument } from "@aws-sdk/lib-dynamodb"
import * as uuid from "uuid"
import { faker } from "@faker-js/faker"
import { chunk } from "lodash"
const main = async () => {
const allItems = Array.from({ length: 10000 }).map(() => ({
id: uuid.v4(),
name: faker.name.findName(),
age: Number(faker.random.numeric(2)),
content: faker.lorem.sentences(),
createdAt: faker.date.recent().toISOString(),
updatedAt: faker.date.recent().toISOString(),
}))
const ddbDoc = DynamoDBDocument.from(
new DynamoDB({ region: "ap-northeast-1" })
)
// 10000件のデータを作成
const chunkedAllItems = chunk(allItems, 25)
for (const items of chunkedAllItems) {
await Promise.all(
items.map((item) =>
ddbDoc.put({
TableName: "testItems",
Item: item,
})
)
)
}
}
main()
17.52 real 6.59 user 0.65 sys
205717504 maximum resident set size
17.52秒でした。 batchWrite
にかなり迫ってますが、メモリ使用量が batchWrite
よりやや増えてる点は気になります。
transactWrite
transactWrite
は速度より一貫性を重視している操作ですが、比較としてやってみます。
import { DynamoDB } from "@aws-sdk/client-dynamodb"
import { DynamoDBDocument } from "@aws-sdk/lib-dynamodb"
import * as uuid from "uuid"
import { faker } from "@faker-js/faker"
import { chunk } from "lodash"
const main = async () => {
const allItems = Array.from({ length: 10000 }).map(() => ({
id: uuid.v4(),
name: faker.name.findName(),
age: Number(faker.random.numeric(2)),
content: faker.lorem.sentences(),
createdAt: faker.date.recent().toISOString(),
updatedAt: faker.date.recent().toISOString(),
}))
const ddbDoc = DynamoDBDocument.from(
new DynamoDB({ region: "ap-northeast-1" })
)
// 10000件のデータを作成
const chunkedAllItems = chunk(allItems, 25)
for (const items of chunkedAllItems) {
await ddbDoc.transactWrite({
TransactItems: items.map((item) => ({
Put: {
TableName: "testItems",
Item: item,
},
})),
})
}
}
main()
24.78 real 3.12 user 0.37 sys
152387584 maximum resident set size
24.78秒でした。batchWrite
よりは遅いですがそこまでというか、実用上問題は出てこなさそうという感想です。
まとめ
それぞれ1万件投入にかかった時間をまとめます。
ケース | 時間 |
---|---|
put | 293.38秒 |
batchWrite | 14.45秒 |
put+Promise.all | 17.52秒 |
transactWrite | 24.78秒 |
- 大量データの保存に
put
直列実行を選択するとすごく遅い -
batchWrite
が最速 -
put
+Promise.all
も速い -
transactWrite
はbatchWrite
よりは遅いが、十分な速度
といった所感でした。
Discussion