頭を良くするには

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

blog.hatenablog.com

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

運動はしたければすればいいし、したくなければしなくていいものです。

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

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

朝ラン

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

以下が具体的な効果です

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

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

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

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

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

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

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

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

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

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

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

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

新しい刺激

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

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

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

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

オーバーワークは禁物です。

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

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

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

オーバーワークを避ける

インプットに偏らない

メシ

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

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

CloudFormationでAPIGatewayのCORSを有効にするには

サーバーレスアーキテクチャの実現にはEC2やECSを使用せずに、S3上のJSからApiGatewayにアクセスできるないといけません。 するとおのずと、ApiGatewayに作成したAPIにCORSを有効にする必要があります。

AWSを使うなら、ちゃんとCloudFormationを使ってインフラをソースで管理するのがよいと思います。
本番のアプリケーションのソースをリビジョン管理せずに直接修正しないのと同じです。
似たような仕組みを作るのに数ヶ月ではなく、10分程度で作れるので、手間がかからず、効率的です。
インフラのリファクタや刷新も楽にできます。

ApiGatewayにCORSを設定していきます。

CORSを有効にする際に必要な設定

コンソールから設定する場合

コンソールから設定する場合に表示される内容

Create OPTIONS method
Add 200 Method Response with Empty Response Model to OPTIONS method
Add Mock Integration to OPTIONS method
Add 200 Integration Response to OPTIONS method
Add Access-Control-Allow-Headers, Access-Control-Allow-Methods, Access-Control-Allow-Origin Method Response Headers to OPTIONS method
Add Access-Control-Allow-Headers, Access-Control-Allow-Methods, Access-Control-Allow-Origin Integration Response Header Mappings to OPTIONS method
Add Access-Control-Allow-Origin Method Response Header to POST method
Add Access-Control-Allow-Origin Integration Response Header Mapping to POST method
Add Access-Control-Allow-Origin Method Response Header to GET method
Add Access-Control-Allow-Origin Integration Response Header Mapping to GET method

Access-Control-Expose-Headers(カンマ区切りの許可するブラウザ), Access-Control-Max-Age(preflightリクエストのキャッシュ秒), Access-Control-Allow-Credentials(credentialsでのアクセス許可をする場合はtrue)はオプションです。

設定される内容

  • OPTIONSメソッド
Mock Endpoint
  • Method Request
  • Integration Request
Type: MOCK

Request body paththrough
→When no template matches the request Content-Type header

Content-Type: application/json
  • Integration Response
Method response status: 200
Default mapping: true

Header Mappings
Access-Control-Allow-Headers: 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'
Access-Control-Allow-Methods: 'POST,GET,OPTIONS'
Access-Control-Allow-Origin: '*'

Content-Type: application/json

Output passthrough: Yes
  • Method Response
HTTP Status: 200

Response Headers for 200
Access-Control-Allow-Headers
Access-Control-Allow-Methods
Access-Control-Allow-Origin

Response Body for 200
Content type: application/json
Models: Empty

上記を実現するyaml

  ${path}OptionsMethod:
    Type: AWS::ApiGateway::Method
    Properties:
      AuthorizationType: NONE
      HttpMethod: OPTIONS
      ResourceId:
        !Ref ${path}Resource
      RestApiId:
        !ImportValue RestApi
      Integration:
        Type: MOCK
        PassthroughBehavior: WHEN_NO_TEMPLATES
        RequestTemplates:
          application/json: "{\"statusCode\" : 200}"
        IntegrationResponses:
          - StatusCode: 200
            ResponseTemplates:
              application/json: "$input.body"
            ResponseParameters:
              method.response.header.Access-Control-Allow-Methods: "'GET,POST,OPTIONS'"
              method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
              method.response.header.Access-Control-Allow-Origin: !Sub
                - "'http://${Domain}'"
                - { Domain: !ImportValue Domain }
      MethodResponses:
        - ResponseParameters:
            method.response.header.Access-Control-Allow-Methods: true
            method.response.header.Access-Control-Allow-Headers: true
            method.response.header.Access-Control-Allow-Origin: true
          StatusCode: 200

※${path}はAPIのOPTIONSメソッドを追加する対象のパスに変換します
※RestApiはAWS::ApiGateway::RestApiを事前に作成して、Exportしておきます
※${path}ResourceはAWS::ApiGateway::Resourceを事前に作成して、Exportしておきます

