Rails AR 속도 테스트

전 포스팅에 이어 각 케이스별 Benchmark 결과 공유해볼까 한다.
Benchmark 사용법 및 자세한 내용은 아래 링크를 확인하시면 된다.
http://www.ruby-doc.org/stdlib-2.0/libdoc/benchmark/rdoc/Benchmark.html

1. 우리가 흔히 사용하는 100% AR을 사용한 경우.
puts Benchmark.measure{User.limit(100000).to_a;nil}
120.070000 1.790000 121.860000 (122.293380)
총 소모된 시간 약 122초

2. 컨넥션만 AR을 이용한 후 row값을 배열로 받는 경우
puts Benchmark.measure{ActiveRecord::Base.connection().select_rows(‘select * from users limit 100000’);nil}
8.110000 1.560000 9.670000 ( 9.995749)
총 소모된 시간 약 10초

3. 컨넥션만 AR을 이용한 후 row값을 해쉬로 받는 경우 (mysql2만 사용한것과 같은 리턴 타입)
puts Benchmark.measure{ActiveRecord::Base.connection().select(‘select * from users limit 100000’);nil}
8.790000 1.320000 10.110000 ( 10.415056)
총 소모된 시간 약 10초

4. 컨넥션만 AR을 이용한 후 날것(raw)로 받는 경우
Benchmark.measure{ActiveRecord::Base.connection().execute(‘select * from users limit 100000’);nil}
0.160000 0.030000 0.190000 ( 0.501308)
총 소모된 시간 약 0.5초

5. mysql2를 이용한 경우.
puts Benchmark.measure{mysql.query(“select * from users limit 100000”);nil}
0.150000 0.030000 0.180000 ( 0.469528)
총 소모된 시간 약 0.5초

6. mysql2에서 row값을 Array로 받는 경우.
puts Benchmark.measure{mysql.query(“select * from users limit 100000”, as: :array).to_a;nil}
0.140000 0.020000 0.160000 ( 0.474315)
총 소모된 시간 약 0.5초

7. mysql2에서 결과값을 Array로 변환한 경우.
puts Benchmark.measure{mysql.query(“select * from users limit 100000”).to_a;nil}
27.890000 3.050000 30.940000 ( 31.259377)
총 소모된 시간 약 31초

같은 결과값 타입끼리 비교를 해보면

결과값 타입 AR mysql
배열 2. 컨넥션만 AR을 이용한 후 리턴값을 배열로 받는 경우
약 10초
7. mysql2에서 타입을 Array로 요청한 경우.
약 0.5초
해쉬 3. 컨넥션만 AR을 이용한 후 리턴값을 해쉬로 받는 경우
약 10초
5. mysql2를 이용한 경우.
약 0.5초

mysql2의 압도적인 승리다.

사실 4.AR이용 날것(raw)과 5.mysql2의 리턴받는 객체는 똑같다.
둘다 Mysql2::Result를 따라간다.
하지만 4번은 Array로 형변환을 하고, 5번은 Hash로 형변환을 한다는 차이점이 있다.
Screen Shot 2013-06-14 at 10.22.25 PM

Ruby 2.0.0-p0 설치하고 rbenv에 적용하기.

Ruby a programmer's Best Friend

확인해보니 ruby-build2.0.0-p0이 등록이 되어있다.

그냥 rbenv를 이용해서 설치하면 된다.

만약 리스트에 없다면 brew upgrade하고,

rbenv와 ruby-build가 리스트업되어 있는지 확인한 다음

2.0.0-p0을 인스톨하면 된다.

$ brew update
$ brew upgrade
$ rbenv install -l
$ rbenv install 2.0.0-p0

구지 아래 방법으로 올릴 필요 없다!


ruby 2.0.0-p0이 릴리즈 되었다.

하지만 아직 rbenv에 ruby 2.0.0-p0이 리스트업되지 않았다. (정확하게 말하면 ruby-build에 리스트업이 안된것임)

