DynamoDBのboto3のAPIにbatch_get_itemがあります。複数のレコードを取得する際に便利ですが、たまにデータ件数が欠けることがあります。筆者もハマったので事象と解決策を記事にします。
先に結論
- batch_get_itemでデータ件数が欠けることがある。
- 欠ける原因は様々。(内部エラーやスロットリング等)
- 欠けた場合はUnprocessedKeysに欠けたデータのキーが入るので個別にリトライ処理を作る必要がある。
- batch_write_itemでも同じようなことが起きる。(キー名はUnprocessedItems)
自前でリトライ処理を仕込まないといけないので手間がかかるし、実装し忘れによる事故は怖いなあと思います。
発生する事象 batch_get_itemで取得するデータの件数が欠ける
例えばbatch_get_itemでDynamoDBから100件取得しようとしても99件しか取得できないということがありました。
何回かリトライすると100件全件取得できたこともあります。
その時、DynamoDBの設定はオンデマンドであり、スロットリングは発生していませんでした。発生頻度もまちまちな気がしました。
- batch_get_itemで取得したいデータを全件取得できないことがある(欠ける)
- リトライすると全件取得できることがある
- スロットリングは発生していない
- 欠けの発生頻度はまちまち
スロットリングが発生していない状態のデータ欠けの確率
環境やテーブルによってまちまちでした。
getするデータ数やサイズが大きいとデータ欠けが起きる確率が多いように感じました。
原因 取得できなかったキーはUnprocessedKeysで返却されることがある
boto3のリファレンスに答えがありました。
今回はオンデマンドテーブルでUnprocessedKeysによるデータ欠けが起きたので原因はおそらく内部エラーと思われます。(もちろん、取得データは16MBには遠く及びません。)
対処法
UnprocessedKeysが返却されたらリトライを実施します。サンプルコードを以下に書いてみました。
レスポンスにUnprocessedKeysが含まれている場合は上限を設けて指数関数バックオフさせています。
# リトライ回数カウント用
retry_count = 0
# 指数関数バックオフ用
sleep_time = 1
# 最大リトライ回数を定義
unprocessed_retry_max = 5
# batch_get_itemで取得したitemを格納する変数
items = []
# テーブル名(例)
table_name = 'table_name'
# 取得するキーリスト(例)
key_list = [
{
'Artist': {
'S': 'No One You Know',
},
'SongTitle': {
'S': 'Call Me Today',
},
},
{
'Artist': {
'S': 'Acme Band',
},
'SongTitle': {
'S': 'Happy Day',
},
},
]
# batch_get_itemに入力する引数
queries_keys = {table_name: {'Keys': key_list}}
dynamodb = boto3.client('dynamodb')
while True:
response = dynamodb.batch_get_item(RequestItems=queries_keys)
if 'Responses' in response and table_name in response['Responses']:
items.extend(response['Responses'][table_name])
if 'UnprocessedKeys' in response and response['UnprocessedKeys']:
queries_keys = response['UnprocessedKeys']
retry_count += 1
# リトライ最大回数に達した
if retry_count > unprocessed_retry_max:
break
# 指数関数バックオフ
time.sleep(sleep_time)
sleep_time = sleep_time * 2
else:
break
UnprocessedKeysの返却はメトリクスから追えるのか?
様々なメトリクスを確認しましたが、UnprocessedKeys発生を記録する標準メトリクスはありませんでした。
(もしUnprocessedKeysの発生を記録する標準メトリクスをご存じの方がいれば教えていただきたいです。)
注意:batch_write_itemでも同じようなことが発生する
batch_write_itemでも同様に内部エラーやスロットリングで部分的に書き込みが失敗することがあります。
batch_get_itemではキー名が少し異なり、処理できなかったアイテムはUnprocessedItemsで返却されます。同じくリトライを実装する必要があります。
# リトライ回数カウント用
retry_count = 0
# 指数関数バックオフ用
sleep_time = 1
# 最大リトライ回数を定義
unprocessed_retry_max = 5
# テーブル名(例)
table_name = 'table_name'
# 取得するキーリスト(例)
items_list = [
{
'PutRequest': {
'Item': {
'AlbumTitle': {
'S': 'Somewhat Famous',
},
'Artist': {
'S': 'No One You Know',
},
'SongTitle': {
'S': 'Call Me Today',
},
},
},
},
{
'PutRequest': {
'Item': {
'AlbumTitle': {
'S': 'Songs About Life',
},
'Artist': {
'S': 'Acme Band',
},
'SongTitle': {
'S': 'Happy Day',
},
},
},
}
]
# batch_write_itemに入力する引数
batch_items = {table_name: {'Keys': items_list}}
dynamodb = boto3.client('dynamodb')
while True:
response = dynamodb.batch_write_item(RequestItems=batch_items)
if 'UnprocessedItems' in response and response['UnprocessedItems']:
batch_items = response['UnprocessedItems']
retry_count += 1
# リトライ最大回数に達した
if retry_count > unprocessed_retry_max:
break
# 指数関数バックオフ
time.sleep(sleep_time)
sleep_time = sleep_time * 2
else:
break
まとめ
batch_get_itemでUnprocessedKeysによるリトライ処理の個別実装が必要でした。(batch_write_itemも同じくUnprocessedItemsによるリトライが必要。)
このリトライ処理を実装しないとデータ欠けによる重大なバグにつながります。batch_get_itemやbatch_write_itemを使ったプログラムを開発している方はリトライ実装がされているか点検してみてはいかがでしょうか。
PR
当ブログはWordPressテーマSWELLを使用しています。非常に使いやすく、簡単にプロのようなデザインを使えるのでお勧めです!!
SWELL – シンプル美と機能性両立を両立させた、圧巻のWordPressテーマ
システムエンジニア
AWSを中心としたクラウド案件に携わっています。
IoTシステムのバックエンド開発、Datadogを用いた監視開発など経験があります。
IT資格マニアでいろいろ取得しています。
AWS認定:SAP, DOP, SAA, DVA, SOA, CLF
Azure認定:AZ-104, AZ-300
ITIL Foundation
Oracle Master Bronze (DBA)
Oracle Master Silver (SQL)
Oracle Java Silver SE
■略歴
理系の大学院を卒業
IT企業に就職
AWSのシステム導入のプロジェクトを担当