DynamoDB ConditionExpressionで同時実行制御を行う

問題

ダウンロードカウンターを増加させる際、同時に複数のリクエストが来ると最大回数を超過する可能性があります。GET → 確認 → UPDATEパターンはrace conditionに脆弱です。

解決方法

import { UpdateCommand } from '@aws-sdk/lib-dynamodb';

await docClient.send(new UpdateCommand({
  TableName: 'tokens',
  Key: { PK: `TOKEN#${token}`, SK: 'TOKEN' },
  UpdateExpression: 'SET downloadCount = downloadCount + :inc',
  ConditionExpression: 'downloadCount < :max',
  ExpressionAttributeValues: {
    ':inc': 1,
    ':max': maxDownloads,
  },
}));
// 条件不一致時はConditionalCheckFailedExceptionが発生

ポイント

  • ConditionExpressionはアップデート実行前に条件をチェックします。条件が合わなければアップデート自体が実行されません。これがアトミックなのでrace conditionは不可能です。
  • 「読み取り → 確認 → 書き込み」をコードで行うと、その間に他のリクエストが割り込む可能性があります。DynamoDBに条件チェックを委譲すれば、その隙間がなくなります。
  • ConditionalCheckFailedExceptionをcatchして「ダウンロード制限超過」のレスポンスを返せます。このエラーは正常なビジネスロジックの一部です。