그렇다면 당신은 어떻할 것인가?

  • 뭐. 급한것도 아닌데.. 리스트업될때까지 기다렸다 쓰지 뭐.
  • 오.. 난 지금 당장 깔아서 확인 해봐야겠어!

본 포스팅은 당연히 후자같은 사람들을 위한 포스팅이다.

미리 말해두지만,

rbenv와 전혀 충돌이 일어나지 않는다. ruby-build에 2.0.0-p0이 추가된다 하더라도 전혀 문제 될 것 없다.

왜냐면 지금 할려고 하는일이, rbenv로 2.0.0-p0을 설치하는것과 동일한 과정을 수동(?)으로 하는것이기 때문이다.

1. Install Ruby 2.0.0-p0

http://www.ruby-lang.org/en/news/2013/02/24/ruby-2-0-0-p0-is-released/

위 링크로 가서 자기가 원하는 압축형식의 ruby 2.0.0-p0을 다운받고 압축을 푼다.

2. Make Ruby 2.0.0-p0

Make 하기전에 prefix를 configure해야 한다.

prefix는 rbenv에서 관리하는 ruby들이 설치되어 있는곳으로 한다. (응? 무슨말이냐고? 아래 쉘명령어 보면 이해될것임)

그리고 make!

$ ./configure --prefix=$HOME/.rbenv/versions/2.0.0-p0
$ make
$ make test
$ make install

3. rbenv에 등록하기

rbenv rehash한 후 versions를 확인해보면 2.0.0-p0이 등록된걸 확인할 수 있다.

$ rbenv rehash
$ rbenv versions
rbenv versions

rbenv versions

설치 및 등록은 끝!

이제 rbenv에서 원하는 버전을 선택하기만 하면 된다.

$ rbenv global 2.0.0-p0
$ rbenv versions
$ ruby -v
rbenv global 2.0.0-p0rbenv versions ruby -v

rbenv global 2.0.0-p0
rbenv versions
ruby -v

carrierwave. (re)create image

carrierwave사용시, 새로운 version을 추가하거나 기존의 version중 process를 변경해야 하는 경우가 종종 발생합니다.

이때 새로 만든 혹은 변경된 version의 이미지를 생성을 해야 하는데, 방법은 크게 2가지 입니다.

  • 이미지을 재업로드해서, 이미지를 만드는 방법
  • 해당 이미지만 만드는 방법

1. 이미지 재업로드 하는 방법

은 웹페이지에서 일일이 하나씩 해주는 훌륭한 방법이 있습니다.

책 삽질마스터 표지

하지만 좀 더 아름다운 방법은 rails console에서 코드 한줄로 해결하는 방법이죠.

User.all.each { |u| u.update_attributes(profile: "#{Rails.root}/public/#{u.profile_url}") if u.profile.present? }

version 변경이 자주 일어나는게 아니라면, 위 코드 한줄로 해결하는것도 나쁘지 않은 방법입니다.
다만.이렇게 하게되면 model에 걸려있는 filter들도 함께 동작을 하게 됨으로 불필요한 리소스를 낭비하게 됩니다.

그래서

2. 해당 이미지만 만드는 방법

으로 가는것이 좀 더 나은 방법입니다.

여기서 miniMagick을 이용하는 모듈로 만들수 도 있는데, 상황에 따라서 RMagick을 쓸 수 도 있는것이라 Carrierwave에 모듈을 추가하는 방법이 안정적입니다.

사실 Carrierwave에 recreate_versions! 이란 메소드가 있습니다.
recreate_versions!은 모든 version을 다 만드는건데, (하단 ps.2참고)
특정 version 하나만 만드는 함수는 없습니다.

그래서 특정 version 하나만 만드는 함수를 만들어야 하는데.. 다행스럽게도 누군가가 코드를 짜놓았더군요.

감사하는 마음을 가지고 그대로 써먹습니다.

아래 코드를 아무대나 넣어줍니다.

저의 경우 config/initializers에 넣었습니다.

그리고 난 후

