DynamoDBの秒間リクエストの「秒間」の意味について検証してみた

#0.前置き
わかったつもりでも、よくよく考えてみるとひっかかるところがあったりします。
DynamoDBなんかはけっこう奥が深いというか、使っていて資料の読み込みが足らなかったり、勘違いして理解してたりなんていうのがよくあります。
batchGetをループさせて数千件取得するような処理を作っていたのですが、取得件数/2だけのキャパシティーを確保しないと秒間リクエストでひっかかって死ぬのでは？と思ったので、挙動を確認しました。

#1.論点と結論

##１−１．論点
読み込みキャパシティーユニット（以下、RCU）は、１RCUで１秒間に２回の読み込み上限になります。結果整合性の場合は１回の読み込み上限です。
ここでいう**「秒間」が、１秒間の「通算」なのか、１秒間の「瞬間」なのか**が論点です。
特に、ドキュメントに秒間の定義はなかったような気がしてます。

論点整理のため、１００RCUのテーブルに、１００件＊１０回のリクエストを送るようなケースを想定します。0.1秒ごとに１００件のリクエストを送るようなイメージです。１００件を同時にリクエストするので、１リクエストあたり５０RCUを消費することになります。

###１−１−１．「通算」の場合
0.1秒ごとにRCUを50ずつ消費します。
それを通算すると５０，１００，１５０と遷移し、３回目のリクエストで上限を突破します。

###１−１−２．「瞬間」の場合
0.1秒ごとにRCUが５０消費されます。
ただし、１回目のリクエストで５０が消費され、すぐにRCUが回復し、２回目のリクエストでは５０しか消費しないとすれば、上限を突破することはありません。

##１−２．結論
先に結論を提示すると、**１秒間の「瞬間」リクエスト**と考えて問題ないようです。

１秒間に同時に読み込みキャパシティーを超えるコネクションが発生したらアウトだが、コネクションの接続と破棄が超高速で行われているから問題ないというところになりそうです。

以下より、実証をします。ただ、qiitaにまとめようと思ってテストしていたわけではないので、多少データの時系列にぶれや幅があります。その点はご理解いただいた上で御覧ください。

#2.実証
##2-1.テスト実装
実装からの抜粋です。このままだと動きませんが、下記のようなコードを書きました。

```javascript:batchGetAll.js

// テスト用に配列を生成する
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;
};
```

もともとの処理の作成中に配列を生成してぶちこむコードも載せていたのでそれをそのまま流用しました。
テスト用に配列を生成する処理の上限を１００００にすると１００＊１００が走ります。
完全な並列処理ではないので擬似的なものではありますが、結果をみたら秒間リクエストがそれなりに走っていたのでよしとします。
では実行します。ここではオンデマンドモードで実行しています。

##２−２．コード実行結果
lambdaで実装していたので、cloudWatchLogsを確認して、秒間に複数回リクエストが走っている箇所を抜粋します。
<img width="838" alt="スクリーンショット 2019-06-01 12.48.00.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126823/931cbc80-2905-0ac9-5cef-90f3e868f471.png">

１秒間に１００件＊１３回のバッチゲット処理が走りました。
RCUが１秒間の「通算」リクエスト上限なのだと考えると、650RCUが必要なことになります。
一方、RCUが１秒間の「瞬間」リクエスト上限なのだとすると、50RCUでも問題なさそうです。
では、RCUが実際にどう消費されているかを確認します。

##2-3.RCU消費結果
こちらもcloudWatchのメトリクスから見てみます。
<img width="1386" alt="スクリーンショット 2019-06-01 12.47.01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/126823/db1a1893-ef59-8abb-7b04-381d9b176b0d.png">

何度か叩き直しているので、山が２つありますが、左側の山が今回の実行分です。
消費RCUが48.9になっていました。

#3.結論
以上を受けて、RCUは１秒間の「瞬間」リクエスト上限ということになりそうです。
秒間リクエストの上限は、awsのリソースではけっこう散見される表現になります。
ぱっと思い出せるリソースだと、lambda,api gateway,croudfront,s3あたりでしょうか。
他のサービスでも同様に１秒間の「瞬間」リクエスト上限なのであれば、awsとして考える「秒間」に対する考え方が見えてきそうですね。