- Git clone this app and follow the instructions below.
git clone [email protected]:andela/checkpoint_rails_worst_app.git
Currently, the home page takes this long to load:
...
Article Load (0.5ms) SELECT "articles".* FROM "articles" WHERE "articles"."author_id" = ? [["author_id", 3000]]
Article Load (0.5ms) SELECT "articles".* FROM "articles" WHERE "articles"."author_id" = ? [["author_id", 3001]]
Rendered author/index.html.erb within layouts/application (9615.5ms)
Completed 200 OK in 9793ms (Views: 7236.5ms | ActiveRecord: 2550.1ms)
The view takes 7.2 seconds to load. The AR querying takes 2.5 second to load. The page takes close to 10 seconds to load. That's not great at all. That's just awful.
The stats page is even worse:
Rendered stats/index.html.erb within layouts/application (9.9ms)
Completed 200 OK in 16197ms (Views: 38.0ms | ActiveRecord: 4389.4ms)
It took 16 seconds to load and a lot of the time taken isn't even in the ActiveRecord querying or the view. It's the creation of ruby objects that is taking a lot of time. This will be explained in further detail below.
So, What can we do?
Well, let's focus on improving the view and the AR querying first!
Complete this tutorial first: Jumpstart Lab Tutorial on Querying
- add an index to the correct columns
- implement eager loading vs lazy loading on the right pages.
- replace Ruby lookups with ActiveRecord methods.
- fix html_safe issue.
- page cache or fragment cache the root page.
- No need for testing, but you need to get the time down to a reasonable time for both pages.
- The root page needs to implement includes, pagination, and fragment caching.
great explanation of how to index columns and when
Our non-performant app has many opportunities to index. Just look at our associations. There are many foreign keys in our database...
class Article < ActiveRecord::Base
belongs_to :author
has_many :comments
end
Let's try to get some ids from our Article model.
Look at Ruby:
puts Benchmark.measure {Article.select(:id).collect{|a| a.id}}
Article Load (2.6ms) SELECT "articles"."id" FROM "articles"
0.020000 0.000000 0.020000 ( 0.021821)
The real time is 0.021821 for the Ruby query.
vs ActiveRecord
puts Benchmark.measure {Article.pluck(:id)}
(3.2ms) SELECT "articles"."id" FROM "articles"
0.000000 0.000000 0.000000 ( 0.006992)
The real time is 0.006992 for the AR query. Ruby is about 300% slower.
For example, this code is terribly written in the Author model:
def self.most_prolific_writer
all.sort_by{|a| a.articles.count }.last
end
def self.with_most_upvoted_article
all.sort_by do |auth|
auth.articles.sort_by do |art|
art.upvotes
end.last
end.last
end
Both methods use Ruby methods (sort_by) instead of ActiveRecord. Let's fix that.
This is why variable and method naming is important.
In the show.html.erb for articles, we have this code
<% @articles.comments.each do |com| % >
<%= com.body.html_safe %>
<% end %>
What's wrong with it?
The danger is if comment body are user-generated input...which they are.
See here
Understand now? Fix the problem.
Our main view currently takes 4 seconds to load
Rendered author/index.html.erb within layouts/application (5251.7ms)
Completed 200 OK in 5269ms (Views: 4313.1ms | ActiveRecord: 955.6ms)
Let's fix that. Read this: fragment caching