console창으로 가셔서 uploader.recreate_version!([VERSION]) 해주시면 됩니다.

예)

User.all.each { |user| user.profile.recreate_version!([VERSION]) }

근데, version 수정이 비일비재하게 일어난다면..

그때마다 console들어가서 일일이 타이핑해주기가 여간 귀찮은게 아닙니다.

그래서 간결하게 밖에서 과업을 수행할 수 있도록 task로 만들어봤습니다.

방법은 간단합니다.

위 코드를  lib/안에 적당한 파일명으로 넣습니다.

사용법은

rake generate_photo_version[“모델명”,”업로더이름”,”새로운 썸네일을 생성할 버전명”,”오버라이트 할것인지”]
입니다.
예를들면
  • User라는 모델에
  • profile이라는 uploader가 있고
  • normal 이라는 version을 추가했다면

rake generate_photo_version[user,profile,normal,false]

다른예로..

version :normal의 process를 변경하여.. 이미지를 새로 만들어야 할때


rake generate_photo_version[user,profile,normal,true]

하시면 됩니다.

PS

  1.  속도차이는 원본의 용량과 관계가 있을뿐,
    이미지 한개를 만들든 10개를 만들든.. 속도 차이는 미비합니다.
  2. 만약 모든 version을 다 다시 만들고 싶은 경우
    recreate_versions!를 사용하시면 됩니다. (https://github.com/jnicklas/carrierwave/blob/master/lib/carrierwave/uploader/versions.rb)
    사용예) User.first.profile.recreate_versions!
  3. http://menonrails.com/articles/33 에 투고되었습니다.

참고링크

간만에 Rails왔더니 Rails3로 업데이트가… #1

한동안 IPhone, IPad, Android 개발하다가, 잠들어 있던 개인프로젝트를 다시 시작할려고 맘을 먹었다.

Rails2 기반이어서, 깔끔하게 새로 만들자는 마음으로 Rails3를 설치를 했다.
Rails3로 넘어가면서 어떤것들이 바뀌었나 찾아보고 Railscasts도 보면서 틈틈이 학습을 해놓았지만..
역시 난관은 있었다.
내가 부닥쳤던 난관들을 정리해서 올리면, 어떤분은(한분이라도..) 도움을 받으시지 않을까 하는 소박한 희망에 몇자 끄적여본다.
이상하다. 문명히 예전에는 plugin install하고 script/generator 로 initialize를 했었는데..
Could not find generator authentiaceted.
이리저리 찾아봐도 별다른 말은 없고 technoweenie의 restful-authentication이 아닌 satin의 restful-authentication만 딸랑 링크가 걸려있다.
Sation의 restful-authentication의 install 설명을 봐도, 내가 한것과 틀린것이 없다.
아 뭐지 -ㅅ-;;
그러던 찰라
“아. technoweenie가 아직 Rails3 버전을 안맞들었고, Sation은 작업을 해놓은건가?” 라는 의문이 생기고..
Sation걸로 install을 하고 initialize하니 잘 된다.
하지만 막상 사용할려고 하면
uninitialized constant ApplicationController::AuthenticatedSystem

라고 한다.

include AuthenticatedSystem

