Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

what's exactly the difference in performance between pagy and kaminari gems, and who's better?

  • in our project, we are using Kaminari in pagination for everything, and it's working pretty well
  • there's a new gem called 'pagy' and in their gem repo on Github they stated that their performance is better than kaminari and will-paginate, but I don't know if it's really better for our project to invest time in converting from kaminari to pagy

so I gave it a try to compare between it and kaminari:

  • I ran the same query that will load all boxes with 1 box per page, and page: 2 , and checked the queries that ran by each gem, and checked the time it takes
2.5.1 :043 > pagy(Box.all, items: 1, page: 2)
   (2.0ms)  SELECT COUNT(*) FROM "boxes"
  Box Load (1.0ms)  SELECT  "boxes".* FROM "boxes" LIMIT $1 OFFSET $2  [["LIMIT", 1], ["OFFSET", 1]]
 => [#<Pagy:0x00007fd7e88073a8 @vars={:page=>2, :items=>1, :outset=>0, :size=>[1, 4, 4, 1], :page_param=>:page, :params=>{}, :anchor=>"", :link_extra=>"", :item_path=>"pagy.info.item_name", :cycle=>false, :count=>106}, @count=106, @items=1, @outset=0, @page=2, @last=106, @pages=106, @offset=1, @from=2, @to=2, @prev=1, @next=3>, #<ActiveRecord::Relation [#<Box id: "2fbf947f-c0d8-4c02-9c71-22d7deaaff2e", shipping_request_id: "7dab8aba-961a-4d28-80c8-20b2fb9a63b7", created_at: "2019-03-14 18:22:33", updated_at: "2019-03-14 18:22:33", external_uuid: "b44f08ec-ccbc-424c-b0aa-4cbcbe94cd74", inventory_id: 78, dimension: {"length"=>4.5, "width"=>5.5, "height"=>5.6, "unit"=>"cm"}, weight: 56.6, label_url: nil, warehouse_state: "processing", proforma_url: nil, barcode: "EXOBOX-B44F08EC", proforma_name: nil, tracking_url: "", private_tracking: true, external_shipment_id: nil, state: "preparing", tracking_history: [{"status"=>"preparing", "datetime"=>"2019-03-14 20:22:33 +0200", "active"=>true, "order"=>0}, {"status"=>"pending_pick_up", "datetime"=>"", "active"=>false, "order"=>1}, {"status"=>"picked_up", "datetime"=>"", "active"=>false, "order"=>2}, {"status"=>"en_route_to_destination", "datetime"=>"", "active"=>false, "order"=>3}, {"status"=>"delivered", "datetime"=>"", "active"=>false, "order"=>4}], parent_id: nil, tracking_number: "EXOTRACK-B44F08EC">]>] 
2.5.1 :044 > Box.all.page(2).per(1)
  Box Load (1.0ms)  SELECT  "boxes".* FROM "boxes" LIMIT $1 OFFSET $2  [["LIMIT", 1], ["OFFSET", 1]]
 => #<ActiveRecord::Relation [#<Box id: "2fbf947f-c0d8-4c02-9c71-22d7deaaff2e", shipping_request_id: "7dab8aba-961a-4d28-80c8-20b2fb9a63b7", created_at: "2019-03-14 18:22:33", updated_at: "2019-03-14 18:22:33", external_uuid: "b44f08ec-ccbc-424c-b0aa-4cbcbe94cd74", inventory_id: 78, dimension: {"length"=>4.5, "width"=>5.5, "height"=>5.6, "unit"=>"cm"}, weight: 56.6, label_url: nil, warehouse_state: "processing", proforma_url: nil, barcode: "EXOBOX-B44F08EC", proforma_name: nil, tracking_url: "", private_tracking: true, external_shipment_id: nil, state: "preparing", tracking_history: [{"status"=>"preparing", "datetime"=>"2019-03-14 20:22:33 +0200", "active"=>true, "order"=>0}, {"status"=>"pending_pick_up", "datetime"=>"", "active"=>false, "order"=>1}, {"status"=>"picked_up", "datetime"=>"", "active"=>false, "order"=>2}, {"status"=>"en_route_to_destination", "datetime"=>"", "active"=>false, "order"=>3}, {"status"=>"delivered", "datetime"=>"", "active"=>false, "order"=>4}], parent_id: nil, tracking_number: "EXOTRACK-B44F08EC">]> 
2.5.1 :045 > 

they're running the same query too (regardless select count from boxes query) , so how can I spot the difference between them, and who's better, It feels like they're both the same, knowing that we're using them in an API Project

like image 215
ELTA Avatar asked Jan 02 '23 02:01

ELTA


1 Answers

The level of advantages depends on how you use the pagination in your API. The more pagination info you pack into the API response, the more convenient is Pagy.

From the DB point of view, if you use a classic pagination that needs to calculate the count of the collection, there is no difference in the query time. And it couldn't be, since all the pagination gems need always to perform the same 2 queries: one for getting the count and another for getting the page of results.

So in that case the improvement is not in the queries... UNLESS your API doesn't need to add the total count of results in the response, e.g. if you just need to get the page of results and the links for previous, next, etc. then you can completely avoid the count query by using the pagy countless extra and that could be a huge improvement.

A big advantage using Pagy over other gems is that it is not only many time faster doing its calculations (you can easily waste 20ms per render with Kaminari, depending on the settings), but also that it uses a small fraction of the memory needed by the other gems. That means that it's a big load-relief for the server especially if your app is high-traffic. Its efficiency amounts to hundreds of times more than Kaminari in normal UI conditions, and it should be still many tens of time more in API conditions even with minimal pagination info in the response.

There is a migration guide that should make the migration from legacy gems quite simple. If your Kaminari usage is standard, it will be a mere matter of minutes (mostly search and replace). If you monkey-patched Kaminari or used it in weird ways (probably not with an API), you may have to read some more Pagy doc or ask for live support.

For your API, you can also take a look at the headers extra which encapsulate the API needs for pagination.

FYI: The Pagy v3 (that will be out soon) will improve the speed and lightness even more (noticeably more), if you use ruby 2.0+ with UI helpers. It is still missing the benchmarks it for APIs, but it will certainly improve the headers extra though.

like image 161
user712557 Avatar answered Jan 05 '23 16:01

user712557