【DynamoDB】QueryCommand実行時のLimitの評価順に気をつける
はじめに
最近、DynamoDBに入門しました。DynamoDBはデータ読み取りにキャパシティユニットを消費するため、できる限り読み取るデータ量を少なくしたいかと思います。そこでQueryCommand実行時にLimitを使ってデータ量を減らそうとしたところ、想定と異なる挙動をしたため共有させて頂きます。
やりたいこと
以下のようなテーブルがあるとします。
該当するMonthのデータのうち、UserStatus=”active”のものが存在するかどうかを取得したいケースを考えます。
1件でも存在すれば良いので、キャパシティユニットの消費を抑えるため、Limit: 1を使用したいと思います。
最初に実装した内容
当初、以下のようなコマンドでデータを取得しようとしていました。
const command = new QueryCommand({
TableName: "TestTable",
KeyConditionExpression: "RegisteredMonth = :Month",
FilterExpression: "UserStatus = :Status",
ExpressionAttributeValues: {
":Month": 202506,
":Status": "active",
},
Limit: 1,
})
const { Items } = await ddbDocClient.send(command);
console.log(Items);
UserStatus=”active”であるデータは1件存在するため、そのデータが取得されることを期待していました。
しかし、結果は0件でした。
$ node test.js
[]
修正版
QueryCommandを使用すると、まずクエリが行われます。クエリというのはKeyConditionExpressionに指定した条件に合致するデータを取得することです。クエリで返されるデータ量は、Limitを指定した場合はその指定した件数になります。つまり、Limit: 1を指定した場合、クエリの時点でデータが1件のみに絞られます。
FilterExpressionはクエリの後に行われるフィルタリングです。Limit: 1を指定した場合、クエリされた1件がUserStatus=”active”でなければ結果は0件ということになります。
これを防ぐには、絞り込みたい条件をクエリのプロセスの中で行う必要があります。つまり、KeyConditionExpressionに指定できるようにします。KeyConditionExpressionに指定できるのはデフォルトのPKとSK、そしてGSI及びLSIのPKとSKです。
先ほどのテーブルと同じ構造で、UserStatusをローカルセカンダリインデックスのソートキーにしたテーブルを新たに作成し、以下のコマンドを実行します。セカンダリインデックスを使用する場合はIndexNameを指定します。
const command = new QueryCommand({
TableName: "TestTable2",
IndexName: "UserStatusIndex",
KeyConditionExpression: "RegisteredMonth = :Month AND UserStatus = :Status",
ExpressionAttributeValues: {
":Month": 202506,
":Status": "active",
},
Limit: 1,
})
const { Items } = await ddbDocClient.send(command);
console.log(Items);
今度はデータが1件取得できました。
$ node test2.js
[ { ID: '4', RegisteredMonth: 202506, UserStatus: 'active' } ]
ちなみに読み取りキャパシティユニットの消費量はクエリ時に決まります。そのため、フィルタリングでどれだけ多くのデータが除外されたとしても消費量に影響はありません。
参考ドキュメント
実はこの挙動はSDKリファレンスにしっかりと書いてあります。公式ドキュメントはちゃんと読まなければなりませんね。
A single
Query
operation will read up to the maximum number of items set (if using theLimit
parameter) or a maximum of 1 MB of data and then apply any filtering to the results usingFilterExpression
.
Google翻訳
1 回のクエリ操作では、設定されたアイテムの最大数 (Limit パラメータを使用している場合) または最大 1 MB のデータが読み取られ、その後、FilterExpression を使用して結果にフィルタリングが適用されます。
おわりに
新しい技術を使うときは、感覚で扱わずにきちんと仕様を理解して使わなければならないなと改めて思いました。
この記事がどなたかの参考になれば幸いです。