実行結果

OPTIONSメソッド

  • Response Body
{}
  • Response Headers
{
  "Access-Control-Allow-Origin":"http://${Domain}",
  "Access-Control-Allow-Methods":"GET,POST,OPTIONS",
  "Access-Control-Allow-Headers":
    "Content-Type,
    X-Amz-Date,
    Authorization,
    X-Api-Key,X-Amz-Security-Token",
  "Content-Type":"application/json"
}

GETメソッド

  • Response Headers
{
  "Access-Control-Allow-Origin":"http://${Domain}",
  "Access-Control-Allow-Methods":"GET,OPTIONS",
  "X-Amzn-Trace-Id":"Root=1-111111111111111111",
  "Access-Control-Allow-Headers":
    "Content-Type,
    X-Amz-Date,
    Authorization,
    X-Api-Key,
    X-Amz-Security-Token",
  "Content-Type":"application/json"
}

Rails5のAPIモードを試してみる

準備

$ ruby -v
ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-darwin13]
$ rails -v
Rails 5.0.0.1

new && bundle install

rails new hoge --api -T -B -d mysql
bundle install --path=vendor/bundle

middleware

$ rails middleware
use Rack::Sendfile
use ActionDispatch::Static
use ActionDispatch::Executor
use ActiveSupport::Cache::Strategy::LocalCache::Middleware
use Rack::Runtime
use ActionDispatch::RequestId
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
use ActionDispatch::DebugExceptions
use ActionDispatch::RemoteIp
use ActionDispatch::Reloader
use ActionDispatch::Callbacks
use ActiveRecord::Migration::CheckPending
use Rack::Head
use Rack::ConditionalGet
use Rack::ETag
run XXXX::Application.routes

※削りたければconfig.middleware.delete ::Rack::Sendfile等で削れるようです。

database.ymlのuser, passwordを設定

CREATE USER 'hoge'@'localhost' IDENTIFIED WITH mysql_native_password BY 'hoge';
UPDATE MYSQL.USER SET xxxx_priv = 'y' WHERE USER = 'hoge';

各種作成

rails g scaffold user
rails db:create

参考

http://edgeguides.rubyonrails.org/api_app.html

Golangの基礎

参考

ドキュメント - The Go Programming Language

Golangの基礎としてはまず,tour of goを一通り見て理解します。

A Tour of Go

tour of go

Packages

goのプログラムの開始

mainパッケージからスタート

パッケージ名

importの最後のパス

※これによって関数の所属するパッケージが明確になります。

Exported names

最初の文字が大文字で始まる名前は外部のパッケージから参照できます

※exportされます

Functions

定義

func 関数名 (変数 *型) (引数1 型1, 引数2 型2) (返り値1の型, 返り値2の型) {
    return 返り値1, 返り値2
}

型が後置の理由




https://blog.golang.org/gos-declaration-syntax

Variables

関数の外

var i = 1
var str = "hoge"

関数の中

i := 1
str := "hoge"

※変数の型は右側の変数から型推論されます

Basic types

bool

string

int  int8  int16  int32  int64
uint uint8 uint16 uint32 uint64 uintptr

byte // uint8 の別名

rune // int32 の別名
     // Unicode のコードポイントを表す

float32 float64
complex64 complex128

※bool, string, int, float64がよく使います
※intは64-bitまでなので、足りない場合はconstを使う

Constants

定数にできるもの

文字(character)
文字列(string)
boolean
数値(numeric)

定義

const (
  hoge = "hoge"
)

※:=は使えない


for

定義

sum := 0
for i := 0; i < 10; i++ {
    sum += i
}

※iのスコープはfor内

無限ループ

for {
}

if

定義

if hoge := Hoge.hoge(x); hoge > 0 {
    return hoge
}

hogeはif文のスコープ内or else内でのみ使用可能です
※基本elseは使用しないとは思いますが

switch

定義

func main() {
    fmt.Print("Go runs on ")
    switch os := runtime.GOOS; os {
    case "darwin":
        fmt.Println("OS X.")
    case "linux":
        fmt.Println("Linux.")
    default:
        // freebsd, openbsd,
        // plan9, windows...
        fmt.Printf("%s.", os)
    }
}

