AmazonLexの基礎

The following topics provide additional information. We recommend that you review them in order and then explore the Getting Started with Amazon Lex exercises.

とのことで、ざっと見てみる

前提知識

AmazonLexの概要(コアコンセプト·用語)を把握してみる - keiwt’s diary

Programming Model

Model Building API Operations

CloudFormation対応されるまでは叩くAPIコマンドをリビジョン管理

Runtime API Operations

PostContent

テキストと音声
  • fomats
Input: LPCM, Opus
Output: MPEG, OGG, PCM

※8kHz(コールセンター) and 16kHz

PostText

テキストのみ

Lambda Functions as Code Hooks

Customize user interaction

例: ユーザーの履歴に応じてピザのトッピングを表示

※ギガ・ミートしか頼まない人にはギガ・ミートを一番上に表示
※過去に注文した数の降順でトッピングを表示
※最近注文したものから表示

Validate the user's input

例: ピザ1000枚注文した場合
例: シーフード食べれない人がシーフードピザを注文した場合

※異常値等の排除

Fulfill the user's intent

例: ピザを注文する

Lambda functions as code hooks

※Intentのフック

Dialog code hook for initialization and validation

AmazonLexがユーザーのインプットを理解して、botから返答  
例: 承知致しました。ピザは3枚ですね。  
PutIntent時にdialogCodeHookを指定

Fulfillment code hook

例: ピザの注文フック  
PutIntent時にfulfillmentActivityを指定

Lambda関数の分け方

1つの関数でdialogとfulfillmentを実行させることは可能  
One Lambda function can do initialization, validation, and fulfillment.  

The event data that the Lambda function receives has a field
  that identifies the caller as either a dialog or fulfillment code hook.  

Elicit

https://ejje.weblio.jp/content/elicit

  • ElicitSlot
例: 必須ではないslot valueを聞くことができる
例: XXXクーポンをご利用なられる場合にはIDを教えてください。
  • ElicitIntent
例: 前のIntentが完了した後に新しいIntentを呼び出せる
例: お飲み物はいかがいたしましょうか?

Service Permissions

Amazon Lex uses AWS Identity and Access Management (IAM) service-linked roles.

service-linked roles

ユーザーに手動で作成させるとハマる人がいるので、AWSが事前に定義してくれているものです

AWSServiceRoleForLexBots




※LexがPollyを呼び出すため
bot作成時に自動作成

AWSServiceRoleForLexChannels




botの連携先にテキストをPOSTするため
botをSlack等に連携させると自動的に作成

Creating Resource-Based Policies for AWS Lambda

Amazon Lexはリソースべーすのポリシーを使ってLambda関数を呼び出します

Resource-Based Polices forAWS Lambda

※Lambdaではなく、Lambda関数毎にアクセス権限を管理できます

Lambda 関数は、AWS Lambda のリソースの 1 つです。

Lambda 関数に添付されたアクセス権限ポリシーは、リソースベースのポリシー (または Lambda の Lambda 関数ポリシー) と呼ばれます。Lambda 関数ポリシーを使用して、Lambda 関数の呼び出し権限を管理できます

Principal: lex.amazonaws.com
SourceArn: ${IntentのArn}

Deleting Service-Linked Roles

依存リソース削除後に削除する

Managing Messages (Prompts and Statements)

Types of Messages

Prompt

ユーザーに返答を求めるもの

Statement

返答を求めないもの

動的な返答

  • SlotName
例: {PizzaTopping}

※ギガ・ミート

  • AttributeName
例: [DeliveryTime]

※14時

  • いつ定義するか
bot作成時

Contexts for Configuring Messages

Bot-level messages

  • clarification prompts
ユーザーのIntentが理解できないときのメッセージ
例: I did'n understand you.

※Intentに紐付いたLambdaがメッセージを返さない時にも使われる

  • hang-up messages
複数回ユーザーからのメッセージが理解できないときのメッセージ
例: Sorry, I'm not able to assist at this time.

※intent, slot, follow-up prompts, intent confirmations含む

Intent-level messages

  • Confirmation prompts and cancel statements
すべてのslot valueが出揃った後 && fulfill intent前  
確認でNoならキャンセルのメッセージ
  • Goodbye message or follow-up prompts
注文完了しました。ピザは{Hour}時にお届けいたします。
他に何かご質問や確認事項等ございますでしょうか。
↓
No
↓
この度はLexピザをご利用いただきましてありがとうございました。

※Fulfillment code hookが成功して、Lambda関数がメッセージを返さなかったときにも使われる

  • Prompts to elicit value slot values
ピザは何枚でしょうか
お届け日時の入力をお願い致します
トッピングを以下から選択してください
サイズを以下から選択してください

bot作成時に指定しても、IntentのLambdaで上書かれる

コンテキスト毎にメッセージを分ける場合

session attributeとslot valueを使って分岐させる

※参照なしのメッセージを1つは用意する
※参照が解決できないとBadRequestException

Supported Message Formats

テキスト
Speech Synthesis Markup Language (SSML)

※Accept HTTP headerをtext/plain; charset=utf-8にするとテキストのみ返す
※音声のみのbotにテキストを投げるとBadRequestExceptionになるので、テキストも用意する
※outputDialogModeがテキストの場合にはLambdaからPlainTextを返す

Response Cards

選択肢を返す

※PostTextのみ(音声は現時点では非対応)
※Intent作成時に設定
※動的にしたい場合にはLambda使う

対象

Conclusion statement
Confirmation prompt
Follow-up prompt
Rejection statement
Slot type utterances

※それぞれ1つのResponse Cardだけ

Defining Static Response Cards

API: PutBot
Console: Intent作成時

Generating Response Cards Dynamically

initialization and validation Lambda関数を使う
dialogActionセクションに指定

Managing Conversation Context


Bot Deployment Options


Built-in Intents and Slot Types


Custom Slot Types


参考

http://docs.aws.amazon.com/ja_jp/lex/latest/dg/how-it-works.html ※一番下のTopics

https://docs.aws.amazon.com/console/iam/service-linked-role

http://docs.aws.amazon.com/lambda/latest/dg/access-control-resource-based.html

http://docs.aws.amazon.com/ja_jp/polly/latest/dg/ssml.html

http://docs.aws.amazon.com/ja_jp/lex/latest/dg/lambda-input-response-format.html

http://docs.aws.amazon.com/ja_jp/lex/latest/dg/howitworks-manage-prompts.html#msg-prompts-resp-card-dynamic

http://docs.aws.amazon.com/ja_jp/lex/latest/dg/howitworks-manage-prompts.html#msg-prompts-resp-card-dynamic

AmazonLexの概要(コアコンセプト·用語)を把握してみる

bot

A bot performs automated tasks
Amazon Lex bots can understand user input provided with text or speech and converse in natural language. 

※テキストでも、音声でも自動化したタスクをしてくれるもの

Intent

An intent represents an action that the user wants to perform.

※やりたいこと
※例: OrderPizza

utterances

How a user might convey the intent.

※intentの指定方法
※例: ピザが食べたい

How to fulfill the intent

We recommend that you create a Lambda function to fulfill the intent.

※lambdaを使ってやりたいことを実現します

Slot

For example, the OrderPizza intent requires slots such as pizza size, crust type, and number of pizzas.

※やりたいことを実現するために必要な情報 ※例: ピザのサイズ、ピザの皮の種類、ピザの数

Slot type

Size – With enumeration values Small, Medium, and Large.
Crust – With enumeration values Thick and Thin.

参考

http://docs.aws.amazon.com/lex/latest/dg/how-it-works.html

AWS CloudFormation StackSets使ってみる

AWS CloudFormation StackSetsとは

AWS CloudFormation StackSets は、複数のアカウントおよびリージョンのスタックを 1 度のオペレーションで、作成、更新、削除できるようにすることで、スタックの機能を拡張します。

使用するケース

1つのAWSアカウントに複数のアプリケーションを作成してしまっている場合にアカウントを分けて管理する
CloudTrailの有効化等一律設定が必要なものを1発で設定する場合

準備

アカウントを複数用意する

ルートアカウント

