will_paginate, kaminari, offset-limit의 paginate 성능 비교 및 분석

안녕하세요 @darammg 입니다.

이번 포스트는 paginate 시 어떤 방법으로 하느냐에 따른 성능분석 비교 및 분석에 대해 포스팅해 보도록 하겠습니다. 먼저 paginate는 많은 record를 페이지 단위로 나누는 방법이고, 대표적으로 여러 커뮤니티 사이트의 게시판이나 쿠폰모아의 쿠폰정보들을 paginate 해서 보여주고 있습니다.

rails의 예전버전에서는 기본적으로 paginate가 내장되어 있었지만 rails 버전이 2 이상이 되면서 추가 gem 이나 offset-limit 방법으로 구현해 주어야 합니다. 현재 쿠폰모아의 paginate는 will_paginate라는 유명한 gem으로 paginate를 하고 있고 저는 쿠폰모아 개편에 사용될 api를 만들다가 will_paginate의 성능에 관심이 생겨 호기심 차 성능 비교를 해보게 되었습니다.

성능 비교는 will_paginate gem, kaminari gem, offset-limit을 이용한 수동 paginate, 이렇게 3가지 방법으로 진행하였고, paginate 하는 record수는 2000개 정도를 기준으로 하였습니다.(paginate 하는 record 수가 100개 이하라면 속도차이가 거의 나지 않습니다)

결과에 대한 분석을 해보기 전에 will_paginate 와 kaminari, 그리고 offset-limit 방법의 차이를 말씀드리면, will_paginate의 경우 rails 의 active_record를 paginate 하였을 때 Willpaginate::collection 이라는 새로운 type(및 class, 이후 type으로 부르겠습니다)으로 변환해 버립니다.

paginate 와 함께 active_record에서 type이 변환되며 paginate 되는 시간은 오래 걸리지만 루프는 굉장히 빨리 돕니다. 반면에 kaminari나 offset-limit 방법은 active_record type은 그대로 유지 되고 paginate 속도는 굉장히 빠르지만 실제 루프 돌때는 will_paginate 대비 시간이 더 걸립니다.

이것은 active_record의 lazy load 와 관련이 있는 것으로 보이는데, active_record의 경우 선언이 될 때 메모리에 load 하는게 아니고 실제 사용될 때(즉 루프를 돌릴때) 메모리에 올라가서 처리하며 이것을 lazy load 라고 부릅니다.

따라서 Willpaginate::collection 이나 array 타입은 lazy load가 적용되지 않아서 paginate 할때는 시간이 오래 걸리지만 루프 돌 때는 시간이 얼마 소요되지 않은 것이고 active_record 타입은 paginate 할때는 시간이 거의 소요되지 않지만 루프 돌 때는 시간이 많이 소요 되는 것이죠. 결과는 아래와 같습니다.

<클릭하면 크게 보이며, 시간은 초단위 입니다.>

성능 비교는 각 항목마다 5회씩 진행했으며 시간은

time = Time.now
Model.paginate
puts Time.now – time

와 같은 방법으로 측정하였습니다.(환경에 따라 전혀 다른 결과가 나올 수도 있음을 명시합니다)

결과에 대한 분석을 해보면 역시 lazy load와 관계가 있는 것으로 보이며 per_page 30개 기준으로 했을 경우 paginate 되는 속도가 offset-limit > kaminari > will_paginate 순으로 나왔습니다. 하지만 해당 paginate된 정보가 밖으로 뿌려질 때, 즉 루프를 도는 속도는 will_paginate > offset-limit = kaminari 순으로 나왔습니다. paginate 되는 속도와 루프를 도는 속도를 합산하여 보면 offset-limit > kaminari > will_paginate 순으로 보입니다. (per_page 30개 기준)

offset-limit으로 구현한 paginate는 total_count 라던지 뷰단에서 보여주는 page 번호들을 모두 수동으로 해줘야 해서 사용하기에는 불편함이 있지만 역시 다른 작업 없이 직접 쿼리를 날리는 만큼 빠른 속도를 보여주었습니다.

하지만 쿠폰모아에서는 날라온 쿼리 결과를 캐시로 관리하기 때문에 paginate 시간보다 each문을 돌리는데 걸리는 시간이 더 중요할 수 있습니다. kaminari나 offset-limit은 루프를 돌 때 시간이 더 걸리기 때문에 이 문제를 해결하기 위하여 active_record를 array로 변환(to_a 사용) 하면 해당 문제를 해결할 수 있으며 결과는 표에 나와있듯이 만족할 만한 수준이었습니다.

