Rails의 AR이 대량의 데이터를 읽어올때 생기는 속도 문제.

DB에는 약 500,000개의 rows가 있다.

A액션을 하게되면
DB에 쿼리를 날리게 되고 약 75,000개의 rows가 return된다.
이 데이터를 가지고 알고리즘을 돌리게 되는데
문제는 고작 75,000개의 데이터를 처리하는데 시간이 너무 오래 걸리는 것이다.
약 80초가 걸리는데 이건 뭔가 문제가 있다.
SELECT 쿼리는 최적화가 되어있는 상태로 문제가 없었다.

도대체 어디서 이렇게 시간을 많이 잡아먹나 확인을 해보니
DB에 쿼리를 날리고 난 후 알고리즘 들어가기 전까지가 엄청나게 많은 시간을 잡아먹는 것이었다.

ActiveRecord가 DB 결과값을 변환하는데 걸린 시간: 약 75초 알고리즘 로직이 돌아간 시간: 약 3.4초

ActiveRecord가 DB 결과값을 변환하는데 걸린 시간: 약 75초
알고리즘 로직이 돌아간 시간: 약 3.4초
총 연산시간: 약 80초

쿼리도 문제없고, 알고리즘도 문제없다.
하지만 둘 사이에 알 수 없는 작업으로 시간이 오래 걸린다면..
의심해볼 건 ORM.. 바로 ActiveRecord(이하 AR)

그래서 과감하게 AR을 버리고 mysql2(gem)만 이용하도록 수정을 했다. (mysql2… mysql을 사용하면 필수적으로 설치하는 그 gem 맞다.)
그랬더니 시간이 급속적으로 향상되었다.

DB 결과값을 변환하는데 걸린 시간: 약 0.3초
알고리즘 로직이 돌아간 시간: 약 0.7초
총 연산시간: 약 1.1초

80초에서 1초로 줄었. 응?
눈을 비비고 다시 한번 확인.
혹시나 싶어서 여러번 A액션 재실행.
헐.. 대박 ㅋㅋ
말도 안되는 성과 도출!
쩔어 쩔어

여기서 주목할 점은 알고리즘부분까지 소모시간이 줄었다는 것이다.
이것 또한 같은 이유로 인함인데
여기서 AR의 동작원리라고 하기엔 좀 거창하지만를 이해할 필요가 있는데
mysql2은 결과값을 해쉬(Hash)로 보관하게 되는데
(엄밀히 따지면 Mysql2::Result에 Hash형태로 각 row가 보관이 된다.)
사실 AR도 mysql2를 이용해서 넘겨받은 해쉬를 AR(데이터 타입)로 변환하게 된다.
알다시피 AR(데이터 타입)에는 각종 모듈 및 헬퍼등 살이 디룩디룩 찌고 다리가 4개인 우르곳이라면
반면 해쉬는 Ruby Core lib 모듈 포함된 녀석으로 아주 앙상하게 마른 뼈밖에 없는 피들스틱이랄까?
(Rails에서 사용하는 해쉬는 포식시켜 스택이 3정도 쌓은 초가스랄까? 뭐래는겨 ㅋㅋㅋ)

결론
결국 AR이 무겁고, 이렇게 무거운 AR을 사용하기 때문에 생기는 이슈인 것이다.
그렇다고 AR이 나쁘다! 구리다! 하자다! 라고 하기엔 어폐가 있다.
Rails는 웹 개발 프래임웍이다. 웹 개발이 목적이고 이를 위해 만들어진 ORM이 AR이다.
이런 AR에게 데이타를 몇 만건씩 가져오는걸 기대하는것 자체가 문제가 있다.
보편적인 웹 서비스에선 몇 만건씩 데이터를 가져오는 일 따위는 일어나지 않을것이니

그러므로 배치성 작업 혹은 많은 량의 데이트를 컨트롤해야 하는 경우에
Rails가 필요 없다면 그냥 Ruby로만 짜는것이 좋고,
Rails를 써야만 한다면 AR사용은 고민해볼 필요가 있다.

덤.
3,500개의 rows를 AR이 변환하는데 걸리는데 1.8초정도 소모가 되었다.
고작 3,500개밖에 안되는데도 약 2초가 소모된다.
소량의 작업에선 속도가 문제가 안되지만..
1,000건이 넘어갈 경우에는 꼭 테스트를 해보길 권장한다.

+ 추가
아직 확인은 못해봤지만.. Sangmin Ryu님의 실서비스 테스트 결과를 보면 속도가 느리지 않다
물런 하드웨어 스펙이 차이가 좀 많이 나는것 같지만.. ㅋㅋ 그걸로 설명하기엔 속도차가 너무 크다.
모델의 덩치와도 관계가 있다고 추측된다.
일단 해당 이슈가 발생한 모델의 필드가 약 50개이고 그 중 10개는 text이다.
뿐만 아니라 메소드 또한 많은데 이 모델의 소스 코드가 약 600줄정도 된다.

AR이 해야할 작업이 많아져서 속도가 느려졌던걸로 판단된다.

+ 추가
AR 속도 테스트

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로 글 썻더니.. 행간이 정리가 안되네요.
아 글이 무지 지저분해졌어 ㅠ,.ㅠ