すべてのサブドメインの親アカウントにする

サブドメイン用アカウント

アカウント名
メールアドレス

※.com等なしのものを用意する

aws-cliのアップデート

pip install --upgrade awscli

.bash_profile

export AdminAccountId=
export AdminAccountBucket=
export TargetAccount=
export TargetAccountBucket=

管理者アカウントでAWSCloudFormationStackSetAdministrationRoleの作成(例)

aws configure --profile ${TargetAccount}
aws s3 mb s3://${TargetAccountBucket} --region ap-northeast-1 --profile ${TargetAccount}
aws s3 cp ./yaml/target/AWSCloudFormationStackSetExecutionRole.yml s3://${TargetAccountBucket}/yaml/target/ --profile ${TargetAccount}

aws cloudformation create-stack --stack-name target-stack-sets \
  --capabilities CAPABILITY_NAMED_IAM \
  --parameters ParameterKey=AdministratorAccountId,ParameterValue=${AdminAccountId} \
  --template-url https://s3-ap-northeast-1.amazonaws.com/${TargetAccountBucket}/yaml/target/AWSCloudFormationStackSetExecutionRole.yml \
  --profile ${TargetAccount}

ターゲットアカウントAWSCloudFormationStackSetExecutionRoleを作成(例)

aws configure --profile ${TargetAccount}
aws s3 mb s3://${TargetAccountBucket} --region ap-northeast-1 --profile ${TargetAccount}
aws s3 cp ./yaml/target/AWSCloudFormationStackSetExecutionRole.yml s3://${TargetAccountBucket}/yaml/target/ --profile ${TargetAccount}

aws cloudformation create-stack --stack-name target-stack-sets \
  --capabilities CAPABILITY_NAMED_IAM \
  --parameters ParameterKey=AdministratorAccountId,ParameterValue=${AdminAccountId} \
  --template-url https://s3-ap-northeast-1.amazonaws.com/${TargetAccountBucket}/yaml/target/AWSCloudFormationStackSetExecutionRole.yml \
  --profile ${TargetAccount}

stack-setsの作成

aws cloudformation create-stack-set --stack-set-name my-awsconfig-stackset \
  --capabilities CAPABILITY_IAM \
  --template-url https://s3.amazonaws.com/cloudformation-stackset-sample-templates-us-east-1/EnableAWSConfig.yml

stack-instancesの作成

aws cloudformation create-stack-instances --stack-set-name my-awsconfig-stackset \
  --accounts '["1111111111111"]' \
  --regions '["ap-northeast-1"]' \
  --operation-preferences FailureToleranceCount=0,MaxConcurrentCount=1

stack-instancesの削除

aws cloudformation delete-stack-instances --stack-set-name my-awsconfig-stackset \
  --accounts '["1111111111111"]' \
  --regions '["ap-northeast-1"]' \
  --operation-preferences FailureToleranceCount=0,MaxConcurrentCount=1 \
  --no-retain-stacks

stack-setsの削除

aws cloudformation delete-stack-set --stack-set-name my-awsconfig-stackset

textlintで日本語のチェックを自動化してみる

インストール

npm i -g textlint textlint-rule-preset-japanese textlint-rule-preset-jtf-style

設定

  • ~/.textlintrc
{
    "rules" : {
        "preset-japanese": true,
        "preset-jtf-style": true
    }
}

チェック

textlint XXXX.txt

以下のようになりました。

    3:18  error    Line 3 exceeds the maximum line length of 100             preset-japanese/sentence-length
    3:21  ✓ error  %E => %E                                                 preset-japanese/spellcheck-tech-word
    5:11  error    一文に二回以上利用されている助詞 "ば" がみつかりました。  preset-japanese/no-doubled-joshi
   14:5   ✓ error  数値の範囲を示す場合には全角の〜を使用します。            preset-jtf-style/4.2.5.波線(〜)
   27:10  error    一文に二回以上利用されている助詞 "に" がみつかりました。  preset-japanese/no-doubled-joshi
   27:55  error    一つの文で"、"を3つ以上使用しています                     preset-japanese/max-ten
   40:10  error    一文に二回以上利用されている助詞 "に" がみつかりました。  preset-japanese/no-doubled-joshi
   40:42  error    一文に二回以上利用されている助詞 "が" がみつかりました。  preset-japanese/no-doubled-joshi
   44:15  error    一文に二回以上利用されている助詞 "が" がみつかりました。  preset-japanese/no-doubled-joshi
   84:53  error    一つの文で"、"を3つ以上使用しています                     preset-japanese/max-ten
  106:18  error    一文に二回以上利用されている助詞 "が" がみつかりました。  preset-japanese/no-doubled-joshi

✖ 11 problems (11 errors, 0 warnings)
✓ 2 fixable problems.

修正

textlint --fix XXXX.txt
   3:21  ✔   %E => %E                                       preset-japanese/spellcheck-tech-word
  14:5   ✔   数値の範囲を示す場合には全角の〜を使用します。  preset-jtf-style/4.2.5.波線(〜)

✔ Fixed 2 problems

自動修正は難しいようです。

いい文章の要素をまとめれば、文章の品質も自動的にチェックできそうな気もしますね。

参考

GitHub - textlint/textlint: The pluggable natural language linter for text and markdown.

GitHub - textlint-ja/textlint-rule-preset-JTF-style: JTF日本語標準スタイルガイド for textlint.

GitHub - textlint-ja/textlint-rule-preset-japanese: textlint rule preset for Japanese.

頭を良くするには

最近はてなでランキング1位になっていたブログを見てみます。

blog.hatenablog.com

脳を鍛えるには運動しかないとハーバードの教授が言っているようです。

https://www.amazon.co.jp/%E8%84%B3%E3%82%92%E9%8D%9B%E3%81%88%E3%82%8B%E3%81%AB%E3%81%AF%E9%81%8B%E5%8B%95%E3%81%97%E3%81%8B%E3%81%AA%E3%81%84-%E6%9C%80%E6%96%B0%E7%A7%91%E5%AD%A6%E3%81%A7%E3%82%8F%E3%81%8B%E3%81%A3%E3%81%9F%E8%84%B3%E7%B4%B0%E8%83%9E%E3%81%AE%E5%A2%97%E3%82%84%E3%81%97%E6%96%B9-%E3%82%B8%E3%83%A7%E3%83%B3-J-%E3%83%AC%E3%82%A4%E3%83%86%E3%82%A3/dp/4140813539

生まれつき運動しないと気持ち悪い・やってられない性格の人間は脳力が高くなりやすい気はしています。

脳力を鍛える脳トレは以下がよいと思います。

朝ラン

朝ランをするとしないでは、脳力に雲泥の差が出てくると思います。
効果も2~3日続くような気がします。

以下が具体的な効果です

仕事の生産性向上
器の大きさ/ストレス耐性力向上
ストレス低減
ミス削減
脳の容量の増加
記憶力増加
新しい知識を付ける力の向上

朝になんらかの運動になるものをしていない人は生産性が低く、器が小さく、ストレスフルでミスが多く、記憶力が低く、新しい知識を身につける力が低く、保守的になりやすい気はしています。

脳はランニングで鍛えろ!「脳力」を鍛えるとっておきの走り方|Career Supli

ランニングによる脳への5つの効果!走って脳を活性化させよう!

運動と頭を使うことを交互に行う

運動で脳力が上がった後に頭を使うことで脳力向上につながる気がします。

タイミングがよければ、脳が超回復していくのでしょうか。

朝になんらかの運動になるものをすれば、仕事での脳の超回復を発生させることができる気がします。

仕事だけだと、肝心な脳力は下がる一方でストレスは貯まるだけで、脳力は低い人間になってしまいます。

労働時間が長い人に頭がいい人がいないのはそのためかと。

オフィスとジムを一緒にしてみるのもよいかもしれません。

http://jp.wsj.com/articles/SB12616845268056034052504582158340471478120

新しい刺激

新しい刺激になるのは転職や現場を変えたりフリーランスになったり、住む場所を変えたりことです。

周りの環境や人が変わることは大きな刺激になります。

