database cleanerの設定について

rspecのテスト実行時にDatabaseCleanerを使用しています。

データベースを毎回キレイな状態にしてくれるやつですね。

これの何がいいかというと、テストデータが固定化されるのと、増えないのでテストの実行が遅くならないということです。

ただし、設定によってテストが遅くなることもあります。

きちんとした設定をすることが高速化につながります。

そして、設定について、曖昧だったのでまとめておきます。

transactionとtruncationについて

trancationはDBを毎回削除しています。

truncate文ですね。

transactionはDBをrollbackしています。

手元で実行速度を確認したかったので、試してみました。

truncation

Finished in 11.22 seconds (files took 0.45743 seconds to load)
45 examples, 0 failures, 1 pending

transaction

Finished in 10.94 seconds (files took 0.45952 seconds to load)
45 examples, 0 failures, 1 pending

若干ながら、transactionの方が早いですね。

公式のドキュメントにも、transactionの方が早いって謳ってます。

For the SQL libraries the fastest option will be to use :transaction as transactions are simply rolled back. If you can use this strategy you should.

しかし、少しだけ問題があります。

However, if you wind up needing to use multiple database connections in your tests (i.e. your tests run in a different process than your application) then using this strategy becomes a bit more difficult. You can get around the problem a number of ways.

javascriptのテストを実行するときようなときは注意が必要です。

transactionの問題点

jsのテストを実行する場合は、ブラウザで動いているプロセスから、データベースの値が取得できなくてテストが落ちてしまいます。

transactionの場合は、transaction内でデータを生成しますが、別のスレッドからはデータを取得できないからです。

公式にも書いていますが、下記のようにfeatureテストのみtrancationを使用するようにします。

  config.before(:each, type: :feature) do
    # :rack_test driver's Rack app under test shares database connection
    # with the specs, so continue to use transaction strategy for speed.
    driver_shares_db_connection_with_specs = Capybara.current_driver == :rack_test

    if !driver_shares_db_connection_with_specs
      # Driver is probably for an external browser with an app
      # under test that does *not* share a database connection with the
      # specs, so use truncation strategy.
      DatabaseCleaner.strategy = :truncation
    end
  end

また、下記の部分もfalseに変更します。

  config.use_transactional_fixtures = false

挿入するデータをtransactionするのではなく、truncationするようになります。

ここの常にtruncationする必要はないため、特定の時だけfalseにする設定ができれば、処理が早くなりそうな気がします。

まとめ

DB 別のスレッドからデータ参照
truncation truncate(削除)
transaction rollback ×

適当に設定していたけど、意外に奥が深くて面白いですね。

viewの条件式を減らす

viewにロジックを書きすぎるな!ってことです。

自戒の意味を込めて書きます。

- if area? && action_name == "show"
  - if citys?
     = render "foo"
  - else
     = render "bar"
- else
  = render "bar"

こういうのがあったとします。

viewにこんだけロジックが積まれるとしんどいので、まとめます。

def foo?
  if area? && action_name == "show"
    if citys?
      true
    end
  end
end
- if doo?
  = render "foo"
- else
  = render "bar"

こんな感じになるかと思います。

viewにロジックが散らばると、同一条件で別の場所で表示するときにまた書かないといけなくなります。

ロジックをviewでガリガリ書くのはダメですね。

javascriptとrubyのクロージャーについて

クロージャーは「ローカル変数を参照する関数」です。

このいいところは、変数を一時保存できるところだと思ってます。

function closure() {
  let x = 0;
  return function(){
    return ++x;
  }
}

c = closure()
c()
=>1
c()
=>2

何回押したとか、初回だけとかの条件分岐をする場合は、使いやすいのではないでしょうか。

ちなみにrubyだと関数はブロック(Procオブジェクト)で表すことになります。

def counter
  x = 0
  -> {x += 1}
end

c = counter
c.call
=>1
c.call
=>2

こんな感じですね。

以上です。

javascriptの引数について

javascriptの引数についてです。

引数のチェックをjavascriptでは行ってくれません。

ES2015からは改善されているようです。

引数のチェックはしない

function showName(name) {
  console.log(name);
}

showName('foo');
=>foo

showName('foo', 'bar');
=>foo

showName();
=>undefined

これはrubyだと、引数が合わないよって怒ってくるところですね。

def show(name)
  name
end

