Subscribed unsubscribe Subscribe Subscribe

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"
}

Instagramの愛猫のマンチカンのハッシュタグまとめ

コピペ用

#cat #catstagram #猫 #ねこ #ネコ #にゃんこ #cute #かわいい #catsoftheday #catsoftheworld #catlover #catsofinstagram #catstagram #instagramcats #instacat #にゃんすたぐらむ #munchkin #マンチカン #短足マンチカン #さくらタン #にゃんだふるらいふ #ねこのいる生活 #関東にゃんこ部 #みんねこ #みんなのねこ部 #picneko #ペコねこ部 #マンチカン部 #mewmewmofmof #mewmof

候補

改行なし

#cat #cats #catstagram #neco #neko #猫 #ねこ #ネコ #にゃんこ #ニャンコ #pet #pets #petstagram #petsagram #ペット #cute #かわいい #animal #animals #動物 #catsoftheday #catsoftheworld #ilovemycat #lovecats #catlover #catsofinstagram #catstagram #instagramcats #instacat #nekostagram #necostagram #にゃんすたぐらむ #ニャンスタグラム #munchkin #munchikinlovers #マンチカン #まんちかん #短足マンチカン #短足猫 #短足猫 #短足にゃんこ #短足ニャンコ #サクラタン #サクラたん #さくらタン #ニャンダフルライフ #にゃんだふるらいふ #ねこのいる生活 #ねこのいる生活 #猫部 #ねこ部 #ネコ部 #ニャンコ部 #にゃんこ部 #ふわもこ部 #マンチカン部 #meow #meowcat #mewmewmofmof #mewmof #meowmof #catmovie #猫動画 #ねこ動画 #ネコ動画

改行あり(候補一覧)

#cat #cats #catstagram
#neco #neko
#猫 #ねこ #ネコ #にゃんこ #ニャンコ
#pet #pets #petstagram #petsagram
#ペット
#cute
#かわいい
#animal #animals
#動物
#catsoftheday #catsoftheworld #ilovemycat #lovecats  #catlover
#catsofinstagram #catstagram #instagramcats #instacat #nekostagram #necostagram
#にゃんすたぐらむ #ニャンスタグラム
#munchkin #munchikinlovers
#マンチカン #まんちかん #短足マンチカン #短足猫 #短足猫 #短足にゃんこ #短足ニャンコ
#サクラタン #サクラたん #さくらタン 
#ニャンダフルライフ #にゃんだふるらいふ #ねこのいる生活 #ねこのいる生活
#猫部 #ねこ部 #ネコ部 #ニャンコ部 #にゃんこ部 #ふわもこ部 #マンチカン部
#meow #meowcat
#mewmewmofmof #mewmof #meowmof
#catmovie
#猫動画 #ねこ動画 #ネコ動画

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"
      }
    }
  }
}

DynamoDBの基礎

TableName

名前を指定しない場合、AWS CloudFormation は一意の物理 ID を生成し、その ID をテーブル名として使用します。

KeySchema

primary key for the table

※文字列、数値、またはバイナリ
※テーブル作成時に必要な唯一のもの
※変更する際にはテーブルも作り直す

  • HASHのみの場合
      KeySchema:
        -
          AttributeName: ID
          KeyType: HASH

※内部ハッシュ関数への入力として使用され、パーティションが決まります
※ハッシュのキーの値は一意です

  • HASHとRANGEの場合
      KeySchema:
        -
          AttributeName: ARTIST
          KeyType: HASH
        -
          AttributeName: SONG_TITLE
          KeyType: RANGE

※同じHASHキーのレコードはRANGEキーの値でソートされてまとめて保存されます。

SecondaryIndex

HASHキーとHASH and RANGEキー以外でのクエリを投げたいときに追加します。
SecondaryIndexの上限では足りなくなる場合はRDSを使うことが推奨されています。

  • GlobalSecondaryIndexes
    テーブルと異なるHASHキーとRANGEキーを持つIndexで最大で5個。
    ※Cannot perform more than one GSI creation or deletion in a single update
    ※Cannot update GSI's properties other than Provisioned Throughput. You can create a new GSI with a different name.

  • LocalSecondaryIndexes
    テーブルと同じHASHキーと、異なるRANGEキーを持つIndexで最大で5個
    ※Table KeySchema does not have a range key, which is required when specifying a LocalSecondaryIndex

AttributeDefinitions

A list of AttributeName and AttributeType objects that describe the key schema for the table and indexes.

  • Property AttributeDefinitions is inconsistent with the KeySchema of the table and the secondary indexes
AttributesDefinitionsはKeySchemaとIndexesを合わせたものにしてください。  
GlobalSecondaryIndexesがテーブルと異なるHASHキーとRANGEキーになっているか、
LocalSecondaryIndexesがテーブルと同じHASHキーと、異なるRANGEキーを確認してください。
また、KeySchemaとIndexesを合わせたものがAttributesDefinitionsになっているかを確認します。

ProvisionedThroughput

Amazon DynamoDB が負荷を分散する前に、指定したテーブルの項目について整合性のある読み込み or 読み込みを行う、1秒あたりの最小サイズ(最大1KB)を設定します。

      ProvisionedThroughput:
        ReadCapacityUnits: 5
        WriteCapacityUnits: 5

StreamSpecification

テーブルに保存された項目の変更をキャプチャする DynamoDB テーブルストリームの設定

      StreamSpecification:
        StreamViewType: NEW_AND_OLD_IMAGES