人によっては大きなストレスとなるかもしれませんが、新しい刺激の超回復で脳力は高くなると思います。

ストレスが大きすぎると逆に脳は萎縮して、脳力は下がると思いますが。

勤続XX年×マイホーム×会社員が最も脳力を低くしやすいような気がします。

物事のネガティブな面に執着したり、重箱の隅をつついてばかりになってしまった人はかなり脳が退化しています。

回避よりも新規追求が高い人間は生まれつき、脳力が高くなりやすいと思います。

その点、年を取ると回避が高くなり、新規追求が低くなりやすいので、脳力は上げにくくなっていきやすいかと思います。

オーバーワークを避ける

オーバーワークを避けることで脳の萎縮を防ぎます。

脳を休め、脳が成長する機会を与えます。

マインドフルネス等が流行っているのですが、基本的には脳に休養を与えれば瞑想である必要はないと思います。

インプットに偏らない

よくあるのがインプットに偏ったビジネスマン・管理職です。

インプットは脳としては単に情報を蓄えているだけです。

日本でGoogleAmazonのような企業が出てこないのはここに起因していると思われます。

人間らしさである肝心な生み出す脳力が低くなってしまいます。

情報をインプットして、それ通り意思決定や作業・業務をするのは日本の得意分野ですが、それは機械がすることで、AIに代替されるものです。

生み出す脳力によいインプットとしては日常から距離のあるもの、普段触れない情報です。

日常・自身の業界の情報には脳が慣れきっているため、よい刺激にはなりません。

メシ

脳の原料は基本的にメシです。

何を食べるかは脳の品質を決めます。

ジャンキーなものをよく食べれば、脳も劣化していきます。

ディスプレイを見る時間を少なくする

長くエンジニアをしている人には人間としての基本的な機能が低下している人が多い印象です。

滑舌が悪くなってしまった人、コミュニケーション力が低下してしまった人、顔色が暗くなってしまった人、覇気がなくなってしまった人、イライラしやすい人、意欲が低くなってしまった人

これらは基本的にディスプレイを見ることで脳が萎縮してしまっていることが原因の1つと想定されます。

スマホやPCよりもVRの方がより脳が萎縮する気はしていますので、拡張現実は今後想定されているよりも成長しない分野になると思います。

20年前に比べて、各種統計に大きな変化があったものはもしかしたらスマホ・PCを見る時間が増えたことに起因するかもしれません。

AWS Serverless Application Model (AWS SAM)使ってみる: 変換後のリソースのyamlを見てみる

AWS SAMとはAWSが提供しているサーバーレスフレームワークです。

APIGateway, Lambda, DynamoDBをすべてCloudFormationで作成するのは難易度が高いため、
簡素化したyamlAWS側でCloudFormation標準のものに変換して各リソースを作成してくれます。

なお、作成されるのは標準化されたものですので、自由に柔軟なリソース管理をしたい場合はSAMではなく、
CloudFormationで作成した方がよいと思います。
誰でも、簡単にという点ではSAMの標準にならって作成するのが一番よいと思われます。

詳細をAWS SAMのリソースの指定とAWS側での変換後のリソースで比較してみます。

Transform: AWS::Serverless-2016-10-31

これを指定することでyamlがAWS側でCloudFormation標準の書き方に変更されます
aws cloudformation create-stackコマンドのyamlでは使えません
aws cloudformation deployコマンドのyamlに使用できます

AWS::Serverless::SimpleTable

このぐらいなら、AWS::DynamoDB::Tableで問題ないですね。

To use the more advanced functionality of DynamoDB, use an AWS::DynamoDB::Table resource instead.

だそうですので、最初からAWS::DynamoDB::Tableを使えばいい気がします。

ユーザーが指定するリソース

  Table:
    Type: AWS::Serverless::SimpleTable

AWSで変換後のリソース

  Table:
    Type: AWS::DynamoDB::Table
    Properties:
      ProvisionedThroughput:
        WriteCapacityUnits: 5
        ReadCapacityUnits: 5
      AttributeDefinitions:
      - AttributeName: id
        AttributeType: S
      KeySchema:
      - KeyType: HASH
        AttributeName: id

AWS::Serverless::Function

以下を作成してくれるようです。
これなら、最初からCloudFormationで作成した方が自由で柔軟性がありそうです。
指定したいけど、SAM非対応な指定項目が出そうな気がします。

  • AmazonDynamoDBReadOnlyAccessとAWSLambdaBasicExecutionRoleのロール
  • DeploymentとProdStage
  • swaggerでのRestApi
  • test用とprod用のLambda::Permission
  • Lambda関数

ユーザーが指定するもの

  GetFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: index.get
      Runtime: nodejs6.10
      CodeUri: s3://keiwt-hogeless-api/api.zip
      Policies: AmazonDynamoDBReadOnlyAccess
      Environment:
        Variables:
          TABLE_NAME: !Ref Table
      Events:
        GetResource:
          Type: Api
          Properties:
            Path: /resource/{resourceId}
            Method: get

AWSで変換後のリソース

IAM

  GetFunctionRole:
    Type: AWS::IAM::Role
    Properties:
      ManagedPolicyArns:
      - arn:aws:iam::aws:policy/AmazonDynamoDBReadOnlyAccess
      - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Action:
          - sts:AssumeRole
          Effect: Allow
          Principal:
            Service:
            - lambda.amazonaws.com

ApiGateway

  ServerlessRestApiProdStage:
    Type: AWS::ApiGateway::Stage
    Properties:
      DeploymentId:
        Ref: ServerlessRestApiDeployment77d5a05be2
      RestApiId:
        Ref: ServerlessRestApi
      StageName: Prod

  ServerlessRestApiDeployment77d5a05be2:
    Type: AWS::ApiGateway::Deployment
    Properties:
      RestApiId:
        Ref: ServerlessRestApi
      Description: 'RestApi deployment id: 77d5a05be2514b45c5df870702eac8162155cb25'
      StageName: Stage

  ServerlessRestApi:
    Type: AWS::ApiGateway::RestApi
    Properties:
      Body:
        info:
          version: '1.0'
          title:
            Ref: AWS::StackName
        paths:
          "/resource/{resourceId}":
            put:
              x-amazon-apigateway-integration:
                httpMethod: POST
                type: aws_proxy
                uri:
                  Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${PutFunction.Arn}/invocations
              responses: {}
            get:
              x-amazon-apigateway-integration:
                httpMethod: POST
                type: aws_proxy
                uri:
                  Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GetFunction.Arn}/invocations
              responses: {}
            delete:
              x-amazon-apigateway-integration:
                httpMethod: POST
                type: aws_proxy
                uri:
                  Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${DeleteFunction.Arn}/invocations
              responses: {}
        swagger: '2.0'

Lambda

  GetFunctionGetResourcePermissionTest:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:invokeFunction
      Principal: apigateway.amazonaws.com
      FunctionName:
        Ref: GetFunction
      SourceArn:
        Fn::Sub:
        - arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/resource/{resourceId}
        - __Stage__: "*"
          __ApiId__:
            Ref: ServerlessRestApi

  GetFunctionGetResourcePermissionProd:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:invokeFunction
      Principal: apigateway.amazonaws.com
      FunctionName:
        Ref: GetFunction
      SourceArn:
        Fn::Sub:
        - arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/resource/{resourceId}
        - __Stage__: Prod
          __ApiId__:
            Ref: ServerlessRestApi

  GetFunction:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        S3Bucket: keiwt-hogeless-api
        S3Key: api.zip
      Tags:
      - Value: SAM
        Key: lambda:createdBy
      Environment:
        Variables:
          TABLE_NAME:
            Ref: Table
      Handler: index.get
      Role:
        Fn::GetAtt:
        - GetFunctionRole
        - Arn
      Runtime: nodejs6.10

以下はyaml全体のbeforeとafterです。