require File.join(Rails.root, ‘lib’, ‘authenticated_system.rb’)
로 바꿔주니깐 되긴 하는데, lib아래 있는 모든 파일을 이렇게 불러올 수 는 없는 노릇.
뭔가 더 스마트한 방법이 있을것 같긴한데.. Railscasts에서 이 내용을 본것같아 이리저리 뒤져봐도 못찾고.
결국 구글링으로 알아냈다.
application.rb 에다가
config.autoload_paths << File.join(config.root, “lib”)
해주면 된다.
2. haml
예전에는 gem으로 설치하고, 그 후에 plugin install을 해줬어야 했다.
난 당연히 plugin install을 했는데, 왠걸 initialize가 제대로 안된다.
vendor/plugin/haml보니 init.rb은 있는데 0 bytes고..
haml을 initialize하면 생기는 파일은 init.rb 딸랑 하나인데, 이 녀석이 하는 일은 haml gem을 불러오는것 뿐이다.
그런데…..
Rails3부터 bundler가 추가되면서 Gemfile이라는게 생겼다, init.rb가 존재해야 할 명분이 사라져 버린것이다.
init.rb가 하던일을 Gemfile에 gem ‘haml’ 적어주면 끝나니 말이다.
Gemfile에
gem ‘haml’
을 적어주고,
$user> bundle install
하면 끝.
3. Routes => :path_prefix
내가 작업했던 수없이 많은 개인 프로젝트(오픈한것은 NA;;)들의 Routes에는 :path_prefix가 꼭 사용되었었다.
근데, Rails 3로 오면서 이녀석 Deprecated…..;;
혹시나 하는 마음에
scope “:user_login” do

해봤는데 안된다.

혹시나 하는 마음에 한번더 시도를 해보았는데
scope “(/:user_login)” do
이렇게 하니 된다.
예전에는 resources 하나하나 모두 :path_prefix를 적어주어야 하는 번거로움이 있었는데
이와 같은 방식으로 변해서, 보다 깔끔하게 코드를 작성할 수 있게 되었다.
ps. 작업하다 추가되는것들은 모아놨다가 또 포스팅할 계획임.
ps2. 한국루비사용자모임도 죽은지 오래.. 살아있는 Ruby Forum이 없는것 같은데.. 하나 만들까 -ㅅ-;
도메인은 사놓은게 있긴 한데..

mac에서 rails하는 사람들이 snow leopard 설치시 주의할 점

이 문제들을 해결하느라 하루를 허비했네요.

 
간단정리
  1. Install “Xcode.mpkg”(snow leopard)
  2. data backup
  3. install MySQL 64bit
  4. gem install mysql for 64bit
  5. port update
  6. other gems re-install
 
 
Install Snow Leopard
일반설치를 하시면 안됩니다. 꼭 “선택설치(Optional Installs)”를 클릭하신 후 “Xcode.mpkg”를 설치하셔야 합니다. 
“Xcode.mpkg” 클릭 후 설치과정에서 옵션들을 선택하는 페이지가 있는데 그대로 설치를 하시면 됩니다.


여담. 저는 Xcode.mpkg로 설치를 안했어서.. 이것 때문에 한참 고생했습니다.
 
MySQL
Snow Leopard가 되면서 64bit로 Upgrade를 해야됩니다. 즉 재설치를 해야 한다는 말인데, 이렇게 되면 기존의 data들이 뿅~* 하고 날라갑니다. 그러므로 필요한 data들은 backup을 하세요.
mysqldump -u username -ppassword database_name > dump.sql

모든 databases를 backup하시기 원하시면

mysqldump -u username -ppassword –all-databases > dump.sql 
 
자 이제 최신 버전(10.5)의 64bit MySQL을 다운받습니다. (10.5 MySQL받기)
다운받은 dmg를 mount 시켜보면 4개의 파일들이 있습니다.

ReadMe를 제외한 나머지 3개 모두 설치를 합니다.

 
설치 순서는
  1. mysql-5.1.38-osx10.5-x86_64.pkg (mysql입니다.)
  2. MySQLStartupItem.pkg (mac이 booting할때 mysqld를 자동 실행합니다.)
  3. MySQL.prefPane (시스템 환경설정에 MySQL panel을 추가합니다)
그 후 
sudo env ARCHFLAGS=”-arch x86_64″ gem install mysql — –with-mysql-config=/usr/local/mysql/bin/mysql_config

하여 mysql gem을 설치합니다.

 
백업했던 데이터들을 복구합니다.
 
mysql -u username -ppassword database_name < dump.sql 


자. 그럼 console을 열어서 제대로 동작하는지 확인을 해봅니다.
그런데 만약 아래와 같은 Error Message가 나온다면
 
uninitialized constant MysqlCompat::MysqlRes
 
