#0.前置き
わかったつもりでも、よくよく考えてみるとひっかかるところがあったりします。
DynamoDBなんかはけっこう奥が深いというか、使っていて資料の読み込みが足らなかったり、勘違いして理解してたりなんていうのがよくあります。
batchGetをループさせて数千件取得するような処理を作っていたのですが、取得件数/2だけのキャパシティーを確保しないと秒間リクエストでひっかかって死ぬのでは?と思ったので、挙動を確認しました。
#1.論点と結論
##1−1.論点
読み込みキャパシティーユニット(以下、RCU)は、1RCUで1秒間に2回の読み込み上限になります。結果整合性の場合は1回の読み込み上限です。
ここでいう**「秒間」が、1秒間の「通算」なのか、1秒間の「瞬間」なのか**が論点です。
特に、ドキュメントに秒間の定義はなかったような気がしてます。
論点整理のため、100RCUのテーブルに、100件*10回のリクエストを送るようなケースを想定します。0.1秒ごとに100件のリクエストを送るようなイメージです。100件を同時にリクエストするので、1リクエストあたり50RCUを消費することになります。
###1−1−1.「通算」の場合
0.1秒ごとにRCUを50ずつ消費します。
それを通算すると50,100,150と遷移し、3回目のリクエストで上限を突破します。
###1−1−2.「瞬間」の場合
0.1秒ごとにRCUが50消費されます。
ただし、1回目のリクエストで50が消費され、すぐにRCUが回復し、2回目のリクエストでは50しか消費しないとすれば、上限を突破することはありません。
##1−2.結論
先に結論を提示すると、1秒間の「瞬間」リクエストと考えて問題ないようです。
1秒間に同時に読み込みキャパシティーを超えるコネクションが発生したらアウトだが、コネクションの接続と破棄が超高速で行われているから問題ないというところになりそうです。
以下より、実証をします。ただ、qiitaにまとめようと思ってテストしていたわけではないので、多少データの時系列にぶれや幅があります。その点はご理解いただいた上で御覧ください。
#2.実証
##2-1.テスト実装
実装からの抜粋です。このままだと動きませんが、下記のようなコードを書きました。
// テスト用に配列を生成する
let values = [];
for(let i=1;i <= 10000; i++){
values.push("id-" + i);
}
const response = await batchGetAll(tableName,values);
// 結果を表示する
console.log(response);
// 取得した配列を100件ずつちぎっては投げてbatchGetをぶん回す処理
const batchGetAll = async (tableName, values) => {
let valuesSplice = values.splice(0,100);
let keys = valuesSplice.map((element) => {
return { id: element };
});
const params = {
RequestItems: {
[tableName]: {
Keys: keys
}
}
};
let response = await dynamoDb.batchGet(params).promise();
let results = response['Responses'][tableName];
while(values.length > 0) {
// ログに表示される部分
console.log(values.length);
valuesSplice = values.splice(0,100);
keys = valuesSplice.map((element) => {
return { id: element };
});
params["RequestItems"][tableName]["Keys"] = keys;
response = await dynamoDb.batchGet(params).promise();
results = results.concat(response['Responses'][tableName]);
}
return results;
};
もともとの処理の作成中に配列を生成してぶちこむコードも載せていたのでそれをそのまま流用しました。
テスト用に配列を生成する処理の上限を10000にすると100*100が走ります。
完全な並列処理ではないので擬似的なものではありますが、結果をみたら秒間リクエストがそれなりに走っていたのでよしとします。
では実行します。ここではオンデマンドモードで実行しています。
##2−2.コード実行結果
lambdaで実装していたので、cloudWatchLogsを確認して、秒間に複数回リクエストが走っている箇所を抜粋します。
1秒間に100件*13回のバッチゲット処理が走りました。
RCUが1秒間の「通算」リクエスト上限なのだと考えると、650RCUが必要なことになります。
一方、RCUが1秒間の「瞬間」リクエスト上限なのだとすると、50RCUでも問題なさそうです。
では、RCUが実際にどう消費されているかを確認します。
##2-3.RCU消費結果
こちらもcloudWatchのメトリクスから見てみます。
何度か叩き直しているので、山が2つありますが、左側の山が今回の実行分です。
消費RCUが48.9になっていました。
#3.結論
以上を受けて、RCUは1秒間の「瞬間」リクエスト上限ということになりそうです。
秒間リクエストの上限は、awsのリソースではけっこう散見される表現になります。
ぱっと思い出せるリソースだと、lambda,api gateway,croudfront,s3あたりでしょうか。
他のサービスでも同様に1秒間の「瞬間」リクエスト上限なのであれば、awsとして考える「秒間」に対する考え方が見えてきそうですね。