example-before.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
  GetFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: index.get
      Runtime: nodejs6.10
      CodeUri: s3://keiwt-hogeless-api/api.zip
      Policies: AmazonDynamoDBReadOnlyAccess
      Environment:
        Variables:
          TABLE_NAME: !Ref Table
      Events:
        GetResource:
          Type: Api
          Properties:
            Path: /resource/{resourceId}
            Method: get

  PutFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: index.put
      Runtime: nodejs6.10
      CodeUri: s3://keiwt-hogeless-api/api.zip
      Policies: AmazonDynamoDBFullAccess
      Environment:
        Variables:
          TABLE_NAME: !Ref Table
      Events:
        PutResource:
          Type: Api
          Properties:
            Path: /resource/{resourceId}
            Method: put

  DeleteFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: index.delete
      Runtime: nodejs6.10
      CodeUri: s3://keiwt-hogeless-api/api.zip
      Policies: AmazonDynamoDBFullAccess
      Environment:
        Variables:
          TABLE_NAME: !Ref Table
      Events:
        DeleteResource:
          Type: Api
          Properties:
            Path: /resource/{resourceId}
            Method: delete

  Table:
    Type: AWS::Serverless::SimpleTable

example-after.yaml

---
AWSTemplateFormatVersion: '2010-09-09'
Resources:
  GetFunctionGetResourcePermissionTest:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:invokeFunction
      Principal: apigateway.amazonaws.com
      FunctionName:
        Ref: GetFunction
      SourceArn:
        Fn::Sub:
        - arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/resource/{resourceId}
        - __Stage__: "*"
          __ApiId__:
            Ref: ServerlessRestApi
  GetFunctionRole:
    Type: AWS::IAM::Role
    Properties:
      ManagedPolicyArns:
      - arn:aws:iam::aws:policy/AmazonDynamoDBReadOnlyAccess
      - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Action:
          - sts:AssumeRole
          Effect: Allow
          Principal:
            Service:
            - lambda.amazonaws.com
  DeleteFunction:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        S3Bucket: keiwt-hogeless-api
        S3Key: api.zip
      Tags:
      - Value: SAM
        Key: lambda:createdBy
      Environment:
        Variables:
          TABLE_NAME:
            Ref: Table
      Handler: index.delete
      Role:
        Fn::GetAtt:
        - DeleteFunctionRole
        - Arn
      Runtime: nodejs6.10
  PutFunction:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        S3Bucket: keiwt-hogeless-api
        S3Key: api.zip
      Tags:
      - Value: SAM
        Key: lambda:createdBy
      Environment:
        Variables:
          TABLE_NAME:
            Ref: Table
      Handler: index.put
      Role:
        Fn::GetAtt:
        - PutFunctionRole
        - Arn
      Runtime: nodejs6.10
  DeleteFunctionRole:
    Type: AWS::IAM::Role
    Properties:
      ManagedPolicyArns:
      - arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess
      - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Action:
          - sts:AssumeRole
          Effect: Allow
          Principal:
            Service:
            - lambda.amazonaws.com
  ServerlessRestApiProdStage:
    Type: AWS::ApiGateway::Stage
    Properties:
      DeploymentId:
        Ref: ServerlessRestApiDeployment77d5a05be2
      RestApiId:
        Ref: ServerlessRestApi
      StageName: Prod
  ServerlessRestApi:
    Type: AWS::ApiGateway::RestApi
    Properties:
      Body:
        info:
          version: '1.0'
          title:
            Ref: AWS::StackName
        paths:
          "/resource/{resourceId}":
            put:
              x-amazon-apigateway-integration:
                httpMethod: POST
                type: aws_proxy
                uri:
                  Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${PutFunction.Arn}/invocations
              responses: {}
            get:
              x-amazon-apigateway-integration:
                httpMethod: POST
                type: aws_proxy
                uri:
                  Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GetFunction.Arn}/invocations
              responses: {}
            delete:
              x-amazon-apigateway-integration:
                httpMethod: POST
                type: aws_proxy
                uri:
                  Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${DeleteFunction.Arn}/invocations
              responses: {}
        swagger: '2.0'
  PutFunctionPutResourcePermissionTest:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:invokeFunction
      Principal: apigateway.amazonaws.com
      FunctionName:
        Ref: PutFunction
      SourceArn:
        Fn::Sub:
        - arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/PUT/resource/{resourceId}
        - __Stage__: "*"
          __ApiId__:
            Ref: ServerlessRestApi
  GetFunction:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        S3Bucket: keiwt-hogeless-api
        S3Key: api.zip
      Tags:
      - Value: SAM
        Key: lambda:createdBy
      Environment:
        Variables:
          TABLE_NAME:
            Ref: Table
      Handler: index.get
      Role:
        Fn::GetAtt:
        - GetFunctionRole
        - Arn
      Runtime: nodejs6.10
  GetFunctionGetResourcePermissionProd:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:invokeFunction
      Principal: apigateway.amazonaws.com
      FunctionName:
        Ref: GetFunction
      SourceArn:
        Fn::Sub:
        - arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/resource/{resourceId}
        - __Stage__: Prod
          __ApiId__:
            Ref: ServerlessRestApi
  ServerlessRestApiDeployment77d5a05be2:
    Type: AWS::ApiGateway::Deployment
    Properties:
      RestApiId:
        Ref: ServerlessRestApi
      Description: 'RestApi deployment id: 77d5a05be2514b45c5df870702eac8162155cb25'
      StageName: Stage
  DeleteFunctionDeleteResourcePermissionProd:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:invokeFunction
      Principal: apigateway.amazonaws.com
      FunctionName:
        Ref: DeleteFunction
      SourceArn:
        Fn::Sub:
        - arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/DELETE/resource/{resourceId}
        - __Stage__: Prod
          __ApiId__:
            Ref: ServerlessRestApi
  DeleteFunctionDeleteResourcePermissionTest:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:invokeFunction
      Principal: apigateway.amazonaws.com
      FunctionName:
        Ref: DeleteFunction
      SourceArn:
        Fn::Sub:
        - arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/DELETE/resource/{resourceId}
        - __Stage__: "*"
          __ApiId__:
            Ref: ServerlessRestApi
  Table:
    Type: AWS::DynamoDB::Table
    Properties:
      ProvisionedThroughput:
        WriteCapacityUnits: 5
        ReadCapacityUnits: 5
      AttributeDefinitions:
      - AttributeName: id
        AttributeType: S
      KeySchema:
      - KeyType: HASH
        AttributeName: id
  PutFunctionPutResourcePermissionProd:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:invokeFunction
      Principal: apigateway.amazonaws.com
      FunctionName:
        Ref: PutFunction
      SourceArn:
        Fn::Sub:
        - arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/PUT/resource/{resourceId}
        - __Stage__: Prod
          __ApiId__:
            Ref: ServerlessRestApi
  PutFunctionRole:
    Type: AWS::IAM::Role
    Properties:
      ManagedPolicyArns:
      - arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess
      - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Action:
          - sts:AssumeRole
          Effect: Allow
          Principal:
            Service:
            - lambda.amazonaws.com

DRYにするか分けるかについて

DRYにするかどうかはよく誤って判断されることが多いと思います。 スタートアップ時には先のことを想定していないケースが多く、サービスの規模が拡大してから問題が発生することが多いかと思います。

そこで、どういうものはDRYにすべきで、どういう場合にきちんと分けるべきかまとめてみます。

アプリケーション内の処理

言うまでもなく、1処理1メソッドにすべきです

同じような処理が複数見られる場合は1箇所に集約します。 ただし、DRYにするのはあくまで処理であって、種類が別のものまでDRYにしてしまうと特定の種類の場合しか不要な処理が他の種類の場合でも無駄に動いてしまうでしょう。 種類毎にアプリケーションの受け口は分けて、各種類の受け口から使用するものは共通にしておくと、後で柔軟に対応できると思います。

アプリケーション(リポジトリ)をまたいでの共通の処理はライブラリとしてアプリケーションとは別のリポジトリにすると良いと思います。 そうするとマイクロサービス化しやすいと思います。

汎用的な処理は自作よりもgem、標準のライブラリを使用するのが良いと思います。

サービスの対象の種類

例えば、不動産であれば、賃貸マンション、新築マンション、新築一戸建て、中古マンション、中古一戸建てなどのことで、飲食業界ではランチや宴会などのことです。