mysql  gem이 여러개 깔려 있어서 그런것입니다.
 
sudo gem uninstall mysql
 
깔려있는 mysql gem들을 삭제후 재설치를 합니다. (이 부분은 아무리 뒤져봐도 정보가 없어, 혼자 삽질하다가 알아냈습니다.)
 
sudo env ARCHFLAGS=”-arch x86_64″ gem install mysql — –with-mysql-config=/usr/local/mysql/bin/mysql_config
 
MacPort
 
그 후 port를 upgrade해야 하는데.
 
sudo port selfupdate sudo port sync sudo port upgrade –force installed #아직 준비가 덜 된것들이 있어서 이 부분은 안하시는게 좋아요. 이것때문에 엄청 삽질했어요.

하시면 됩니다. 그런데 만약 아래와 같은 Error Message가 뜬다면

 
dlopen(/opt/local/share/macports/Tcl/pextlib1.0/Pextlib.dylib, 10): no suitable image found.

snow leopard용 port를 설치하신 후 upgrade하시면 됩니다. (snow leopard용 port 다운받기)
 
 
other gems
MySQL말고도 여러 gem들이 문제가 되고 있습니다. 이러한 gem들은 재설치를 해야 하는데, script를 이용하시거나, irb에서 명령어를 치셔서 하실 수 도 있습니다.
만약 gem들에서 문제가 발생하지 않는다면, 구지 재설치 하실 필요는 없습니다.
`gem list`.each_line {|line| `sudo env ARCHFLAGS=”-arch x86_64″ gem install #{line.split.first}`}
 
참고링크
ps. Mac에서 Safari로 글 썻더니.. 행간이 정리가 안되네요.
아 글이 무지 지저분해졌어 ㅠ,.ㅠ
 

같은 Model을 has_many :through하기

제목을 뭐로 해야할지 참 애매하네요.
지금 만들려고 하는 Join Table은, 같은 Model을 향하는 Join Table입니다.(말이 어렵네요. ㅎㅎ)


Subscription 이라는 Join Table은 user_id, writer_id를 가지고 있습니다.
user_id, writer_id 모두 User Model을 가르키는 ID를 가지고 있죠.

제가 하고 싶은것은
User.subscriptions # User가 구독하고 있는 모든 Users
User.subscribers # User를 구독하고 있는 모든 Users
입니다.

Join Table명을 (딱히 좋은 이름이 생각안나서) Subscript로 변경하겠습니다.

class Subscript < ActiveRecord::Base
belongs_to :user
belongs_to :writer, :foreign_key => “writer_id”, :class_name => “User”
end

 

class User < ActiveRecord::Base
has_many :subscripts #내(Logged User)가 구독하고 있는 User ids
has_many :subscriptions, :through => :subscripts, :source => :writer #내가 구독하는 Users

has_many :subscribes, :foreign_key => “writer_id”, :class_name => “Subscript” #날 구독하는 User ids
has_many :subscribers, :through => :subscribes, :source => :user #날 구독하는 Users
end

코드 설명을 세세하게 하면 싫어하실것 같아 간략하게 코드 설명을 하겠습니다.
Subscript의 writer_id를 이용하는 새로운 관계(belongs_to)를 만듭니다.
그리고 그 만든 관계명을 User에서 writer_id로 User를 불러와야 하는 곳(:subscriptions)의 :source로 써준 것이죠.

좀 찜찜하지만 이런식으로도 구현할 수 있습니다.

class User < ActiveRecord::Base
has_many :subscripts, :dependent => :destroy
def subscriptions
ids = self.subscripts.collect { |s| s.writer_id }
User.find(ids)
end
end

확장성의 제약이 있겠지만, 사용하는것에는 큰 문제점이 없을듯 하네요
하지만.. 그래도 역시 찜찜하죠? ㅎㅎ

여담>
지금 딱 새벽 6시 20분이네요. 오랜만에 와이프 아침밥이나 해주고 자야겠네요 ㅎㅎ (라고 말하고 기절하기)