[4] pry(main)> show('foo')
=> "foo"
[5] pry(main)> show('foo', 'bar')
ArgumentError: wrong number of arguments (given 2, expected 1)
from (pry):5:in `show'
[6] pry(main)> show()
ArgumentError: wrong number of arguments (given 0, expected 1)
from (pry):5:in `show'

引数を持つオブジェクト

argumentsが持っています。

argumentsオブジェクトを利用して、引数の数を確認することができます。

下のような感じですね。

function showName(name) {
  if (arguments.length !== 1) {
    console.log(`given ${arguments.length}, expected 1`)
  }else {
  console.log(name);
  }
}

可変長引数のように書く

可変長引数の場合は、配列で回していくことになります。

下のような感じになります。

function sum() {
  var result = 0;
  for(var i = 0, len = arguments.length; i < len; i++) {
    var tmp = arguments[i]
    if (typeof tmp !== 'number') {
      throw new Error('引数が数値ではありません');
    }
    result += tmp;
  }
  return result;
}
sum(1, 3, 5, 7, 9);

こういうのを見ると、rubyは簡潔に書ける言語だなって感じてしまう。

javascriptのスコープについて

javascriptのスコープについてです。

曖昧な知識のままだったので、まとめます。

ブロックスコープ

ブロックでのスコープという概念がありません。

{
  var i = 5;
}
console.log(i);
=>5

このように平気でアクセスできてしまいます。

rubyだとブロックの中の変数は外からは参照することができません。

Proc.new do
  x = 1