これは基本的にはサービス毎明確に分けて、マイクロサービス化すべきです。

最もやってはいけないのは種類をパラメーターにしたり、フラグで分けることです。
そうしてしまうと、いずれサービスは崩壊するでしょう。
サービスの裏側は種類を見るif分等でぐちゃぐちゃになるでしょう。
また、世の中にリノベーションマンション、部ランチ等の新しい種類が追加された場合にはそれだけ別に分けてしまったり、既存の種類を消したい場合には工数が膨大になり、リスクが高くなります。 つまり、世の中の変化に脆弱なアプリケーションになるでしょう。

インフラ

各サービスで使用する標準のインフラのソースを作成すべきでしょう。 そうすれば、各サービスのインフラ構築が数分で済みます。 また、各サービスで共通の変更が必要な場合はソースを修正して、各サービスに適応するだけで済みます。

それぞれのサービスがそれぞれインフラを構築してしまっている場合には各サービスで調査が必要になることもあるかもしれません。

一番望ましいのはサーバーレス構成のインフラのソースを作成するのみで、AmazonGoogleにインフラはお任せすることだと思います。

ドメイン

ブランド戦略によりますが、基本的に各サービスは企業orサービスドメインサブドメインになると思います。 マイクロサービスの場合も同様ですがサービスの実態に合わせるのが良いかと思います。 ドメインをすべて同じにしてしまうとロードバランサの障害で全サービスが停止しないように不必要に良いインスタンスにして、余分なコストが発生するでしょう。

サービス名称

例えば、WantedlyがサービスのWantedlyをWantedly Visitに変更したように、スタートアップ時には会社名がサービス名になっていることが多いと思います。

また、Wantedlyというブランドは人材系のサービスに自身を限定してしまっている印象があります。

ブランドやサービス名の変更は大きな影響があるため、最初から先を想定する方が良いかと思います。

ブランドの戦略にもよるのですが、ブランド名を分けて、ブランド価値低下の影響範囲を少なくするのか、既存の確立されたブランドのカチを新規サービスでも享受するのかは判断が分かれるところだと思います。

どちらにしろ、企業名やサービス名はサービスの領域を限定しない抽象的な方がよいと思います。

その点、AmazonGoogleはよく考えられたものです。 AmazonならIT分野に進出してAmazonWebServices、Googleなら、ホームデバイスGoogle Home。企業名が抽象的なためにどの分野にも進出しやすいと思います。

The parameter Origin DomainName does not refer to a valid S3 bucket.

CloudFormationでサーバーレスアプリケーションを作る際にCloudFrontからWebsiteの設定をしたS3にトラフィックを流すと思います。 その際にタイトルのエラーが発生することがあります。

以下のような設定の場合です

        Origins:
          - DomainName: !Sub
              - ${Domain}.s3-website-${AWS::Region}.amazonaws.com
              - { Domain: !Ref Domain }
             Id: !Ref OriginId
             S3OriginConfig:
               OriginAccessIdentity: !Ref OriginAccessIdentity

結論としてはWebsiteのドメイン名はS3OriginConfigの対象ではないので、以下のようにCustomOriginConfigを使用すれば解決します。

CustomOrigin is a property of the Amazon CloudFront Origin property that describes an HTTP server.

docs.aws.amazon.com

        Origins:
          - DomainName: !Sub
              - ${Domain}.s3-website-${AWS::Region}.amazonaws.com
              - { Domain: !Ref Domain }
            Id: !Ref OriginId
            CustomOriginConfig:
              OriginProtocolPolicy: http-only
              HTTPPort: 80

間違えやすい原因としてはCustomOriginConfigのキー名かと。 S3のWebsite用ドメインなので、ついS3OriginConfigに設定しそうになってしまいます。

AWSにはCustomOriginConfigではなく、以下の3通りのいずれかにして欲しいものです。

ドキュメント通りにHttpServerOriginConfigにする
CustomOriginConfigとは別にS3WebsiteOriginConfigを作る
CustomOriginConfigではなく、WebsiteOriginConfigを作る

WEBにおいて明確しておいた方がよい方針・指針

WEBにおいては方針や指針をちゃんと決めておけば、負の遺産の発生を防ぐことができます。

方針・指針が特に明確に決まっていない、または曖昧なまま事業を開始するといずれ負の遺産が膨大になったり、スピードが出ない開発になったり、開発プロセスが増えていってしまいます。

結果として作り直した方がよい状況が発生してしまいます。

設計等に関しては基本的に最初からきちんと分けて、疎にしておくことが重要です。

特に開発初期の中心メンバーが方針・指針を明確に決めるべきだと思います。

そこで、こうしておいた方がよい方針・指針をご紹介します。

開発標準

静的解析

明確にしない場合

様々な書き方が散乱
後の開発者の混乱の元となるコードが多数
XXX派論争による無駄な時間の発生
ソースの複雑度が高くなり、膨大な負の遺産が発生する
1コード1機能で明確にディレクトリを分けて、実装では使うだけにすることができなくなる
似たようなソースが散乱するようになる
ソースのコピペが多発する
ソースの量が膨大になる
パフォーマンス劣化
メモリを無駄に消費
DDOSに対して、脆弱になる
単体のspecが巨大になりやすく、テスト工数が増大する

保存時に動かせるもの

行末のスペースを削除
余分なタブやスペースの削除
ファイルの最終行の改行

保存時に何も動かしていない場合

無駄な大量の空白(余分なタブやスペース)が散乱
行末の改行がないwarning
保存時に自動的にスクリプトを動かすと空白の大量のdiff
無駄にリソースを使う
リポジトリが大きくなればなるほど、余分なタブやスペースの総バイト数が増加

技術要素の選定・採用基準

先を見る

先を見ていない場合

流行っているからという理由だけで飛びつく
後にオワコンになり、学習コストが高く、デメリットしかなくなる
その技術要素を採用する理由があるかをゼロベースで考えることができる人がいない

ツール・フレームワーク等をできるだけ自作しない

不要に自作する場合

オペレーションに問題がある
変えるべきはほとんどの場合、自社・自分自身であり、ツール側ではないことを認識していない
膨大で保守性が低い、自作デプロイスクリプト・ツール
各種バージョンアップをしにくくなる(自作フレームワークの場合)
バージョンアップができないことによる脆弱性・情報漏洩リスクの上昇
変にカスタマイズした結果作者の意図とは異なる使い方をされるリスクの増大
秘伝のXXXXシリーズで後の開発者のコストが高くなる
個人やコミュニティが開発したものを使用している場合メンテされなくなる
プロビジョニング用サーバーの保守運用が発生する
CI用サーバーの保守運用が発生する
保守運用コストの増大

用語・名称マスター

英語にする

英語にしない場合

マルチバイト起因の脆弱性
日本語や英語での似たような表現が散乱する

省略語

明確にしていない場合

様々な省略語が散乱する

連続した数値に意味を持たせない

0012→A
0013→B

連続した数値に意味を持たせる場合

直感的なソースの理解を妨げてしまう

同じ対象の名称を統一する

同じ対象の名称を統一しない場合

同じもので、異なった名称が散乱する

※明確に用語・名称の定義をしていないことが原因です


ディレクトリ分けの方針・各ディレクトリの役割の方針

実装時にどれを使うか

実装時にどれを使うかの方針が明確ではない場合

毎回機能の使用ではなく、実装が必要になる
工数が多くなる
人数が必要になる
バグが発生しやすくなる
テスト工数の増大
似たような処理が散乱するようになっていく
※リポジトリのサイズが大きくなる
レビュー時間が長くなる
出戻りが発生する

キャッシュの方針

明確ではない場合

キャッシュに接続できない場合にアプリケーションが機能しない
いろんな所でキャッシュされてしまう

リポジトリを分ける方針

明確ではない場合

リポジトリが巨大になる
デプロイ時間が長くなる
インフラがどんぶり勘定になる

各サーバーの役割

1サーバー1機能ではない場合(機能が共存している場合)

※フロント、管理画面、バッチ、API、BFF API