※breakの最後で自動的にbreakされます
※breakさせたくない場合はfallthrough
※基本的にはswitchを使っている時点で複数のものがひとつの関数にきています ※switchを使うよりも先にメソッドを分けて、リファクタしてください

定義2

switch {
case t.Hour() < 12:
    fmt.Println("Good morning!")
case t.Hour() < 17:
    fmt.Println("Good afternoon.")
default:
    fmt.Println("Good evening.")
}

※if-elseが大量にある場合は以下のように書き直せます
※swtich true {}と同じです ※基本的にはif-elseを使っている時点で複数のものがひとつの関数にきています ※switch {}を使うよりも先にメソッドを分けて、リファクタしてください

defer

定義

func main() {
    defer fmt.Println("world2")
    defer fmt.Println("world")

    fmt.Println("hello")
}

※呼び出し元の関数がreturnするまで実行されません
※deferはLIFOのスタックに格納されます

https://blog.golang.org/defer-panic-and-recover


Pointers

値取得

value := *pointer

ポインター取得

pointer := &value

ポインターに値設定

*pointer := value

Arrays

定義

var a [2]string
a[0] = "Hello"
a[1] = "World"
fmt.Println(a[0], a[1])
fmt.Println(a)

primes := [6]int{2, 3, 5, 7, 11, 13}
fmt.Println(primes)

※配列のサイズを変えることはできません

Slices

定義

primes := [6]int{2, 3, 5, 7, 11, 13}

var s []int = primes[1:4]
fmt.Println(s) // [3 5 7]

※スライスは可変長(柔軟な配列)
※配列よりも使われます

スライスは配列への参照のようなもの

names := [4]string{
  "John",
  "Paul",
  "George",
  "Ringo",
}
a := names[0:2]
b := names[1:3]
b[0] = "XXX"
fmt.Println(a, b) // [John XXX] [XXX George]
fmt.Println(names) // [John XXX George Ringo]

※スライスの要素を変更すると元となる配列の要素が変更されます
※同じ元となる配列を共有している他のスライスは変更が反映されます

Slice literals

[]bool{true, true, false}

※[3]bool{true, true, false}の配列を作成して、それを参照するスライスを返す

Slice defaults

var a [10]int

a[0:10]
a[:10] // a[0:10] 
a[0:] a[0:10]
a[:] a[0:10]

Slice length and capacity

定義

len(s) // 要素の数
cap(s) // 元となる配列の要素数

s := []int{2, 3, 5, 7, 11, 13}
s = s[:0] // len=0 cap=6 []
s = s[:4] // len=4 cap=6 [2 3 5 7]
s = s[2:] // len=2 cap=4 [5 7]

Nil slices

var s []int // s == nil
fmt.Println(s, len(s), cap(s))  // [] 0 0

※要素の内スライス == nil

Creating a slice with make

hoge := make([]type, len, cap)
a := make([]int, 5) // len=5 cap=5 [0 0 0 0 0]
b := make([]int, 0, 5) // len=0 cap=5 []

※動的サイズの配列を作成

Slices of slices

// [[_ _ _] [_ _ _] [_ _ _]]
board := [][]string{
    []string{"_", "_", "_"},
    []string{"_", "_", "_"},
    []string{"_", "_", "_"},
}

※board[0]は[ _ ]

https://blog.golang.org/go-slices-usage-and-internals

append

var s []int
s = append(s, 1)
s = append(s, 2, 3, 4)

Range

インデックスを使う場合

for i, v := range slice {

}

インデックスを使わない場合

for _, v := range slice {

}

Maps

m = make(map[キーの型]バリューの型)

Map literals

type Vertex struct {
    Lat, Long float64
}

// map[Bell Labs:{40.68433 -74.39967} Google:{37.42202 -122.08408}]
var m = map[string]Vertex{
    "Bell Labs": Vertex{
        40.68433, -74.39967,
    },
    "Google": Vertex{
        37.42202, -122.08408,
    },
}

Map literals continued

type Vertex struct {
    Lat, Long float64
}

// map[Bell Labs:{40.68433 -74.39967} Google:{37.42202 -122.08408}]
var m = map[string]Vertex{
    "Bell Labs": {40.68433, -74.39967},
    "Google":    {37.42202, -122.08408},
}

