PCがあれば何でもできる!

へっぽこアラサープログラマーが、覚えたての知識を得意げにお届けします

【Ruby】DynamoDBのJSONドキュメントを試してみた(aws-sdk v2.0.0 stable)

f:id:m_doi2:20141011115038j:plain

ついにDynamoDBがJSONドキュメントに対応されましたね!
NoSQLのAmazon DynamoDBがJSONドキュメントに対応、25GB/月間2億リクエストまで無料枠も拡大 - Publickey

記事にはJavaのサンプルしかなかったので、早速Rubyで試してみました。
今回は「aws-sdk v2.0.0 stable」を使用しています。

続きを読む

【Rails】AWS:DynamoDBのORM「Dynamoid」の不便な点とその改善方法(その2)

2回目の「Dynamoid」の不便な点と改善方法についてのお話です!

前回の記事はこちら。
【Rails】AWS:DynamoDBのORM「Dynamoid」の不便な点とその改善方法(その1) - PCがあれば何でもできる!

Modelに定義していない属性が、検索レコード内に存在すると落ちる

例えばこんなModelを作ったとして

class User
  include Dynamoid::Document
 
  table :key => :id
  field :name, :string
  field :age, :integer
end

 

こんな感じで検索したとします。

result = User.find(1)

 

そして、検索したレコードがこんな感じになっていると

id   name    age sex
--------------------------------
1   doizaki 29  male

 

Modelにsexのfieldを定義していないため、”sex=”のメソッドが無いと言って落ちます。