フロントの問題で他の機能に影響が出る
管理画面の問題で他の機能に影響が出る
バッチの問題で他の機能に影響が出る
APIの問題で他の機能に影響が出る
BFF APIの問題で他の機能に影響が出る

パラメーターの複数対応

複数対応していない場合

APIに必要以上にアクセスしてしまうリスクが高くなる
※複数必要になった段階で改修しない場合

RDB設計

カラム設計

アプリケーション側での処理を考慮できていない場合

SQLでできることをアプリケーション側で計算することになる

データの特性を考えていない場合





汎用的にするか目的に特化したものにするか

明確にしていない場合

似たような処理が散乱する
他の目的に他の目的が混ざってスパゲティコードになる

どこをDRYでどこを疎にするか

明確ではない場合

モノリシックでDRYなインフラができる

バリデーションの方針

明確ではない場合

同じ値を複数回バリデーションして無駄に処理をしてしまう

データの存在の担保の方針

明確ではない場合

キーの存在確認が散乱して、無駄に処理をしてしまう

DB(SQL等)/API/アプリケーションでやることの方針

明確ではない場合

アプリケーションでデータを取得して表示するだけにならない
アプリケーション側のコードが複雑になる

DynamoDBのベスト・プラクティス

事前に必要な知識

リレーショナルではない

アプリケーション側でデータの整合性を担保する

キャパシティーユニット

4KBを1秒あたり1回の強力な整合性のある読み込み
or
4KBを1秒あたり2回の結果整合性のある読み込み

制限

http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Limits.html

パーティション

10GB
3000ReadCapacityUnits
1000WriteCapacityUnits

項目サイズ

400KB

項目数

制限なし

裏側の動作

データ

複数のサーバーに配置する

パーティション

  • 作成時
( readCapacityUnits / 3,000 ) + ( writeCapacityUnits / 1,000 ) = initialPartitions (rounded up)
read capacity units / partitions = read capacity units per partition
write capacity units / partitions = write capacity units per partition

パーティションが増えるほどスループットが落ちる

  • 10GBオーバー時
超えたパーティションのみを2つに分割する

※分割されたパーティションは半分のスループットになる

スループットの変更時

  • 対応できない場合
パーティションを2倍にする

パーティションが増えるほどスループットが落ちる

  • 減らした場合
パーティションの数を減らさない

※最初から大きめにしない

スループットを超えた場合

ProvisionedThroughputExceeded例外が発生する

※再試行 or UpdateTable(スループットを増やす)する

http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Programming.Errors.html#Programming.Errors.RetryAndBackoff

クエリ

http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/QueryAndScan.html

Query

プライマリキー or セカンダリインデックスを使用

Scan

テーブル or セカンダリインデックスのすべての項目の読み込み
※一度に 1 つのパーティションしか読み込めない

結果整合性のある読み込みがデフォルトで実行
1024KB / 4KB / 2 = 256の読み込みオペレーション

並列スキャン

テーブル or セカンダリインデックスを複数のセグメントに論理的に分割して、
複数のアプリケーションワーカーがセグメントに対して並列スキャン
  • Segments
特定のワーカーがスキャンするセグメント
  • TotalSegments
並列スキャンの対象となるセグメントの合計数

※アプリケーションで使用されるワーカーの数と同じにする

ローカルセカンダリインデックス

http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/LSI.html

テーブルと同じパーティションキーと異なるソートキーで構成されるインデックス
local secondary indexを作成してQuery or Scan
非キー属性に頻繁にアクセスする場合に設定する
書き込みや更新が多数になる場合はKEYS_ONLYを射影することでサイズを最小限にする

※インデックスに射影されていない属性も取り出せる
※テーブルの一部またはすべての属性がコピーされる
※リクエストした属性が射影されていない場合はテーブルから項目全体を読み込む ※テーブル毎に最大5つ
※最大10GB
※ストレージとプロビジョニング済みのスループットを消費する

グローバルセカンダリインデックス

http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/GSI.html

テーブルのプライマリキーとは異なるプライマリキーによって構成されるインデックス
クエリではベーステーブルから属性をフェッチできません
非キー属性にアクセスすることはできません
キー値は一意である必要がありません
テーブルのパーティションキーとソートキーは必ずインデックスに射影されます

テーブルのベストプラクティス

パーティションキー

個別の値が多数含まれ、できるだけランダムかつ均一に値がリクエストされるようにする
2014-07-09.1
...
2014-07-09.200

急激に増大するキャパシティーは控えめに使用する

データアップロード時に書き込みアクティビティを分散する

パーティションキーとレンジキーの組み合わせ毎にアップロードして分散する

パーティションキーAとレンジキーAのアップロード
パーティションキーAとレンジキーBのアップロード
パーティションキーBとレンジキーAのアップロード
パーティションキーBとレンジキーBのアップロード

※できるだけ多くのサーバーに分散したアップロードをする

時系列データへのアクセスパターンを理解する

すべての項目を単一のテーブルに格納せずに月単位または週単位のテーブルにする
古い項目はS3にバックアップして、テーブル毎削除する

人気の高い項目をキャッシュに格納する

まずキャッシュを見て、なければ初めてDynanoDBに問い合わせる

プロビジョニングされたスループットを調整するときにワークロードの均一性を考慮する

バルクロード時に必要なスループットと通常のスループットは合わせる。

※バルクロード時だけ、スループットを上げて、後で下げるとパーティションが2倍に増えて、スループットが悪化する可能性大

大規模環境でのアプリケーションのテスト

データが増えて、パーティションが増え、スループットが悪化したときのことを考えておく
高いスループット設定をしてから、下げると同じ状況を作り出せる

スループットに対するストレージの比率は大規模と同じようにする
※そもそもパーティション当たり10GBを超えないようにする


項目のベストプラクティス

大規模な設定属性の代わりに 1 対多のテーブルを使用する

なんでも1つのテーブルに格納しない

パーティションキーはID等にする

複数テーブルの使用による多様なアクセスパターンのサポート

表示頻度・アクセス頻度によってテーブルを分ける

パーティションキーはID等にする

大量の属性値を圧縮する

長い文字列は圧縮してから格納する

※圧縮しても400KB超える可能性がある場合には最初からS3に格納する

Amazon S3 に大量の属性値を格納する

画像(DynamoDBにはURL)
長い文字列(DynamoDBにはS3のオブジェクトID)

※S3のオブジェクトIDは最大1024バイト
※&等非推奨の文字はS3のオブジェクトIDに使用しない

http://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/UsingMetadata.html

大量の属性を複数の項目に分割する

XxxxChunksテーブルの作成
親テーブルにはChunkCount,ChunkVersionを置いておく
XxxxChunksテーブルのIDは${親ID}#${ChunkVersion}#${ChunkSequenceNo}

※BatchGetItem・BatchWriteItemを使用する


クエリとスキャンのベストプラクティス

読み込みアクティビティの急激な増大の回避

Scan(Query)時にLimitパラメータを指定して読み込みオペレーションの数を減らす

重要なトラフィック用と記録用にテーブルを分けて1時間毎に分担する
※重要なものをそうでないものを分ける

ミッションクリティカルなテーブルとシャドウテーブルに同時に書き込んでおく
※重要なリクエストと重要でないリクエストをテーブルを分けて対応する

並列スキャンの利用

並列スキャンで高速化

※あくまでデータサイズが大きく、読み込みスループットが十分にある場合で高速化したい場合です
スループットが多量に消費されるので要注意

並列スキャンが適している場合

テーブルのサイズが20GB以上
プロビジョニングされている読み込みスループットが完全に使用されていない
シーケンシャルScanオペレーションでは遅すぎる

TotalSegments の選択

1セグメント/2GBにしてみる

※アプリケーションのワーカー数とセグメント数は合わせる

  • スループットを消費しないうちにScanリクエストが制限される場合
TotalSegmentsの値を増やす
  • Scan リクエストによって、プロビジョニングされたスループットが必要以上に消費される場合
TotalSegmentsの値を減らします

ローカルセカンダリインデックスのベストプラクティス

インデックスの使用は控えめにする

頻繁にクエリを行わない属性では、local secondary index を作成しない

※使用されていないインデックスは、ストレージおよび I/O コストを増大させる

