AWS IoT CoreからAmazon DynamoDBに保存したデータが一部欠落していたので原因を調べて修正してみた
AWS IoT CoreからAmazon DynamoDBに保存したデータが欠落している
おのやんです。
みなさん、AWS IoT Core(以下、IoT Core)からAmazon DynamoDB(以下、DynamoDB)に保存したデータが欠落していたことはありませんか?私はあります。
一般的に、IoTデバイスからAWS環境へ大量のストリームデータを常時送信するようなケースの場合、データの欠落を完全にゼロにすることは現実的ではありません。データの欠落を防止する場合、例えば
- Amazon SNS / Amazon SQS から AWS Lambda / AWS Step Functionsを経由してDynamoDBにデータ保存
- Amazon SQSの処理失敗分はデッドレターキューに入れて、リトライ系システムも作り込む
のような対応が考えられます。
しかし、今回発生したデータ欠落はもっと別の原因だったため、今回はこちらの内容を中心に紹介します。
3行まとめ
- 今回の事象はDynamoDBのデータ上書き
- パーティションキー・ソートキーが同一のデータは、書き込み処理で上書きされる
- パーティションキー・ソートーキーが被らない設計が必要
状況
もともと、IoT Coreに接続したデバイスからデータが送信され、それをDynamoDBに保存するシステムを作成していました。
デバイスデータをクエリするのは、IoT Coreのルールで設定するSQLです。これをもとに、DynamoDBv2ルールアクションを利用してDynamoDBにデータを保存します。
参考までに、サンプルSQLを載せておきます。ここで、DynamoDBのパーティションキーはdeviceId
、ソートキーはtimestamp
です。
SELECT
topic(1) AS deviceId,
FLOOR(timestamp() / 1000) AS timestamp,
TEMPERATURE AS temperature,
HUMIDITY AS humidity,
BAROMETER AS barometer,
FROM
'device-0001/pub'
この状態で、デバイスからDynamoDBでデータを保存していったのですが、あるタイミングでアプリケーションから見たときにデータが欠落していることがわかりました。
数分の間に、デバイスが一定の個数のデータを送信する仕様になっていたのですが、ところどころ一定の数に満たないデータが存在していたという状況です。
原因の調査
私はこちらの現象を調査するために、IoT Core のAmazon CloudWatch Logs(以下、CloudWatch Logs)を設定しました。今回はデバッグで詳細な情報が必要だったため、「デバッグ」モードでログを取ることにしました。
またIoT Coreルールの設定に、エラー処理用のエラートピックを追加しました。何らかの理由でIoT Coreのルールアクションが失敗した場合に、エラートピックで捕捉して調査します。
こちらの内容は、以下のブログでも紹介されています。
以上の設定を行なって、DynamoDBのデータ欠落の原因を調査しました。具体的には、デバイスを動かしてAWSへデータを送信して、実際にデータが欠落した際のエラートピック、CloudWatch Logsのログを調べました。
この際、まずエラートピックは反応がありませんでした。実際にMQTT テストクライアントのページでデータをサブスクライブしたのですが、データ欠落が発生した時もエラーメッセージは転送されませんでした。
一方で、CloudWatch Logsのログは、全件のデータ処理が成功しているようでした。今回はIoT CoreのルールアクションとしてDynamoDBV2を採用しているのですが、この中身であるdynamodb:PutItem
の処理は、すべてSuccess
になっていました。
また、欠落したデータの前後を見てみると、欠落したデータには、同じ秒数で前後に別のデータ保存処理が実行されていました。
ここでふと、DynamoDBへデータを保存するときに時刻に関する処理を挟んでいることを思い出しました。
IoT Coreルール上では、DynamoDBにタイムスタンプを保存する際に、データ量・その他アプリケーション上の使用により、単位をミリ秒から秒に変換していました。
FLOOR(timestamp() / 1000) AS timestamp,
...何か見えてきそうですね
原因
ということで、今回の事象は「秒数をキーに設定しているDynamoDBにデータを保存する際、IoT Coreルールで秒数を変換していたため、同じ秒数のデータを保存する際に同一キーの書き込み処理が発生して、データが上書きされた」です
これを整理してまとめると、以下のようになります。
- データ欠落は、データを受信した時間の秒数が同じ場合に発生する
- アプリ側の仕様により、データ受信時間のUNIX時間をミリ秒から秒に変換して、DynamoDBの各データのソートキー(
timestamp
)として利用している- ミリ秒から秒の変換: 1000で割って、小数点以下切り捨て
- 同じ秒数で受信した場合、DynamoDBへ保存されるときの
timestamp
の値が同一となる - この際、パーティションキー(
deviceId
)もソートキー(timestamp
)も重複するデータを保存することになり、後続のPutイベントでデータが上書きされた
IoT CoreのDynamoDBv2
ルールアクションの中身は、上で少し触れた通りdynamodb:PutItem
です。
This rule action has the following requirements:
- An IAM role that AWS IoT can assume to perform the dynamodb:PutItem operation.
dynamodb:PutItem
の仕様は以下の通りです。DynamoDBでは同一キーのデータは保存できませんが、具体的にはデータ書き込み時に上書きされるとのことです。
If an item that has the same primary key as the new item already exists in the specified table, the new item completely replaces the existing item.
こちらを解消するべく、アプリ側で仕様変更を依頼し、IoT Coreルールに設定していた秒数変換を削除してtimestamp
をミリ秒に戻しました。現実的に、ミリ秒単位でtimestamp
が重複することはないかな、と思った次第です。その結果、データ欠落を解消することができました。
SELECT
topic(1) AS deviceId,
timestamp() AS timestamp,
TEMPERATURE AS temperature,
HUMIDITY AS humidity,
BAROMETER AS barometer,
FROM
'device-0001/pub'
キーの重複を防ぐ設計を
今回のケースではDynamoDBテーブル内にキーが重複するケースがあることが原因でした。DynamoDB側の仕様を把握していれば早かったのですが、エラーとして検出されず手こずりました。
そのため、パーティションキー・ソートキーが重複しないような設計をすることが必要です。
DynamoDBでは同一キーのデータの存在を許さないという制約は認識していましたが、より具体的にみると同一キーのデータが書き込み時に上書きされるんですね。
データベースを扱うならこういった設計は必須ですが、不備があった場合にどういう事象が起こるのかを改めてまとめてみました、参考になれば幸いです。では!