※バリューの型はVertexなので、Bell Labsの要素は指定しなくてもVertexです

Mutating Maps

m[key] = elem
elem = m[key]
delete(m, key)
elem, ok = m[key]

Function values

func compute(fn func(float64, float64) float64) float64 {
    return fn(3, 4)
}

func main() {
    hypot := func(x, y float64) float64 {
        return math.Sqrt(x*x + y*y)
    }
    fmt.Println(hypot(5, 12))

    fmt.Println(compute(hypot))
    fmt.Println(compute(math.Pow))
}

rubyでいうlambdaのようなものです

Function closures

func adder() func(int) int {
    sum := 0
    return func(x int) int {
        sum += x
        return sum
    }
}

func main() {
    pos, neg := adder(), adder()
    for i := 0; i < 10; i++ {
        fmt.Println(
            i,
            pos(i),
            neg(-2*i),
        )
    }
}

0 0 0
1 1 -2
2 3 -6
3 6 -12
4 10 -20
5 15 -30
6 21 -42
7 28 -56
8 36 -72
9 45 -90

グローバル変数を使わなくてよくなります
※あまり使わない方がよいでしょう


TypeMethods

func (structValue Struct) method(arg1 argType) returnType {

}

Variable receivers

type MyFloat float64

func (f MyFloat) Abs() float64 {
    if f < 0 {
        return float64(-f)
    }
    return float64(f)
}

rubyでいうクラスの再オープンのようにメソッド追加できますが、型はオリジナルのものになります

Pointer receivers

type Vertex struct {
    X, Y float64
}

