uuidをキーとするテーブルの各レコード(DynamoDB 的には厳密にはアイテム)に対して、各行にuuidリストが書かれたテキストファイルを読んで対象のレコードにフラグをセットしていくスクリプトを実行した。
本記事末尾に記載したような Node.js のスクリプトで 80WCU (Write Capacity Unit) 消費で済むと思っていた。しかし結果的に 240WCU と3倍消費していた。
原因
- RCU/WCU の U は Unit であり、Item (Record) ではない。RCU の Unit は 4KB であり、WCU の Unit は 1KB であった。
- テーブルに入ってる既存のデータが各レコードで平均 3KB 近くあったため、1更新に 3WCU 使っていた。
反省と考察
- レコードの 4KB を最大値の基準として気にしていたが、RCU と WCU の Unit に差があったことを知らなかった。
- テーブル設計が最適ではなかった。
テープルの中で更新頻度が高く読み取り頻度が低いフィールド(属性)があり、かつ独立できるものがあった。これはテーブルを分けるべきだった。 - 1レコードのサイズを太らせていくような実装は危険、不要になったフィールドは消し込んだ方が良い(もちろんその場合もWCUを考慮した削除が必要である)。
スクリプト
'use strict';
const fs = require('fs');
const AWS = require('aws-sdk');
const LineStream = require('byline').LineStream;
const PacedWorkStream = require('paced-work-stream');
const PromisedLife = require('promised-lifestream');
const docClient = new AWS.DynamoDB.DocumentClient({ region 'ap-northeast-1' });
const TABLE_NAME = 'users';
const pacedWorker = new PacedWorkStream({
concurrency: 8,
workMS: 100, // 1000ms/sec / 100ms * 8 = 80 WCU
}, function(line) {
const uuid = line.toString();
console.log('%s Begin %s', new Date().toISOString(), uuid);
return docClient.update({
TableName: TABLE_NAME,
Key: { uuid: uuid },
UpdateExpression: 'set #a = :v',
ExpressionAttributeNames: { '#a': 'newFlag' },
ExpressionAttributeValues: { ':v': true }
})
.promise()
.then(() => {
pacedWorker.countTag('update');
console.log('%s End uuid:%s', new Date().toISOString(), uuid);
})
.catch(err => {
console.warn(new Date().toISOString(), err);
});
});
PromisedLife([
fs.createReadStream('uuid.txt'),
new LineStream(),
pacedWorker
])
.then(() => {
console.log(pacedWorker.tagCounts);
})
.catch(err => {
console.error(err);
});