この記事はシリーズものです。
まとめ
- DynamoDB の Item 取得処理を
scan()
からquery()
に変更 - 検索結果を絞り込むために必要な Attribute をテーブルの Partition Key に設定
以上を行うことで、処理時間が5秒から0.5秒に下がり、レスポンス速度を向上することができた。
# 修正前 > curl -w "speed: %{time_total}\n" -o /dev/null -s https://{END_POINT}?input_text=iaa speed: 5.245267 # 修正後 > curl -w "speed: %{time_total}\n" -o /dev/null -s https://{END_POINT}?input_text=iaa speed: 0.523382
以下詳細。
開発環境
> aws --version aws-cli/2.2.5 Python/3.8.8 Darwin/20.6.0 exe/x86_64 prompt/off
API 構成
前提としてAPIの構成をメモ。
ベースは上記の通り。
現状
自作した API に対して GET リクエストを行い、レスポンスが返ってくるまで5秒かかっている。
> curl https://{END_POINT}?input_text=iaa | jq . % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 751 100 751 0 0 142 0 0:00:05 0:00:05 --:--:-- 198 [ { "japanese_word": "近さ", "roman_alphabet": "chikasa", "vowels": "iaa" }, { "japanese_word": "近間", "roman_alphabet": "chikama", "vowels": "iaa" }, { "japanese_word": "ギター", "roman_alphabet": "gitaa", "vowels": "iaa" }, ... ]
5秒は体感めちゃくちゃ遅い。レスポンス速度を向上したい。
様々なアプローチのやり方があるが、今回は DB 周りの取得処理・設計を見直して速度向上を試みる。
というのも、DynamoDB をよくわからないまま使っているので本記事で少しでも理解を深める。
今回修正する範囲
- Lambda → DynamoDB の Item の取得処理
- DynamoDBのテーブル設計
1. Lambda → DynamoDB の Item の取得処理 (1)
現在の取得処理
Lambda から DynamoDB の取得処理を見る。
dynamodb_tbl = boto3.resource('dynamodb').Table('{TABLE_NAME}') def lambda_handler(event, context): # クエリ文字列の取得 input_text = event['queryStringParameters']['input_text'] response = dynamodb_tbl.scan() items = response['Items'] # 以下、for 文でクエリ文字列を使い items の中から欲しいものだけを取得 ...
scan()
は何をしているのだろうか。ドキュメントを見る。
The Scan operation returns one or more items and item attributes by accessing every item in a table or a secondary index.
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb.html#DynamoDB.Table.scan
テーブル内のアイテム全件取得。Lambda 内ではクエリ文字列を使って条件に沿った Item だけを取得することが目的のため、冗長な利用となっている。
scan()
以外で効率的な取得処理を探すと、query()
があるのでドキュメントを見る。
You must provide the name of the partition key attribute and a single value for that attribute. Query returns all items with that partition key value. Optionally, you can provide a sort key attribute and use a comparison operator to refine the search results.
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/dynamodb.html#DynamoDB.Table.query
「Partition Key 属性の名前とその属性の単一の値を指定する必要があります。 クエリは、その Partition Key 値を持つすべてのアイテムを返します。 オプションで、Sort Key 属性を指定し、比較演算子を使用して検索結果を絞り込むことができます。」
query()
は Partition Key, Sort Key を利用して検索結果を絞り込む。明らかに全件取得より効率が良さそう。現在のテーブルの構成で Partition Key, Sort Key が設定されているか確認する。
2. DynamoDBのテーブル設計
テーブル設計を見る前にどういう Item がテーブルに入っているか確認。
{ "Items": [ { "japanese_word": { "S": "証" }, "roman_alphabet": { "S": "shou" }, "vowels": { "S": "ou" }, "uuid4": { "S": "a65b5329-493a-4ef6-9867-9679fdc8fecd" } }, ... ] }
現在のdynamoDBのテーブル構成
> aws dynamodb describe-table \ --table-name {TABLE_NAME} "TableName": "{TABLE_NAME}", "KeySchema": [ { "AttributeName": "uuid4", "KeyType": "HASH" } ], ... } }
現在利用しているテーブルには AttributeName が uuid4, KeyType が HASH のものが設定されている。HASH は Partition Keyのことを指す。*1
query()
を使うには意味のある Attribute を Partition Key か Sort Key に設定する必要がある。そのため現在のテーブル構成では目的が叶えられない。
APIの目的はクエリ文字列と同じ母音(vowels
)を持つ単語のリストを返すことのため、vowels
を Partition Key か Sort Key に設定する必要がある。結果として、以下の考えから Partition Key: vowels
, Sort Key: uuid4 を設定してテーブルを作り直す。
- ❌ Partition Key: uuid4, Sort Key: None (現状の構成)
query()
のために Partition Key にvowels
を設定したいので目的が叶えれない
- ❌ Partition Key:
vowels
, Sort Key: None - ✅ Partition Key:
vowels
, Sort Key: uuid4- 上記の不適要素を解決できる。
- Sort Key が uuid のためソート不可能だが、Key として人間が使わないので一旦OKとする。Partion Key と Sort Key の組み合わせで一意になれば良いので Sort Key はユニークであれば何でも良さそう。今回は uuid4 にした。
GUIポチポチしてテーブルを作り直した。(ここのあたりコード化したい)
1. Lambda → DynamoDB の Item の取得処理 (2)
テーブル設計を変更して query()
を使えるようになったので lambda を修正する。
dynamodb_tbl = boto3.resource('dynamodb').Table('{TABLE_NAME}') def lambda_handler(event, context): # クエリ文字列の取得 input_text = event['queryStringParameters']['input_text'] # response = dynamodb_tbl.scan() response = dynamodb_tbl.query( KeyConditionExpression=Key('vowels').eq(input_text) )
実行すると、Time Totalが表示されなくなった。体感めちゃくちゃ早くなった。
> curl https://{END_POINT}?input_text\=iaa | jq . % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 751 100 751 0 0 1449 0 --:--:-- --:--:-- --:--:-- 1449 [ { "japanese_word": "いばら", "roman_alphabet": "ibara", "vowels": "iaa" }, { "japanese_word": "イサカ", "roman_alphabet": "isaka", "vowels": "iaa" }, ... ]
数値でどれくらいレスポンス速度が向上したか確認するために curl -w を使って total_speed を明示的に表示するようにしたところ、以下の結果となった。
# 修正前 > curl -w "speed: %{time_total}\n" -o /dev/null -s https://{END_POINT}?input_text=iaa speed: 5.245267 # 修正後 > curl -w "speed: %{time_total}\n" -o /dev/null -s https://{END_POINT}?input_text=iaa speed: 0.523382
めちゃくちゃ早くなってる!
処理時間が5秒から0.5秒に下がり、レスポンス速度を向上することができた。