頻繁に更新されず、多数の属性でクエリが行われるテーブルは複数のインデックスを作成する

※readが多く行われ、書き込みは頻繁には発生しないテーブル

多量の書き込みアクティビティが発生するテーブルにはインデックスを設定しない

インデックスが頻繁に変わるため、高コストになってしまう

※インデックスを設定したいものはデータを別のテーブルにコピーする
※インデックスを設定するなら、KEYS_ONLYでもよい

インデックスのサイズは可能な限り小さくする

インデックスが小さいほど高パフォーマンス
頻繁にリクエストを行う属性だけを射影する

ソートキーが少数のデータにだけ存在する項目にインデックスを設定する

writeがreadより多い場合

  • インデックスのエントリーサイズを1KB以下にする
KEYS_ONLYにする

※追加コストが発生しない

  • ALL
異なるソートキーによってテーブルを並べ替えるようにする場合のみ指定する

項目コレクションの拡張の監視

テーブル and インデックスで同じパーティションキーを持つすべての項目が10GBを超えないか

※10GB超えるとItemCollectionSizeLimitExceededExceptionが発生してしまう

http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/LSI.html#LSI.ItemCollections.SizeLimit

グローバルセカンダリインデックス のベストプラクティス

ワークロードが均一になるようにキーを選択する

値の数が多いパーティションキーとソートキーを選択します

グローバルセカンダリインデックス を使用したすばやい検索

小さいグローバルセカンダリインデックスにする

※インデックスに射影するテーブル属性の数が少ないものにする

結果整合性のある読み込みレプリカを作成する

テーブルに書き込んでからそのデータがインデックスに反映されるまでの間に短い伝達遅延がある
スループットの高低で、複数のグローバルセカンダリインデックスを作成する

http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/specifying-conditions.html

参考

docs.aws.amazon.com

http://aws.typepad.com/sajp/2016/12/should-your-dynamodb-table-be-normalized-or-denormalized.html

RDS(RDBMS)とDynamoDB(NoSQL)の使い分けについて

日本ではRDBMSが多用されている件についてですが、よくあるアンチパターンが以下です。

DynamoDB(NoSQL)が最適の場合にRDS(RDBMS)を使っている

何も考えずにRDS(RDBMS)を使用
※モバイル, ウェブ, ゲーム, アドテク, IoTでもRDSを使用
↓
サービスがスケール
↓
アーキテクチャ上の課題が発生する。
※パフォーマンスのボトルネック
※スケーラビリティ
※メンテナンス困難な壮大なSQL
※チューニング工数の増大
※障害が多発
※DBを単一障害点になる
※DBインスタンスが死亡
※スケールアップによる費用増大

DBインスタンスを分けていない

複数アプリケーションのテーブルが同じDBインスタンスに同居しているため、
コネクションプール枯渇の問題、タイムアウトリスクの向上、パフォーマンスの問題が発生する

lambdaからRDSの使用を試みている

リクエストが増える
↓
コネクションが増える
↓
レイテンシの増大

RDBMSの厳密なスキーマ定義の利点を使えていない

テーブルのデータにビジネスルールを適用できておらず、現実の世界でおかしいデータがDBに存在することになる

きちんとAWSの資料に目を通している人が少ないのが上記事象が発生する原因かと思います。

AWSの資料を見て、どういう場合に何を使うのかを見てみます。

まず、それぞれの特徴を見ます。

特徴

項目 RDBMS NoSQL
データモデル テーブル、列、index、テーブル間の関係などはスキーマによって厳密に定義 値、列セット、半構造化 JSON等柔軟。データ取得にはパーティションキー
ACIDプロパティ ACIDをサポートする。アーキテクチャ上の課題が生じやすい。 完全には保証しないが、代わりにRDBMSアーキテクチャ上の課題が生じた場合に、パフォーマンスのボトルネック、スケーラビリティ、複雑な運用、管理コストとサポートコストの上昇といった問題を解消
パフォーマンス クエリ、インデックス、テーブル構造の最適化が必要 チューニング不要
拡張性 スケールアップ(AWS使用料が爆増する) スケールアウト(AWS使用料は変わらない)
API SQL(ORM使用する際はオブジェクトベース) オブジェクトベースの API

ACID

トランザクションが完全に実行されるか一切実行されないか
トランザクションが実行されたら、データが必ずデータベーススキーマに従う
同時発生したトランザクションが相互に独立して実行される
異常発生前の最後の状態まで復旧できる

※ACID(不可分性、一貫性、独立性、永続性)

NoSQLの種類

列指向

データ行ではなく、データ列の読み書きに最適化
必要な総ディスク I/O と、ディスクからロードする必要のあるデータ量が大幅に減少
分析クエリに向いている

Redshift, EMR

ドキュメント指向

JSON等で保存
データを柔軟に整理、保存でき、他の機能を使用する際に必要なストレージを減らせる

パフォーマンス、スケーラビリティを求めるものに向いている
モバイル、ウェブ、ゲーム、アドテク、IOTやその他多くのアプリケーションに向いている

グラフ指向

頂点と辺と呼ばれる有向リンクが格納
SQLでも、NoSQLでも
Amazon DynamoDB and Titan

https://aws.amazon.com/jp/blogs/big-data/building-a-graph-database-on-aws-using-amazon-dynamodb-and-titan/

メモリ内キー値ストア

読み取りの負荷が大きいアプリケーションワークロード
(SNS、ゲーム、メディアの共有、Q&A ポータル
や計算量の多いワークロード(レコメンデーションエンジン))に向いている

キャッシュ, セッション, pub/sub, leaderboards(redis)

Amazon ElastiCache

RDSのユースケース

NoSQLのユースケース以外のもの
パフォーマンスを無視しても、データの整合性を求めるもの
銀行の口座等
※RDBMSのスキーマ定義をビジネス・ルールと完全に一致させることができる場合

データの整合性について

RDBMSではアプリケーションでもバリデーションとDB側でのバリデーションが2重に発生します。
スキーマの定義が完璧でないとRDBMSを使用している利点はなくなります。

余分なオブジェクトについて

RDBMSでORMを使用する場合はオブジェクトを別途作成するため、オーバーヘッドが大きいです
また、最終的にはハッシュとなり、viewに渡されるので、NoSQLが代替になる可能性が高いです

allocated memory by gem
-----------------------------------
    685900  activemodel-5.0.1
    580514  activerecord-5.0.1
     71540  activesupport-5.0.1
     12288  mysql2-0.4.5
      8832  2.3.3/lib
      8192  ruby-memory/app
      3633  arel-7.1.4
      2432  concurrent-ruby-1.0.4

参考

http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/SQLtoNoSQL.html

https://aws.amazon.com/jp/nosql/?nc1=h_ls

http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Introduction.html

https://aws.amazon.com/jp/relational-database/

https://aws.amazon.com/jp/nosql/columnar/

https://aws.amazon.com/jp/nosql/document/

https://aws.amazon.com/jp/nosql/graph/

https://aws.amazon.com/jp/nosql/key-value/

Ruby2.3.3での省メモリ化をやってみる

rubyはよく、メモリリークが発生するとか、メモリを爆食いするとか言われています。
明示的にnilを入れて、メモリを開放したり、GC.startしたりして対応する人が多いようです。 そこで、実際どうなのかruby2.3.3で確認してみます。

個人的にはrubyは書きやすくて好きですが、あまり使うべきではないかと思います。
使うならgoですね〜

https://www.techempower.com/benchmarks/#section=data-r13&hw=cl&test=query

環境

$ ruby -v
ruby 2.3.3p222 (2016-11-21 revision 56859) [x86_64-darwin16]
$ rails -v
Rails 5.0.1

準備

$ rails new ruby-memory -T -B -d mysql
$ bundle install --path=vendor/bundle
$ bundle exec rails g scaffold user a:string b:string c:string d: string e: string f: string g: string h: string i: string j: string k: string l: string m: string n: string o: string p: string q: string r: string s: string t: string u: string v: string w: string x: string y: string z: string
$ mysql.server start
※database.ymlのuserとpasswordを調整
$ bundle exec rails db:create
$ bundle exec rails db:migrate
※seeds作成
$ bundle exec rails db:seed 

