KeyConditionExpression でソートキーを範囲指定 (>= など) してる時に、ExclusiveStartKey で指定した開始位置が、範囲指定のスタート位置より外側である場合に発生する。

↓こういうの。そういえば以前は、DynamoDBのソートキーはレンジキーって呼ばれてたんだっけ。

ValidationException: The provided starting key does not match the range key predicate

具体的には、以下のようなパラメータでqueryしたら出てくる。

// ※MyTable はパーティションキーが user_id, ソートキーが created_at
const params = {
	TableName: 'MyTable',
	KeyConditionExpression: 'user_id = :u and created_at >= :c',
	ExpressionAttributeValues: {
		':u': 'kiriukun',
		':c': '1999-07-31T00:00:10.000Z'
	},
	ExclusiveStartKey: {
		user_id: 'kiriukun',
		created_at: '1999-07-31T00:00:09.000Z'
	}
};

パーティションキーは同値だからここでは触れないものとして、このパラメータの意味は以下の通りになる。

  • KeyConditionExpression では、created_at1999-07-31T00:00:10.000Z 以上のデータを探したい。
  • ExclusiveStartKey では、created_at1999-07-31T00:00:09.000Z の次のデータから開始したい。

名前の通り、ExclusiveStartKey は指定したキーの『次』の値から読み取るためのオプションだ。範囲外なら ExclusiveStartKey は無視してくれればと思うのだが、DynamoDB的にはダメなようだ。

なので KeyConditionExpression で範囲指定しながら ExclusiveStartKey を使うときは、ExclusiveStartKey で指定するソートキーが KeyConditionExpression の範囲内であることを、事前にチェックしておく必要がある。


↓のように、:cExclusiveStartKey.created_at を同値に変更した場合はエラーにならない。

エラー出ない
const params = {
	TableName: 'MyTable',
	KeyConditionExpression: 'user_id = :u and created_at >= :c',
	ExpressionAttributeValues: {
		':u': 'kiriukun',
		':c': '1999-07-31T00:00:10.000Z'
	},
	ExclusiveStartKey: {
		user_id: 'kiriukun',
		created_at: '1999-07-31T00:00:10.000Z'  // ← :c に揃えた
	}
};

1999-07-31T00:00:10.000Z 以上のデータを探すために 1999-07-31T00:00:10.000Z の『次』から開始するのは、範囲内に収まってるからOK。むしろ、ジャスト 1999-07-31T00:00:10.000Z のデータは引っかからないし。

なお、この範囲の判定は ScanIndexForward の影響を受けるので注意。上記のパラメータに ScanIndexForward: false を追加する↓と、またエラーが出るようになる。

エラー出る
const params = {
	TableName: 'MyTable',
	KeyConditionExpression: 'user_id = :u and created_at >= :c',
	ExpressionAttributeValues: {
		':u': 'kiriukun',
		':c': '1999-07-31T00:00:10.000Z'
	},
	ExclusiveStartKey: {
		user_id: 'kiriukun',
		created_at: '1999-07-31T00:00:10.000Z'
	},
	ScanIndexForward: false  // ← 降順で取得するようにした
};

ExclusiveStartKey は指定したキーの次の項目から読み始めるものなので、降順の時は逆転するからだ。降順のとき、1999-07-31T00:00:10.000Z の『次』のデータはそれより昔ということになり、KeyConditionExpression の範囲外になる。

……が、個人的に首を捻ってしまったのは↓の場合。(昇順)

エラー出る!
const params = {
	TableName: 'MyTable',
	KeyConditionExpression: 'user_id = :u and created_at > :c',  // ←ここを > にした
	ExpressionAttributeValues: {
		':u': 'kiriukun',
		':c': '1999-07-31T00:00:10.000Z'
	},
	ExclusiveStartKey: {
		user_id: 'kiriukun',
		created_at: '1999-07-31T00:00:10.000Z'
	}
};

これはエラーになる。なるのだが……

  • KeyConditionExpression では、created_at1999-07-31T00:00:10.000Z より上のデータを探したい。
  • ExclusiveStartKey では、created_at1999-07-31T00:00:10.000Z の次のデータから開始したい。

……確かに自明すぎて ExclusiveStartKey を設定する意味が皆無だけど、ギリギリ範囲外ではないような? 問題にされてるのは、実際の開始位置より ExclusiveStartKey 自体の位置ということだろうか。

DynamoDBの中身とかを知ればしっくりくるのかもしれないけど、とりあえず今は「DynamoDBは明らかにおかしいこと (範囲外) を言われるのが嫌いだし、分かり切ってることを回りくどく (> & ジャストExclusiveStartKey) 言われるのも嫌い」と思うことにした。

以上。