end
p x
=>NameError: undefined local variable or method `x' for main:Object

関数のスコープで制限する

そこで編み出された手法が、関数の中はスコープが存在するので、関数の中に閉じ込めるという手法です。

(function(){
  var i = 5;
  console.log(i);
}).call(this);

console.log(i)
=>Uncaught ReferenceError: i is not defined

先人の人たちはよくこんなことを思いついたものだ。

さすがにこの技ばかりでは可読性がないので、ES2015からは状況が変わりました。

let constでスコープを定める

{
  let i = 5;
  console.log(i);
}
console.log(i)
=>Uncaught ReferenceError: i is not defined

このようにスコープできるようになりました。

以上です。

javascriptの関数の書き方について

最近少しずつ、javascriptについても学習をしています。

当然ながら、rubyとは動きが違うのですが、違う部分を意識するためにも、ここに記載しておきます。

関数の作り方

function命令で定義する

標準的な書き方ですね。

function 関数名 { 処理を記入 }

function hello(name) {
  return `my name is ${name}`;
}
hello("mikami");
"my name is mikami"

Functionコンストラクターで作成する

これは特殊な書き方だと思います。

特にこれで書く利点もないようなので、普通は書かないほうがいいです。

ただ、こんな書き方もあるんだなということで、書いておきます。

var 変数名 = new Function(引数, 処理を記入)

var hello = new Function('name', 'return `my name is ${name}`');
hello('mikmi');
"my name is mikami"

関数リテラルで作成する

これもよく利用される書き方だと思います。

var 変数名 = function(引数, 処理)

var hello = function(name) {
  return `my name is ${name}`;
};
hello('mikami');
"my name is mikami"

アロー関数で定義する

ES2015から使用できるようになりました。

coffee script を使用している人は、アロー演算子は馴染みがあるかも。

var hello = (name) => {
  return `my name is ${name}`;
}
hello('mikami');
"my name is mikami"

function命令の処理

javascriptのfunction命令は静的解析で行われます。

そのため、次のようなコードは動きます。

hello('mikami');

function hello(name) {
  return `my name is ${name}`;
};

では、関数リテラルで表現している場合はどうなるでしょうか?

hello('mikami');

var hello = function(name) {
  return `my name is ${name}`;
};

Uncaught TypeError: hello is not a function(…)

関数リテラルの場合は、実行時に評価されています。

ここら辺はちょっとした注意事項だと思います。

以上です。

nginxのパフォーマンスを向上させてみる

前回のエントリーでパフォーマンスの測定ができるようになりました。

今回はnginxのパフォーマンス向上に努めます。

なお、サイトの事前調査では最大リクエスト数の処理数が70なので、限界までリクエストするようにします。

最初に設定前のファイルを見てみましょう。

nginx.conf

user www-data;
worker_processes 4;
pid /run/nginx.pid;

events {
        worker_connections 1024;
        # multi_accept on;
}

http {

        ##
        # Basic Settings
        ##

        sendfile on;
        tcp_nopush on;
        tcp_nodelay on;
        # keepalive_timeout 10;
        # client_header_timeout 10;
        # client_body_timeout 10;
        # reset_timedout_connection on;
        send_timeout 10;
        types_hash_max_size 2048;
        # server_tokens off;

        # server_names_hash_bucket_size 64;
        # server_name_in_redirect off;

        include /etc/nginx/mime.types;
        default_type application/octet-stream;

        ##
        # SSL Settings
        ##

        ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
        ssl_prefer_server_ciphers on;

        ##
        # Logging Settings
        ##

        access_log /var/log/nginx/access.log;
        error_log /var/log/nginx/error.log;

        ##
        # Gzip Settings
        ##

        gzip on;
        gzip_disable "msie6";

        # gzip_vary on;
        # gzip_proxied any;
        # gzip_comp_level 6;
        # gzip_buffers 16 8k;
        # gzip_http_version 1.1;
        # gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

        ##
        # Virtual Host Configs
        ##

        include /etc/nginx/conf.d/*.conf;
        include /etc/nginx/sites-enabled/*;
}

worker数の設定

worker_processes 4;

結果

Concurrency Level:      69
Time taken for tests:   4.444 seconds
Complete requests:      69
Failed requests:        0
Total transferred:      2385261 bytes
HTML transferred:       2334546 bytes
Requests per second:    15.53 [#/sec] (mean)
Time per request:       4443.779 [ms] (mean)
Time per request:       64.403 [ms] (mean, across all concurrent requests)
Transfer rate:          524.18 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:       20   32   5.5     32      39
Processing:   359 2457 1250.9   2577    4403
Waiting:      315 2410 1250.7   2529    4356
Total:        379 2489 1256.1   2608    4442

Percentage of the requests served within a certain time (ms)
  50%   2597
  66%   3101
  75%   3579
  80%   3835
  90%   4261
  95%   4327
  98%   4442
  99%   4442
 100%   4442 (longest request)

私の初期設定では4になっていました。

こちらをautoに切り替えて、nginxに判断させます。

autoの場合は、自動でコア数に応じてくれます。

lscpuでコア数がわかります。

lscpu
Architecture:          x86_64
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                2
On-line CPU(s) list:   0,1
Thread(s) per core:    1
Core(s) per socket:    1
Socket(s):             2
NUMA node(s):          1
Vendor ID:             GenuineIntel
CPU family:            6
Model:                 45
Model name:            Intel(R) Xeon(R) CPU E5-2640 0 @ 2.50GHz
Stepping:              7
CPU MHz:               2499.998
BogoMIPS:              4999.99
Hypervisor vendor:     KVM
Virtualization type:   full
L1d cache:             32K
L1i cache:             32K
L2 cache:              4096K
NUMA node0 CPU(s):     0,1

今回の場合は2ですね。

なぜ、4にしていたのだろうか・・・orz

結果

Concurrency Level:      69
Time taken for tests:   4.041 seconds
Complete requests:      69
Failed requests:        0
Total transferred:      2385261 bytes
HTML transferred:       2334546 bytes
Requests per second:    17.07 [#/sec] (mean)
Time per request:       4041.382 [ms] (mean)
Time per request:       58.571 [ms] (mean, across all concurrent requests)
Transfer rate:          576.38 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:       21   33   5.7     33      46
Processing:   346 2222 1104.8   2259    3993
Waiting:      303 2175 1104.9   2215    3944
Total:        371 2255 1110.2   2292    4039

Percentage of the requests served within a certain time (ms)
  50%   2287
  66%   2794
  75%   3232
  80%   3401
  90%   3784
  95%   3933
  98%   4039
  99%   4039
 100%   4039 (longest request)

比較

Time per request:       4443.779 [ms] (mean)
Time per request:       64.403 [ms] (mean, across all concurrent requests)
Time per request:       4041.382 [ms] (mean)
Time per request:       58.571 [ms] (mean, across all concurrent requests)

わずかながらですが、早くなりました。

mulit accept

同時に受け入れるかどうかでの判定です。 こちらもonにしてあげましょう。

        multi_accept on;

結果

Concurrency Level:      69
Time taken for tests:   3.443 seconds
Complete requests:      69
Failed requests:        0
Total transferred:      2385261 bytes
HTML transferred:       2334546 bytes
Requests per second:    20.04 [#/sec] (mean)
Time per request:       3443.017 [ms] (mean)
Time per request:       49.899 [ms] (mean, across all concurrent requests)
Transfer rate:          676.55 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:       22   33   5.8     36      39
Processing:   247 1815 979.8   1883    3402
Waiting:      200 1766 977.1   1836    3359
Total:        270 1848 985.3   1920    3441

Percentage of the requests served within a certain time (ms)
  50%   1919
  66%   2331
  75%   2694
  80%   2841
  90%   3207
  95%   3368
  98%   3441
  99%   3441
 100%   3441 (longest request)

どんどん早くなっていく。

Time per request:       3443.017 [ms] (mean)
Time per request:       49.899 [ms] (mean, across all concurrent requests)

こんな設定するだけでも、トータルのレスポンスの処理速度が1,000msも短縮できました。

わずかな設定でここまで差が出るとは・・・

ちゃんとサーバーの設定を見直すことは大事ですね。

Apache Benchでパフォーマンス測定する

サイト作成していて、負荷テストにはまっていました。

便利なパフォーマンス測定ができるものがありますね。

パフォーマンス測定

Apache Bench

こちらを使用すれば、サイトに負荷をかけてレスポンスの向上するのかが確かめることができます。

ab -n 100 -c 100 http://www.example.co.jp/

-nはTotalで送るリクエストになります。 -cは同時に送る数になります。

この場合は、100のリクエストを同時に100個投げるようになります。

実験結果を出すために、今回は50にしてみます。

本当はexample.comには送信していなくて、実験中のサイトになります。

ab -n 50 -c 50 http://www.example.co.jp/

実験結果は次のようになります。

Server Software:        nginx/1.6.2
Server Hostname:        example.com
Server Port:            80

Document Path:          /
Document Length:        33834 bytes

Concurrency Level:      50
Time taken for tests:   2.817 seconds
Complete requests:      50
Failed requests:        0
Total transferred:      1728450 bytes
HTML transferred:       1691700 bytes
Requests per second:    17.75 [#/sec] (mean)
Time per request:       2817.040 [ms] (mean)
Time per request:       56.341 [ms] (mean, across all concurrent requests)
Transfer rate:          599.19 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:       51   61   4.0     63      64
Processing:   152 1236 680.3   1222    2753
Waiting:      126 1186 647.9   1192    2416
Total:        203 1297 683.1   1284    2816

Percentage of the requests served within a certain time (ms)
  50%   1284
  66%   1743
  75%   1809
  80%   2019
  90%   2261
  95%   2354
  98%   2816
  99%   2816
 100%   2816 (longest request)

リクエストがどのくらい成功しているかの判定

Complete requests:      50
Failed requests:        0

こちらになります。 今回の場合は、全部捌けていますね。

秒間捌けるリクエスト数

Requests per second:    17.75 [#/sec] (mean)

17アクセス捌いていたことになります。

リクエストの処理時間

Time per request:       56.341 [ms] (mean, across all concurrent requests)

こちらになります。

56msになります。

ベンチマーク取ることで、速度向上しているかがわかるようになります。

参考:

qiita.com

factory girlでnamespaceのあるモデルのfactoryを生成する

ActsAsTaggableOnのデータを生成したかったのですが、namespaceがあるために生成できませんでした。

そんな時の技です。

FactoryGirl.define do
  factory :tag, class: ActsAsTaggableOn::Tag do |f|
    f.name '脈あり'
  end
end

公式にもっといいのがありました。

Find factories registered for namespaced models by monkbroc · Pull Request #843 · thoughtbot/factory_girl · GitHub

FactoryGirl.define do
  factory :tag, class: ActsAsTaggableOn::Tag do
    name '脈あり'
  end
end

以上です。

factory girlのtraitで関連するモデルを作成するのを選択制にする

関連するモデルを作成する場合は、associationを使います。

ただ、必ずしも毎回そのモデルが必要になるのか?と言われると疑問が浮かびます。

なので、選択制にする方が自由度が高いと考えています。

taritは選択制にできるので、使い方を書きます。

trait

factoryの部分はこう書きます。

  factory :foo do
    title 'foo'
    body 'bar'
    user_id 1

    trait :with_user do
      association :user, factory: :user
    end

呼び出す側は、こう書きます。

create(:foo) { :foo, :with_user }

これで選択制にすることができます。

以上です。