KEYS_ONLY - Only the key attributes of the modified item are written to the stream.
NEW_IMAGE - The entire item, as it appears after it was modified, is written to the stream.
OLD_IMAGE - The entire item, as it appeared before it was modified, is written to the stream.
NEW_AND_OLD_IMAGES - Both the new and the old item images of the item are written to the stream.

参考

http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/HowItWorks.CoreComponents.html
AWS::DynamoDB::Table - AWS CloudFormation

AWS Lambdaのベスト・プラクティス

Lambdaを使用する際にはベストプラクティスが何点かあるようですが、特に重要な点をピックアップします。

Instantiate AWS clients and database clients outside the scope of the handler to take advantage of connection re-use.

var AWS = require('aws-sdk');
var dynamo = new AWS.DynamoDB();

exports.handler = function(event, context) {
  .....
}

Lower costs and improve performance by minimizing the use of startup code not directly related to processing the current event.

必要でない場合はVPCを使用しない

できるだけVPCを使わないようにして、S3やDynamoDBを使用する。

例外処理

Pythonの場合はraise
NodeJS4.3の場合callbackする

エラー文言の場合は[BadRequest]等を付けて意図したHTTPステータスコードが返るようにする。  

参考

Best Practices for Working with AWS Lambda Functions - AWS Lambda
Error Handling Patterns in Amazon API Gateway and AWS Lambda | AWS Compute Blog
AWS Black Belt Techシリーズ AWS Lambda Updates

AWS CloudFormationでyaml, !GetAtt, Export, !ImportValue, !Subが使えるようになったので使ってみる

AWS CloudFormation Update – YAML, Cross-Stack References, Simplified Substitution | AWS Blog

yaml

jsonを以下を使ってyamlにするだけ
Convert JSON to YAML

!GetAttや!Ref

!を使うことでAWSの擬似関数であることが明示的になります。
※!はyamlのタグ用のシンタックスです。
* before

      Parameters:
        LambdaRoleArn:
          Fn::GetAtt:
            - IAMStack.Outputs
            - LambdaRoleArn
        LambdaBucket:
          Ref: LambdaBucket
  • after
      Parameters:
        LambdaRoleArn:
          !GetAtt IAMStack.Outputs.LambdaRoleArn
        LambdaBucket:
          !Ref LambdaBucket

!Sub

!Joinが不要になりました。
ApiGatewayのModelのSchema等はは!Joinの方がよいかもしれません。
* before

    Properties:
      TemplateURL:
        Fn::Join:
        - ''
        - - !Ref CfnTemplateDir
          - "/"
          - IAM.template
  • after
    Properties:
      TemplateURL: !Sub
        - ${CfnTemplateDir}/IAM.template
        - { CfnTemplateDir: !Ref CfnTemplateDir }

stackを分ける場合

今までは親スタックでパラメーターとして渡す必要がありましたが、Export, !ImportValueが使えるようになったので、
親子スタックの依存がなくなりました。
* diff(リソースのあるStack)

--- a/cfn/IAM.template
+++ b/cfn/IAM.template
@@ -118,22 +118,32 @@ Resources:
 Outputs:
   LambdaRoleArn:
     Value:
       !GetAtt LambdaRole.Arn
+    Export:
+      Name: IAMStackLambdaRoleArn
  • diff(親テンプレート)
--- a/cfn/AWS.template
+++ b/cfn/AWS.template
@@ -51,32 +51,26 @@ Resources:
   LambdaStack:
     Type: AWS::CloudFormation::Stack
     Properties:
       TemplateURL: !Sub
         - ${CfnTemplateDir}/Lambda.template
         - { CfnTemplateDir: !Ref CfnTemplateDir }
       Parameters:
-        LambdaRoleArn:
-          !GetAtt IAMStack.Outputs.LambdaRoleArn
  • diff(リソースを参照するStack)
--- a/cfn/Lambda.template
+++ b/cfn/Lambda.template
@@ -1,34 +1,34 @@
 ---
 AWSTemplateFormatVersion: '2010-09-09'
 Description: Reserve Lambda Stack
 Parameters:
-  LambdaRoleArn:
-    Type: String
   LambdaBucket:
     Type: String
 Resources:
   LambdaFunction:
     Type: AWS::Lambda::Function
     Properties:
       Code:
         S3Bucket:
           !Ref LambdaBucket
         S3Key: hello/index.js.zip
       Description: hello
       Handler: index.handler
       MemorySize: '128'
       Role:
-        !Ref LambdaRoleArn
+        !ImportValue IAMStackLambdaRoleArn
       Runtime: nodejs4.3
       Timeout: '25'

CloudFormationでAWS::Route53::RecordSetGroupでS3のバケットにAliasレコードを設定してみる

HostedZoneName

Zone Apex (Naked Domain)を指定します。  

RecordSets

  • Name
ユーザーがブラウザでアクセスするドメインを指定します。  
"HostedZoneId" : "Z2M4EHUR26P7ZW",
"DNSName" : "s3-website-ap-northeast-1.amazonaws.com."

最終的なtemplate

    "RecordSetGroup": {
      "Type": "AWS::Route53::RecordSetGroup",
      "Properties": {
        "HostedZoneName": { "Ref" : "HostedZoneName" },
        "RecordSets": [
          {
            "Name": { "Ref" : "SiteCNAME" },
            "Type": "A",
            "AliasTarget" : {
              "HostedZoneId" : "Z2M4EHUR26P7ZW",
              "DNSName" : "s3-website-ap-northeast-1.amazonaws.com."
            }
          }
        ]
      }
    }