(503.68 ms) GET ITEM - [["User", 1, {}]]
NoMethodError: undefined method `sex=' for #<User:0x000001065e4370>

 

この動きのせいで、過去にゴミ属性を作っていると引っかかります。 Dynamoidさん、そこは無視してください orz

対処方法

ということで、コードでDynamoidさんを説得してみたいと思います。

まず、エラーが発生している場所を調べます。

# encoding: utf-8
module Dynamoid #:nodoc:
 
  # This is the base module for all domain objects that need to be persisted to
  # the database as documents.
  module Document
    ...
    module ClassMethods
      ...
      def load(attrs)
        self.class.undump(attrs).each {|key, value| send "#{key}=", value }
      end
      ...
    end
    ...
  end
end

self.class.undumpで取得したレコードの属性をハッシュ化し、#{key}=メソッドでModelの各fieldに値を退避させています。 が、Modelにfieldを定義しないと、このメソッドが作られないため、落ちてしまうようです。

そこで、メソッド実行前に、respond_to?を使って、メソッドの存在チェックを挟みます。書き換えは、Rubyのオープンクラスの特性を活かして、外部から書き換えます。

module Dynamoid
  module Document
    def load(attrs)
      self.class.undump(attrs).each do |key, value|
        send("#{key}=", value) if respond_to?(key)
      end
    end
  end
end

 

これで無事に取得出来ました!

result = User.find(1)
(524.08 ms) GET ITEM - [["User", 1, {}]]
 => #<User:0x00000106674100 @new_record=false, @attributes={:created_at=>Sat, 17 May 2014 17:36:40 +0900, :updated_at=>Thu, 22 May 2014 13:26:47 +0900, :id=>1, :name=>"doizaki", :age=>29}, @associations={}, @changed_attributes={}>

 

さらにパフォーマンスを追求

さて、動きはしたものの、上記の修正については、ややパフォーマンスが心配です。

respond_to?はクラス内(継承、特異含む)のメソッドを全て探索するため、軽い処理とは言いがたいです。

さらに、今回書き換えたloadメソッドは、検索結果のレコード数回実行されるため、respond_to?が実行されるトータル回数は、検索行数☓属性数回となり、かなりの負荷が予想されます。

そこで、改良パターンを2つ考えてみました。

改良パターン

ハッシュを利用するパターン

Dynamoidは、fieldを定義した際に、自動でModelクラスのattributesというハッシュに、属性情報が保存されます。 そのハッシュを利用することで、respond_to?での余計なメソッド探索を防ぎます。

module Dynamoid
  module Document
    def load(attrs)
      self.class.undump(attrs).each do |key, value|
        send("#{key}=", value) if self.class.attributes.include?(key)
      end
    end
  end
end
begin-rescueによる例外処理で回避するパターン

レコード毎、属性毎に、探索を行わないため、ゴミデータが限りなく少ない場合は早いはず。

module Dynamoid
  module Document
    def load(attrs)
      self.class.undump(attrs).each do |key, value|
        begin
          send("#{key}=", value) if self.class.attributes.include?(key)
        rescue (NoMethodError)
        end
      end
    end
  end
end

パフォーマンス検証

ゴミデータが少ない場合

条件

  • レコード: 10万
  • フィールド(属性): 40
  • ゴミデータ: 全レコードに1属性ずつ

結果(3回の平均)

  • respond_to?: 12,943 ms
  • ハッシュ: 11,812 ms
  • begin-rescure: 11,794 ms
ゴミデータが多い場合

条件

  • レコード: 10万
  • フィールド(属性): 30
  • ゴミデータ: 全レコードに10属性ずつ

結果(3回の平均)

  • respond_to?: 11,691 ms
  • ハッシュ: 9,120 ms
  • begin-rescure: 15,786 ms

 

安定して早いのは、ハッシュでした。例外処理の重さをあらためて感じます。

ただし、ゴミデータ入りの行が限りなく少ない場合は、順位が入れ替わるかもしれませんね。

【Rails】AWS:DynamoDBのORM「Dynamoid」の不便な点とその改善方法(その1)

DynamoDBについて

皆さんは、AWSのNoSQLデータベース「DynamoDB」を利用していますか?

DynamoDBは、テーブル毎にスループット容量を設定できることと、主にその設定値によって利用料が決まることが特徴です。

データ容量による課金ではないため、大量データの管理が高額にならず、利用が集中するテーブルや時間に合わせて、スループット容量と利用料のバランスを調整することができます。

あ、もちろんAmazonなので、速度は折り紙つきです。

弊社はデータ管理に複数のDBを利用していますが、上記の特徴から、件数が大量のデータやアクセスが集中しやすいデータの場合は、DynamoDBを利用しています。

さて、DynamoDBを利用するにあたり、性能は全く申し分ないのですが、いざRuby on Railsでデータ操作のコードを書くとなると、公式のORMが今ひとつ使いにくいことに気が付きます。

aws-sdkの「AWS::Record::HashModel」というクラスなのですが、使いにくい例を挙げると…

  • ActiveRecordの文法に似せてあるものの書き方が古かったり
  • わざわざテーブルのスキーマ情報を取得する必要があったり
  • etc…

Dynamoidを使う

そこで、「Dynamoid」というgemを利用してみます。
Veraticus/Dynamoid · GitHub

aws-sdkをラップし、よりActiveRecordに近い形で操作可能になっており、おすすめです。

Dynamoidはテーブル名に制約がかかってしまう

前段が長くなってしまいましたが、ここからが本題です。
このDynamoidは、どうやら初期のテーブル作成から使用されることを想定しているようで、テーブル名の命名規則にいくつか制限がかかってしまいます。

  • 先頭に固定のnamespaceが付けられる
  • 英小文字のスネークケースとなる
  • 複数系(最後にsが付く)

例えば、下記のようにnamespaceを設定し、

Dynamoid.configure do |config|
  config.namespace = "test"
end

 

下記のようにModelを作成したとすると、

class User
  include Dynamoid::Document
    ...
  end
end

 

UserModelのアクセス先のテーブル名は「test_users」となってしまうのです。

これは、命名規則にマッチしないテーブルが既に存在する場合に、かなりの痛手です。
なにせDynamoDBはテーブル名の変更はできませんので、本気でやるなら新規テーブル作成後、データ移行するという手順を踏むことになります。
悲しい…orz

テーブル名の制約の対処方法

この問題に対処するため、Rubyのオープンクラスの特性を活かして、Dynamoidのコードを外部から書き換えます。

module Dynamoid
  module Persistence
    module ClassMethods
      def table_name
        ## Config.namespaceを付加しないよう書き換え
        #@table_name ||= "#{Dynamoid::Config.namespace}_#{options[:name] || base_class.name.split('::').last.downcase.pluralize}"
        @table_name ||= "#{options[:name] || base_class.name.split('::').last}"
      end
    end
  end
end

 

コメントアウトの文と見比べていただければ、処理内容は単純なのでお分かりかと思います。 このコードをModel使用前に読み込ませることで、テーブル名の制約を取っ払うことができます。

プログラマの悩み。大したスキルもないし、この先ほんとに大丈夫かな→少し自信が持てた話

f:id:m_doi2:20140920014004j:plain

Webエンジニアになろうと決心し、行動を開始したのが、9ヶ月前の12月。

そして現在は…

スマホアプリをメインで作っている企業に、転職することができました。
スキルで言うと、AWS、Rails、Java、副業でPHP、AngularJSの辺りを使いつつ、ようやくWebエンジニアと名乗れるくらいには、なってきたかなと思います。

どうやってここまで来れたのか、お伝えできると良かったのですが…。
正直な話、自分の努力よりも、現職・前職の職場の方々、そして家族が支えてくれたというのが大きいです。
そのため、あまり皆さんの参考になるようなことは、書けそうにありません。

ただ、今回、色々と動いてみて、

「今の自分には、スキルも知識も何もない」という悲観的な考えから、
「もっと自信を持っていいんだ」と思えるようになったことは、大きかったです。

今日は、そう思えるようになったきっかけについて書いてみようと思います。

続きを読む

[Ubuntu]複数の公開鍵を使い分ける方法

VPSログイン時の公開鍵と、Herokuログイン時の公開鍵を、別々に分けたかったので、調べた際のメモ。

①公開鍵の作り方
 複数の公開鍵を使い分ける - May the Source be with you

②Herokuへの公開鍵設定方法
 heroku loginをする前に下記のコマンドを入れる。

heroku keys:add ~/.ssh/id_rsa.sample.pub(①で作った公開鍵名)

[.NET]DataTableの行追加やデータ修正が異様に遅い時に疑うべき点

.NETですが、はまったので備忘録として残しておきます。

 

今回会社で、あるプログラムの動作が遅いとの報告を受け、調査しました。

そのプログラムはC#で書かれたものだったのですが、どうやらDataTableに数万件のデータを追加しているタイミングで、異様に時間がかかっている様子でした。約2万件で1〜2分です。

処理も特に変なことをやっているわけではなく、単純にDataTable.NewRowでDataRowを生成し、各フィールドにデータを入れて、最後にDataTable.Rows.Addで追加しているだけ。

むむむ・・・(´・ω・`)
となってしまいました。

続きを読む

JavaScriptで馴染めない仕様(ブロックスコープ)

JavaScriptの基礎を身につけようと、「パーフェクトJavaScript」を読んで勉強しています。
大体3分の1くらい読み終えました。レビュー通りの良本です。

 

しかし、今までクラスベースのコードしか触っていないこともあり、馴染めそうにない仕様も結構あります。この辺りは今後、自身の復習も兼ねてご紹介していけたらと思います。
ひとまず今日は、だいぶ夜遅くなってしまったので簡単なものだけ。

続きを読む