db/seeds.rb

User.create([
  {
    a: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
    b: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
    c: 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc',
    d: 'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd',
    e: 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee',
    f: 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
    g: 'gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg',
    h: 'hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh',
    i: 'iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii',
    j: 'jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj',
    k: 'kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk',
    l: 'llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll',
    m: 'mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm',
    n: 'nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn',
    o: 'oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo',
    p: 'pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp',
    q: 'qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq',
    r: 'rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr',
    s: 'ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss',
    t: 'tttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt',
    u: 'uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu',
    v: 'vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv',
    w: 'wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww',
    x: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
    y: 'yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy',
    z: 'zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz',
  },
  ....
])

ソース1

require 'objspace'

class UsersController < ApplicationController
  before_action :set_user, only: [:show, :edit, :update, :destroy]

  # GET /users
  # GET /users.json
  def index
    p ObjectSpace.memsize_of_all
    p `ps -o rss= -p #{Process.pid}`.to_i

    @users = User.all
    @users.each do |u|
      p ObjectSpace.memsize_of_all
      p `ps -o rss= -p #{Process.pid}`.to_i
    end
    p ObjectSpace.memsize_of_all
    p `ps -o rss= -p #{Process.pid}`.to_i
  end
  ...
end

結果(1回アクセス)

76413584
107100
  User Load (1.1ms)  SELECT `users`.* FROM `users`
77939342
108876
77942258
108876
...
78041722
108936
78044638
108936

結果(f5長押し)

133268
99497606
133268
99518745
133280
99587787
133300

大量アクセスするとrubyのメモリ解放が追いつかないのか。

ソース2

require 'objspace'

class UsersController < ApplicationController
  before_action :set_user, only: [:show, :edit, :update, :destroy]

  # GET /users
  # GET /users.json
  def index
    p ObjectSpace.memsize_of_all
    p `ps -o rss= -p #{Process.pid}`.to_i

    @users = User.all
    @users.each do |u|
      u = nil
      p ObjectSpace.memsize_of_all
      p `ps -o rss= -p #{Process.pid}`.to_i
    end
    p ObjectSpace.memsize_of_all
    p `ps -o rss= -p #{Process.pid}`.to_i
  end
  ...
end

結果(1回アクセス)

69259461
110160
  User Load (0.8ms)  SELECT `users`.* FROM `users`
70789587
110332
70792503
110332
70795419
110332
...
70891687
110340
70894603
110340

結果(f5長押し)

89480104
136112
89483020
136112
89485936
136112
89488852
136112

何もしないよりかは、ましなのかもしれません。

ソース3

require 'objspace'

class UsersController < ApplicationController
  before_action :set_user, only: [:show, :edit, :update, :destroy]

  # GET /users
  # GET /users.json
  def index
    p ObjectSpace.memsize_of_all
    p `ps -o rss= -p #{Process.pid}`.to_i

    @users = User.all
    @users.each do |u|
      GC.start
      p ObjectSpace.memsize_of_all
      p `ps -o rss= -p #{Process.pid}`.to_i
    end
    p ObjectSpace.memsize_of_all
    p `ps -o rss= -p #{Process.pid}`.to_i
  end
  ...
end

結果(1回アクセス)

74074939
110832
  User Load (0.7ms)  SELECT `users`.* FROM `users`
66737741
110880
66737141
110880
66737141
110880
66737141
110880
...
66737141
110880
66740057
110880

結果(f5長押し)

69000597
135268
69000597
135268
69000597
135268
69003833
135268

結果としては、GC.startすると一番メモリ使用量は少なくなりました。

rubyGCのことまで考えないといけないので、もはや欠陥スクリプト言語ですかね。

memory関連のgem

https://github.com/SamSaffron/memory_profiler

https://github.com/tagomoris/ruby-memory-usage-profiler

参考

http://blog.inouetakuya.info/entry/2015/07/26/204320

http://2012.rubyworld-conf.org/files/slides/rwc2012_A-5.pdf

rbenvの使い方をまとめてみる

rbenvの使い方で以下をすべてまとめたサイトがないのでまとめてみる。

初回にすべきこと
バージョン切替時
rbenv install -lで最新のバージョン(2.3.3)が表示されるようにする

rbenv

初回

brew install rbenv
rbenv init
echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
source ~/.bash_profile

upgrade

brew upgrade rbenv ruby-build
cd ~/.rbenv
git pull
cd ~/.rbenv/plugins/ruby-build
git pull

install

rbenv install -l
rbenv install 2.3.3
rbenv global 2.3.3
rbenv rehash
gem update --system
gem install bundler
gem install rails

参考

https://github.com/rbenv/rbenv

dockerでローカルにAmazonLinuxの環境構築してみる

手順

  • Authenticate your Docker client to the Amazon Linux container image Amazon ECR registry.
aws ecr get-login --region ap-northeast-1 --registry-ids 137112412989
  • Authenticate your Docker CLI to the registry
docker login -u AWS -p password -e none https://137112412989.dkr.ecr.ap-northeast-1.amazonaws.com
パスワード入力
  • You can list the images within the Amazon Linux repository
aws ecr list-images --region ap-northeast-1 --registry-id 137112412989 --repository-name amazonlinux
  • Pull the Amazon Linux container image using the docker pull command
docker pull 137112412989.dkr.ecr.ap-northeast-1.amazonaws.com/amazonlinux:latest
  • pullしたイメージの確認
docker images
  • Run the container locally.
docker run -it 137112412989.dkr.ecr.ap-northeast-1.amazonaws.com/amazonlinux:latest /bin/bash

bash-4.2# cat /etc/os-release 
NAME="Amazon Linux AMI"
VERSION="2016.09"

exit

※docker execだとexitしても停止されません

  • もう一回ログインしたい場合
docker ps
docker ps -a
docker start ${CONTAINER_ID}
docker attach ${CONTAINER_ID}

いろいろコマンドがないようですが、AWSとしてはミニマムで用意されたようなので、自分でもろもろ入れる必要があります。

参考

http://docs.aws.amazon.com/AmazonECR/latest/userguide/amazon_linux_container_image.html

https://forums.aws.amazon.com/thread.jspa?threadID=242790

CloudFormationで複数アプリケーションでExport名を一意にする

For each AWS account, Export names must be unique within a region.

ということなので、Export名は動的にしないと2つ目のアプリケーションで同じテンプレートを使う場合にエラーになってしまいます。

そこで、Export名を動的にする方法をご紹介致します。

Export名を動的にする

  • スタック生成
  IAMStack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: !Sub
        - ${S3TemplateURL}/IAM.yaml
        - { S3TemplateURL: !Ref S3TemplateURL }
      TimeoutInMinutes: 5
      Parameters:
        AppName: !Ref AppName
  • Output
Parameters:
  AppName:
    Type: String
....
Outputs:
  FlowLogsRoleArn:
    Value: !GetAtt FlowLogsRole.Arn
    Export:
      Name: !Sub
        - ${AppName}FlowLogsRoleArn
        - { AppName: !Ref AppName }
  • Import
  VPCFlowLog:
    Type: AWS::EC2::FlowLog
    Properties:
      DeliverLogsPermissionArn:
        Fn::ImportValue: !Sub
        - ${AppName}FlowLogsRoleArn
        - { AppName: !Ref AppName }
      LogGroupName: !Sub VPCFlowLogsGroup-${AWS::StackName}
      ResourceId: !Ref VPC
      ResourceType: VPC
      TrafficType: ALL

これでアプリケーション毎にExport名がuniqueになります。

コツとしては一番親のcreate-stack時に渡したパラメーターはExportせずにパラメーターとして子スタックに渡すことです。
そして、各スタックで生成されたものはExportします。

配列に対して、指定する場合はインデントを注意してください。以下のように指定すれば成功します。

      Certificates:
        - CertificateArn:
            Fn::ImportValue: !Sub
              - ${AppName}AcmCertificateArn
              - { AppName: !Ref AppName }

Outputs - AWS CloudFormation