ask-sdkのpersistenceAdaptorはDynamoDB / S3どっちがベターか?

スマートスピーカー Advent Calendar 2020が空いていたので追加参戦。

ask-sdkのPersistenceAdaptor

ask-sdkはデフォルトだと永続的にデータを保存することができません。

もっとも簡単な方法でも、以下のように明示的に宣言する必要があります。

const standardSkillBuilder = Alexa.SkillBuilders.standard().withTableName('Table_Name')

そしてデータの保存先に利用できるのは、現時点だとAmazon S3またはDynamoDBの2つです。

ask-sdkのよくできているところは、Adaptorとして抽象化していることで、データソースをどれにしたとしてもスキル(ハンドラー)側の実装は変えずに済むという部分です。

以下のコードは、DynamoDB / S3どちらを設定していても動作します。理論上は、同じinterfaceで実装したAdaptorさえ用意すれば、RDSやNeptune / DocumentDBといった他のAWS DBサービスだけでなく、BigQueryなどをデータソースにすることも可能でしょう。

     // Get              
    const atts = await handletInput.attributesManager.getPersistentAttributes()       
    const usedCount = atts.usedCount || 0     
  
    // Putするオブジェクトを作成       
    handletInput.attributesManager.setPersistentAttributes({     
      ...atts,      
      usedCount: usedCount + 1  
    })       
  
    // Put   
    await handletInput.attributesManager.savePersistentAttributes()      

共通化によってDynamoDBの魅力が減っている

このようにAdaptor層があることでDBの選択肢が自由になるメリットがあります。しかし一方で、「どのデータソースでも保存できるようにする」ためにDBが持つ利便性を失うデメリットもありました。

savePersistentAttribuetsの処理をみるとわかりやすいのですが、this.attributesNameのキーに対して全ての値をMap型で保存するという挙動をしています。

そのため、保存したattributesの値を使ってデータを検索しようとすると、AWS SDKでDynamoDBを利用している場合よりもQuery / Scanが難しくなります。

aws dynamodb query  \
       --endpoint-url http://localhost:8000 \
       --table-name title    \
      --key-condition-expression "tconst = :tconst" \
     --filter-expression "aditionalInfo = :aditionalInfo" \
     --expression-attribute-values  '{ 
            ":tconst":{"S":"movie"},
              ":aditionalInfo": {
                     "M": {
                       "Location": {"S": "Bost"},
                        "Language": {"S": "FR"}
                     }
          }
  }'

参考: https://www.bmc.com/blogs/dynamodb-advanced-queries/

また、Rangeキーも指定されていませんので、Queryを実行するにはGSI(Global Secondary Index)を設定してやる必要もあります。

このような点を考えると、DynamoDBについてはAWS SDKのDocumentClientDynamoDB DataMapperなどを利用する方がより効果的な使い方ができるのではないかと思います。

S3をPersistanceAdaptorに使う

PersistenceAdaptorを利用する場合、基本的にGET / PUT / DELETEの3オペレーションのみの操作となります。また、スキルの設定など、トランザクションが必要なほど高頻度に更新されるデータでもないケースが多いことを考えると、S3に保存していく方が効率的ではないでしょうか。

ちなみに、S3を使う場合、1つのAlexa Skill DB用バケットの中に複数のスキルの情報を保存するようにすることも可能です。

import { SkillBuilders } from 'ask-sdk'
 import { S3PersistenceAdapter } from 'ask-sdk-s3-persistence-adapter'
 exports.handler = SkillBuilders.custom()
     .addRequestHandlers(…)
     .withPersistenceAdapter(
       new S3PersistenceAdapter({  
         bucketName: 'S3_BUCKET_NAME',
         pathPrefix: 'MyFirstSkill'
       })
     ) 

pathPrefixをスキル名にすることで、S3_BUCKET_NAME/MyFirstSkill/amzn.xxx.xxx.xxxのようにスキル別にディレクトリを切るような形で保存することができます。

ただし、S3の場合「通年の無料枠」が存在しません。そのため、数円〜数十円程度ではありますが、S3の利用料が発生することはご留意ください。もっとも、Alexa AWSプロモーションクレジットを使えば実質ゼロ円ですが。

DynamoDBをask-sdk / aws-sdk両方で使う

こちらも理論上は可能です。同じTableを使う場合は、AWS SDKで使うようにGSIを適宜設定してやりましょう。

なお、更新するアイテムのキーを揃えることで、どちらのSDKからも同じアイテムを操作するということも可能ですが、ASK SDK側のgetPersistentAttributesの戻り値に含まれることはありませんのでご注意ください。以下のようにきっちりattributesだけを返す実装になっています。

if (!Object.keys(data).length) {
    return {};
} else {
    return data.Item[this.attributesName];
}

Comment