func (v Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func (v *Vertex) Scale(f float64) {
    v.X = v.X * f
    v.Y = v.Y * f
}

func main() {
    v := Vertex{3, 4}
    v.Scale(10) // {30 40}
    fmt.Println(v.Abs())}

※レシーバ自身を更新することが多いため、変数レシーバよりもポインタレシーバの方が一般的 ※変数でも、ポインターでもレシーバにできる
※v.Scale(10)は(&v).Scale(10)と同じ
※メソッドがレシーバが指す先の変数を変更するため,メソッドの呼び出し毎に変数のコピーを防げる ※golangの利点です
※メソッドはすべてポインタレシーバにすべき

Interfaces

type Abser interface {
    Abs() float64
}

func main() {
    var a Abser
    f := MyFloat(-math.Sqrt2)
    v := Vertex{3, 4}
    a = v // Vertex does not implement Abser (Abs method has pointer receiver)
}

type MyFloat float64

func (f MyFloat) Abs() float64 {
    if f < 0 {
        return float64(-f)
    }
    return float64(f)
}

type Vertex struct {
    X, Y float64
}

func (v *Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

※メソッドのシグニチャの集まり

Interfaces are implemented implicitly

type I interface {
    M()
}

type T struct {
    S string
}

// This method means type T implements the interface I,
// but we don't need to explicitly declare that it does so.
func (t T) M() {
    fmt.Println(t.S)
}

func main() {
    var i I = T{"hello"}
    i.M()
}

Interface

values

(value, type)

※空の場合は(, )

method

type T struct {
    S string
}

func main() {
    var i I

    i = &T{"Hello"}
    describe(i) // (&{Hello}, *main.T)
        i.M() // TのM()が実行される
}

func describe(i I) {
    fmt.Printf("(%v, %T)\n", i, i)
}

※基底クラスのメソッドが呼び出されます

values with nil underlying values

func (t *T) M() {
    if t == nil {
        fmt.Println("<nil>")
        return
    }
    fmt.Println(t.S)
}

nilがレシーバのif文を入れるのが一般的 ※if文がないとinvalid memory address or nil pointer dereference(間接参照)

Type assertions

インターフェイスは型を必ず持っている場合

値 := インターフェイス.(型)

インターフェイスが型を持っていない場合
→panic: interface conversion: interface {} is XXXXXX, not 型

インターフェイスは型を持っているか確認する場合

値, ok := インターフェイス.(型)

Type switches

func do(i interface{}) {
    switch v := i.(type) {
    case int:
        fmt.Printf("Twice %v is %v\n", v, v*2)
    case string:
        fmt.Printf("%q is %v bytes long\n", v, len(v))
    default:
        fmt.Printf("I don't know about type %T!\n", v)
    }
}

func main() {
    do(21)
    do("hello")
    do(true)
}

※基本的にswitchを使う時点でおかしいと思ってください
※複数のものを同じメソッドに流すのではなく、メソッドを分けてください

Stringers

type Stringer interface {
    String() string
}
type Person struct {
    Name string
    Age  int
}

func (p Person) String() string {
    return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}

func main() {
    a := Person{"Arthur Dent", 42}
    z := Person{"Zaphod Beeblebrox", 9001}
    fmt.Println(a, z)
}

※Printlnの出力をカスタマイズすることができます
※Personのみなど、限定的に振る舞いを変えることができるます

Errors

出力時はerrorインターフェイスのErrorが実行される

type error interface {
    Error() string
}

nil以外はエラー

func main() {
    if err := run(); err != nil {
        fmt.Println(err)
    }
}

Readers

func (T) Read(b []byte) (n int, err error)

func main() {
    r := strings.NewReader("Hello, Reader!")

    b := make([]byte, 8)
    for {
        n, err := r.Read(b)
        fmt.Printf("n = %v err = %v b = %v\n", n, err, b)
        fmt.Printf("b[:n] = %q\n", b[:n])
        if err == io.EOF {
            break
        }
    }
}

※rを8バイト毎に読み込んでいる
※nはbに入れられたサイズ
※bにはバイトスライスが入る


Goroutines

go f(x, y, z)

※同じアドレス空間で実行されるため、共有メモリへのアクセスは必ず同期する必要があります

Channels

ch := make(chan int)
ch <- v    // v をチャネル ch へ送信する
v := <-ch  // ch から受信した変数を v へ割り当てる

x, y := <-ch, <-ch

※データは、矢印の方向に流れます

Buffered Channels

func main() {
    ch := make(chan int, 2)
    ch <- 1
    ch <- 2
    // v := <- ch
    ch <- 3 // fatal error: all goroutines are asleep - deadlock!
    // fmt.Println(v)
    fmt.Println(<-ch)
    fmt.Println(<-ch)
}

Range and Close

close(ch)
v, ok := <-ch

※受信する値がない && チャネルが閉じているとokはfalseになる
※closeしたチャネルへ送信するとpanic: send on closed channel
※closeする必要はありません
※closeするのは、これ以上値が来ないことを受け手が知る必要があるときにだけです

func fibonacci(n int, c chan int) {
    x, y := 0, 1
    for i := 0; i < n; i++ {
        c <- x
        x, y = y, x+y
    }
    close(c)
}

func main() {
    c := make(chan int, 10)
    go fibonacci(cap(c), c)
    for i := range c {
        fmt.Println(i)
    }
}

※cap©はcチャネルの容量つまり10です。
※close©しないとfatal error: all goroutines are asleep - deadlock!になります
※close©は処理が終わったら、rangeループを終了するということです

Select

select ステートメントは、goroutineを複数の通信操作で待たせます。

selectは複数あるcaseのいずれかが準備できるようになるまでブロックし、準備ができたcaseを実行します。 複数のcaseの準備ができている場合、caseはランダムに選択されます。

func fibonacci(c, quit chan int) {
    x, y := 0, 1
    for {
        select {
        case c <- x:
            x, y = y, x+y
        case <-quit:
            fmt.Println("quit")
            return
        }
    }
}

func main() {
    c := make(chan int)
    quit := make(chan int)
    go func() {
        for i := 0; i < 10; i++ {
            fmt.Println(<-c)
        }
        quit <- 0
    }()
    fibonacci(c, quit)
}

※fibonacci内でselectしているため、cもしくはquitの準備を確認します
※cはすでに作成済みのチャネルのため、case <- xが実行されます
※goroutineのquit <- 0 でcase <-quitで値が取れるため、case <-quitが実行されます

Default Selection

ブロックせずに送受信するならdefaultのcase使う

select {
case i := <-c:
    // use i
default:
    // receiving from c would block
}

sync.Mutex

goroutineとコミュニケーションする場合はチャネル
goroutineとコミュニケーションする必要がない場合はsync.Mutex
goroutineが変数へのコンフリクトを避けるために変数へのアクセスできるようにしたい場合はsync.Mutex

// SafeCounter is safe to use concurrently.
type SafeCounter struct {
    v   map[string]int
    mux sync.Mutex
}

// Inc increments the counter for the given key.
func (c *SafeCounter) Inc(key string) {
    c.mux.Lock()
    // Lock so only one goroutine at a time can access the map c.v.
    c.v[key]++
    c.mux.Unlock()
}

// Value returns the current value of the counter for the given key.
func (c *SafeCounter) Value(key string) int {
    c.mux.Lock()
    // Lock so only one goroutine at a time can access the map c.v.
    defer c.mux.Unlock()
    return c.v[key]
}

func main() {
    c := SafeCounter{v: make(map[string]int)}
    for i := 0; i < 1000; i++ {
        go c.Inc("somekey")
    }

    time.Sleep(time.Second)
    fmt.Println(c.Value("somekey"))
}

やることは以下です

1. mux sync.Mutexの定義
2. c.mux.Lock()
3. defer c.mux.Unlock()

tour of goを一通り理解したら次はこの動画を見ます。

www.youtube.com

次に大まかな流れを確認します。

How to Write Go Code - The Go Programming Language

How to Write Go Code

Workspaces

src contains Go source files,
pkg contains package objects, and
bin contains executable commands.

The go tool builds source packages and installs the resulting binaries to the pkg and bin directories.

bin/
    hello                          # command executable
    outyet                         # command executable
pkg/
    linux_amd64/
        github.com/golang/example/
            stringutil.a           # package object
src/
    github.com/golang/example/
        .git/                      # Git repository metadata
    hello/
        hello.go               # command source
    outyet/
        main.go                # command source
        main_test.go           # test source
    stringutil/
        reverse.go             # package source
        reverse_test.go        # test source
    golang.org/x/image/
        .git/                      # Git repository metadata
    bmp/
        reader.go              # package source
        writer.go              # package source

The GOPATH environment variable

$HOME/go
echo "export PATH=$PATH:$(go env GOPATH)/bin" >> ~/.bash_profile
echo "export GOPATH=$(go env GOPATH)" >> ~/.bash_profile

Import paths

A package's import path corresponds to its location inside a workspace or in a remote repository

Your first program

$ mkdir -p $GOPATH/src/github.com/user/app
$ go install github.com/user/app
$ app

※$GOPATH/bin/appに実行ファイルが置かれます

Your first library

$ mkdir $GOPATH/src/github.com/user/stringutil
// Package stringutil contains utility functions for working with strings.
package stringutil

// Reverse returns its argument string reversed rune-wise left to right.
func Reverse(s string) string {
    r := []rune(s)
    for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
        r[i], r[j] = r[j], r[i]
    }
    return string(r)
}
$ go build github.com/user/stringutil

※bin/stringutilは不要なのでgo build

package main

import (
    "fmt"

    "github.com/user/stringutil"
)

func main() {
    fmt.Printf(stringutil.Reverse("!oG ,olleH"))
}
$ go install github.com/user/app

※pkg/linux_amd64/github.com/user/stringutil.aが作成される

stringutil.a

future invocations of the go tool can find the package object  
to avoid recompiling the package unnecessarily

Package names

package name

※The first statement in a Go source file
※All files in a package must use the same name
※Executable commands must always use package main
※There is no requirement that package names be unique across all packages linked into a single binary, only that the import paths (their full file names) be unique.

Testing

$GOPATH/src/github.com/user/stringutil/reverse_test.go

package stringutil

import "testing"

func TestReverse(t *testing.T) {
    cases := []struct {
        in, want string
    }{
        {"Hello, world", "dlrow ,olleH"},
        {"Hello, 世界", "界世 ,olleH"},
        {"", ""},
    }
    for _, c := range cases {
        got := Reverse(c.in)
        if got != c.want {
            t.Errorf("Reverse(%q) == %q, want %q", c.in, got, c.want)
        }
    }
}

必要なこと

  • ファイル名は_test.go
$GOPATH/src/github.com/user/stringutil/reverse_test.go
  • ファイル内
1. import "testing"
2. func TestXXXX(t *testing.T) {}
3. 失敗時にはt.Error or t.Errorf or t.Fail

ベンチマークのテストしたい場合は*testing.B

  • 実行
$ go test github.com/user/stringutil

Remote packages

$ go get github.com/golang/example/hello

※go get = fetch && build && install


言語仕様

The Go Programming Language Specification - The Go Programming Language


美しく成熟するコード

vimeo.com


Go Concurrency Patterns

Go Concurrency Patterns


Advanced Go Concurrency Patterns

Advanced Go Concurrency Patterns


Share Memory by Communicating

Share Memory By Communicating - The Go Programming Language


A simple programming environment

Go: a simple programming environment

vimeo.com


Writing Web Applications

Writing Web Applications - The Go Programming Language


First Class Functions in Go

First-Class Functions in Go - The Go Programming Language


Goのメモリモデル

Goのメモリモデル - The Go Programming Language

Golangのメリット

コンパイルが早い

型システムには階層がない

気軽に新しい型を作成できる

並列実行と通信を言語としてサポート

完全なガベージコレクションを実装

シンプル

動的型言語の効率、コンパイル言語の静的な型の安全性を併せ持つ

参考

FAQ - golang.jp

インストールや各種設定

Go(golang)のインストールとSublimeText3(GoSublimeの設定)の開発環境構築 - keiwt’s diary
これ通りやれば、保存時にgo build(コンパイル), go test(テスト), go vet(ソースの静的解析), go lint(構文チェック)が動きますので楽ちんです。
※go build .で実行ファイルはカレントディレクトリに置かれます

言語に備わっているもの

build       compile packages and dependencies
clean       remove object files
doc         show documentation for package or symbol
env         print Go environment information
fix         run go tool fix on packages
fmt         run gofmt on package sources
generate    generate Go files by processing source
get         download and install packages and dependencies
install     compile and install packages and dependencies
list        list packages
run         compile and run Go program
test        test packages
tool        run specified go tool
version     print Go version
vet         run go tool vet on packages

以下により、ソースが綺麗で、バグの少ない、速くて、メモリ消費量の少ないソースになります。

[https://golang.org/pkg/testing/:title]

* go vet  
主にコンパイルでは発見することのできない静的エラーを指摘してくれます。  
※デフォルトで-allになっています

Assembly declarations →Mismatches between assembly files and Go function declarations.

Useless assignments →Check for useless assignments.

Boolean conditions →Mistakes involving boolean operators.

Build tags →Badly formed or misplaced +build tags.

Invalid uses of cgo →Detect some violations of the cgo pointer passing rules.

Unkeyed composite literals →Composite struct literals that do not use the field-keyed syntax.

Copying locks →Locks that are erroneously passed by value.

→Tests, benchmarks and documentation examples)

→Mistakes involving tests including functions with incorrect names or signatures and example tests that document identifiers not in the package.

→Failure to call the cancelation function returned by context.WithCancel.

→The cancelation function returned by context.WithCancel, WithTimeout, and WithDeadline must be called or the new context will remain live until its parent context is cancelled. (The background context is never cancelled.)

Methods →Non-standard signatures for methods with familiar names, including: Format GobEncode GobDecode MarshalJSON MarshalXML Peek ReadByte ReadFrom ReadRune Scan Seek UnmarshalJSON UnreadByte UnreadRune WriteByte WriteTo

Nil function comparison →Comparisons between functions and nil.

Printf family →Suspicious calls to functions in the Printf family, including any functions with these names, disregarding case: Print Printf Println Fprint Fprintf Fprintln Sprint Sprintf Sprintln Error Errorf Fatal Fatalf Log Logf Panic Panicf Panicln

Struct tags →Incorrect uses of range loop variables in closures.

Shadowed variables →Variables that may have been unintentionally shadowed.

Shifts →Struct tags that do not follow the format understood by reflect.StructTag.Get. Well-known encoding struct tags (json, xml) used with unexported fields.

Unreachable code →Unreachable code.

Misuse of unsafe Pointers →Likely incorrect uses of unsafe.Pointer to convert integers to pointers. A conversion from uintptr to unsafe.Pointer is invalid if it implies that there is a uintptr-typed word in memory that holds a pointer value, because that word will be invisible to stack copying and to the garbage collector.

Unused result of certain function calls →Calls to well-known functions and methods that return a value that is discarded. By default, this includes functions like fmt.Errorf and fmt.Sprintf and methods like String and Error. The flags -unusedfuncs and -unusedstringmethods control the set.

[https://golang.org/cmd/vet/:title]  

* go lint  
※Effective GO通りになっているかチェックしてくれます。    

Golint differs from gofmt. Gofmt reformats Go source code ,whereas golint prints out style mistakes.

golint is concerned with coding style.

Golint makes suggestions for many of the mechanically checkable items listed in Effective Go and the CodeReviewComments wiki page.

[https://github.com/golang/lint/blob/master/README.md:title]

[https://blog.golang.org/error-handling-and-go:title]  

[http://golang.jp/go_faq#exceptions]

* メソッドと演算子のオーバロードがない

メソッドを呼び出す際に、メソッドの型をいちいち調べしなくてよい方がシンプルです。 他の言語に接した経験から言えることは、同じ名前で、かつシグネチャが異なるメソッドの寄せ集めを持つことは、 ときに役に立ちますが、混乱を招くだけで充分に機能しません。

* implementsがない

Goの型がインタフェースを満たすには、そのインタフェースのメソッドを実装する以外、何も必要ありません。 この特性により、既存のコードを修正せずにインタフェースを規定・使用することを可能にします。 これは、関連性の分離とコードの再利用を促進する「ダック・タイピング」の一種で、 コード開発時のパターン構築を容易にします。 このインタフェースの仕組みは、Goの小回りと軽量さに大きく寄与しています。

[http://golang.jp/go_faq#methods_on_values_or_pointers]

* シングルクオート・ダブルクオート

基本的にダブルクオート。 シングルクオートを使うと missing ‘ syntax error: unexpected ambda, expecting comma or )

The Go language defines the word rune as an alias for the type int32, so programs can be clear when an integer value represents a code point.

Moreover, what you might think of as a character constant is called a rune constant in Go. The type and value of the expression

‘⌘’ is rune with integer value 0x2318.

※シングルクオートだとalias for the type int32になります。  
[https://blog.golang.org/strings:title]
Remove all ads

DynamoDBからデータを取得してみる(query)

テーブル定義

  Views:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: VIEWS
      KeySchema:
        -
          AttributeName: ID
          KeyType: HASH
      GlobalSecondaryIndexes:
        -
          IndexName: GSI_URL
          KeySchema:
            -
              AttributeName: URL
              KeyType: HASH
          Projection:
            NonKeyAttributes:
              - ID
            ProjectionType: INCLUDE
          ProvisionedThroughput:
            ReadCapacityUnits: 5
            WriteCapacityUnits: 5
      AttributeDefinitions:
        -
          AttributeName: ID
          AttributeType: N
        -
          AttributeName: URL
          AttributeType: S
      ProvisionedThroughput:
        ReadCapacityUnits: 5
        WriteCapacityUnits: 5
      StreamSpecification:
        StreamViewType: NEW_AND_OLD_IMAGES

Lambdaのソース(Python2.7)

※VIEWSテーブルからURLが/のデータを取得します

from __future__ import print_function
from boto3.dynamodb.conditions import Key, Attr
import json
import boto3

dynamodb = boto3.resource('dynamodb')

print('Loading function')

def handler(event, context):
  print("Received event: " + json.dumps(event, indent=2))

  try:
    views = dynamodb.Table('VIEWS')
    response = views.query(
      IndexName='GSI_URL',
      KeyConditionExpression=Key('URL').eq('/')
    )
    print(response)
    return {'data':response}
  except Exception as e:
    print(e)
    raise(e)

取得できたデータ

カウントも同時に返してくれるのは便利ですね。

{
  "data": {
    "Count": 4,
    "Items": [
      {
        "URL": "/",
        "ID": 3
      },
      {
        "URL": "/",
        "ID": 2
      },
      {
        "URL": "/",
        "ID": 1
      },
      {
        "URL": "/",
        "ID": 5
      }
    ],
    "ScannedCount": 4,
    "ResponseMetadata": {
      "RetryAttempts": 0,
      "HTTPStatusCode": 200,
      "RequestId": "XXXXXXXXXXXXXXXXXXXXXX",
      "HTTPHeaders": {
        "x-amzn-requestid": "XXXXXXXXXXXXXXXXXXXXXXXXXXX",
        "content-length": "170",
        "server": "Server",
        "connection": "keep-alive",
        "x-amz-crc32": "1111111111111111",
        "date": "Mon, 10 Oct 2016 10:22:48 GMT",
        "content-type": "application/x-amz-json-1.0"
      }
    }
  }
}