다만 한번에 보여줄 record가 엄청나게 만다면(즉, per_page가 천개이상 혹은 만개이상 된다면) will_paginate로 paginate 하는게 더 효율적입니다. 표에서는 나오지 않았지만 per_page가 엄청 클 경우 will_paginate가 발군의 성능을 보여줍니다.
paginate 되는 시간은 다소 걸리지만 active_record를 array로 변환하는 시간보다는 훨씬 짧으며 루프를 도는 데는 시간이 거의 걸리지 않기 때문입니다.

현재 rails의 paginate gem 중 유명한 gem으로 will_paginate와 kaminari 가 있는데 한번에 보여줄 record가 많아졌을 경우 kaminari gem의 성능 문제를 심심치 않게 외국 커뮤니티사이트에서 볼 수 있었습니다. 이 문제는 active_record가 lazy load를 하기 때문으로 보이며 record가 많지 않을 경우에는 그리 신경 쓸 만한 수치가 아닌 것으로 보입니다.(자세한건 표를 참고하세요 ^^.)

아마 이번 결과를 바탕으로 controller 단에서의 paginate는 offset-limit 과 to_a(array로 type변환)조합으로 하고 뷰단에서는 그대로 will_paginate를 쓸 생각입니다.

혹시나 paginate 관련해서 속도에 관심이 있으신 분들은 저처럼 테스트를 해보시고 최적의 조합을 찾으시길 바라며 각 record 마다 속성이 너무 많이 루프 돌릴 때 오래 걸리셨던 분들은 이참에 lazy load 되지 않는 다른 type로 변환후에 속도 측정을 해보시기 바랍니다.(예를 들면 active_record를 array 로 변환후에 test)

※offset-limit 으로 paginate 하는 것은 Model.offset(per_page * (page-1)).limit(per_page) 으로 하였습니다.
※이번 포스팅에 나오는 per_page는 한페이지에 묶여있는 record 수를 의미하며 page는 페이지번호 입니다. 1~100개 레코드가 있고 page => 2, per_page => 30 이라면 31번부터 60번 레코드가 묶이게 됩니다.

3 Comments

  1. Sunho
    Dec 09, 2011 @ 15:03:56

    안녕하세요~
    루비 사용자 모임에서 서비스를 보고 여기까지 따라 오게 되었습니다^^

    지금은 PHP로 프로젝트를 진행하고 있지만, 예전에 ROR을 사용할때의 느낌이 너무 강렬하게 남아 있어
    아직도 ROR로의 회귀를 망설이고 있네요.

    각설하고 질문이 있어 커맨트를 남깁니다. (초면에 다짜고짜 질문을 드려 죄송합니다.;;)

    지금 ROR로 서비스를 구현하시면서 테스트 코드를 만들고 계신가요?
    혹시 테스트 코드를 만들고 계신다면 커버리지가 어느정도 되는지 알 수 있을까요?

    Reply

    • admin
      Dec 11, 2011 @ 14:21:47

      안녕하세요. 김재현입니다.
      실망스러우실 수 있을 것 같지만, 솔찍히 말씀드리자면, 저희가 테스트코드를 많이 작성하지 못하고 있습니다.
      노력해본 적은 있는데, 쉽지 않네요. 서비스 기획이 빠르게 변화하고, 바로 동작코드를 바로 반영해가며 작업을 하고 있어서, 테스트코드를 작성하지 못하고 있습니다ㅠ;
      테스트코드 작성을 위해 조금 더 노력해야겠다고 항상 생각 합니다.
      질문남겨주셔서 감사합니다.!

      Reply

      • Sunho
        Dec 14, 2011 @ 19:08:30

        실망스럽긴요. 혹시나! 하고 여쭤본겁니다^^
        여지껏 제대로 TDD로 개발한 프로젝트는 경험해본적도, 심지어 본적도 없습니다.
        (자랑이 아닐텐데…)
        정말 그만큼 시작하고 꾸준히 하기 어려운 개념이 아닌가 싶네요. 새해부터는 운동해야지 하는 다짐처럼 말이죠.

        바쁘신 와중에도 이렇게 바로바로 답변 주셔서 정말 고맙습니다. (__)

        Reply

Leave a Reply