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になります。
ベンチマーク取ることで、速度向上しているかがわかるようになります。
参考:
factory girlでnamespaceのあるモデルのfactoryを生成する
ActsAsTaggableOnのデータを生成したかったのですが、namespaceがあるために生成できませんでした。
そんな時の技です。
FactoryGirl.define do factory :tag, class: ActsAsTaggableOn::Tag do |f| f.name '脈あり' end end
公式にもっといいのがありました。
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 }
これで選択制にすることができます。
以上です。