84
EASY RAILS PERFORMANCE WINS @mraaroncruz

[Srijan Wednesday Webinar] Easy Performance Wins for Your Rails App

Embed Size (px)

Citation preview

EASY RAILS PERFORMANCE WINS@mraaroncruz

AARON CRUZ!!!!@mraaroncruz

AARON CRUZ!!!!• I build bots for startups so

they can better engage with their audience

@mraaroncruz

AARON CRUZ!!!!• I build bots for startups so

they can better engage with their audience

• Been using Rails for ~7 years

@mraaroncruz

WHERE ARE YOU AT?• Beginner

• Expert

• Somewhere in the middle?

PROFILING1st Win

MEASURE ALL THE THINGS

ATTACK THE LARGEST FIRST

HOW TO PROFILE

http://newrelic.com

WITH NEWRELIC YOU CAN MONITOR

WITH NEWRELIC YOU CAN MONITOR

• Slow controller actions

WITH NEWRELIC YOU CAN MONITOR

• Slow controller actions

• Slow SQL analysis

WITH NEWRELIC YOU CAN MONITOR

• Slow controller actions

• Slow SQL analysis

• Time spent in database calls

WITH NEWRELIC YOU CAN MONITOR

• Slow controller actions

• Slow SQL analysis

• Time spent in database calls

• Performance of External Services

WITH NEWRELIC YOU CAN MONITOR

• Slow controller actions

• Slow SQL analysis

• Time spent in database calls

• Performance of External Services

• Much more...

IT’S FREE! 💵 🙏 😭

METRIC FU

CODE QUALITY GEMS

CODE QUALITY GEMS• Reek - finds code smells

CODE QUALITY GEMS• Reek - finds code smells

• Cane - fails your build if code quality thresholds are not met

CODE QUALITY GEMS• Reek - finds code smells

• Cane - fails your build if code quality thresholds are not met

• Many more…

RACK MINIPROFILER

THE DATABASE2nd Win

SOURCE OF MOST PERFORMANCE ISSUES

😿

MOST COMMON ISSUES

MOST COMMON ISSUES• Not using an index

MOST COMMON ISSUES• Not using an index

• Loading too many records into memory (more an application design problem)

MOST COMMON ISSUES• Not using an index

• Loading too many records into memory (more an application design problem)

• Not being able to fit your index into memory

1. USING AN INDEX

WHAT IS AN INDEX?

THE DOWNSIDE OF INDEXES

WHEN TO ADD AN INDEX

HOW TO FIND BOTTLENECKS

SLOW QUERY LOG

MYSQL SLOW LOG

MYSQL SLOW LOG• /etc/mysql/my.cnf

MYSQL SLOW LOG• /etc/mysql/my.cnf

MYSQLDUMPSLOW

A FEW OTHER TOOLS• pt-query-digest

• mysqlsla

• Anemometer

POSTGRESQL SLOW LOG• Open the file postgresql.conf file

• log_min_duration_statement = 100

• Save the file and reload

• Watch the logs (/var/lib/pgsql/data/pg_log/)

EXPLAIN

users=# EXPLAIN ANALYZE SELECT username FROM users JOIN page_views p ON users.id = p.user_id WHERE users.friend_count = 50 LIMIT 100;

QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------- Limit (cost=0.42..4706.66 rows=100 width=11) (actual time=0.324..15.557 rows=100 loops=1) -> Nested Loop (cost=0.42..2172491.96 rows=46162 width=11) (actual time=0.323..15.543 rows=100 loops=1) -> Seq Scan on page_views p (cost=0.00..82430.44 rows=4496144 width=4) (actual time=0.004..0.839 rows=8179 loops=1) -> Index Scan using users_pkey on users (cost=0.42..0.45 rows=1 width=15) (actual time=0.002..0.002 rows=0 loops=8179) Index Cond: (id = p.user_id) Filter: (friend_count = 50) Rows Removed by Filter: 1 Planning time: 0.300 ms Execution time: 15.592 ms (9 rows)

CREATE INDEX ON page_views (user_id);

QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------- Limit (cost=0.43..122.29 rows=100 width=11) (actual time=0.049..0.316 rows=100 loops=1) -> Nested Loop (cost=0.43..56250.88 rows=46162 width=11) (actual time=0.048..0.303 rows=100 loops=1) -> Seq Scan on users (cost=0.00..20834.00 rows=10267 width=15) (actual time=0.020..0.172 rows=21 loops=1) Filter: (friend_count = 50) Rows Removed by Filter: 1110 -> Index Only Scan using page_views_user_id_idx on page_views p (cost=0.43..3.38 rows=7 width=4) (actual time=0.004..0.005 rows=5 loops=21) Index Cond: (user_id = users.id) Heap Fetches: 0 Planning time: 0.314 ms Execution time: 0.355 ms (10 rows)

44 TIMES FASTER😹

2. LOADING TOO MUCH INTO MEMORY

N+1 QUERIES3rd Win

#includes

class Post < ActiveRecord::Base belongs_to :author

scope :published, -> { where(published: true) .order(published_at: :desc) } end

class Author < ActiveRecord::Base has_many :posts end

posts = Post.published.limit(100) posts.each do |post| puts "#{post.title} - by #{post.author.name}" end

# 1 query for articles # 100 queries for authors

Post.includes(:author).published.limit(100) # 2 queries # 1 for posts # 1 for authors

#joins

young_author_posts = Post.joins(:author) .where("authors.birth_year > ?", Date.today - 20.years) .order(:title)

puts "Posts by young authors"

young_author_posts.each |post| puts post.title end

TOOLS

# In ~/.zshrc, ~/.profile or ~/bashrc

alias devlog=“tail -f log/development.log"

USE ALIASES AND BASH FUNCTIONS

VIEW RENDERING4th Win

YOU CAN SPEND A LOT OF TIME RENDERING VIEWS

RACK MINIPROFILER

https://youtu.be/txT-PM8ujLM

Miha Rekar at RubyC 2016 about flame graphs

YOUR VIEW SHOULD BE DUMB😜

REALLY DUMB😜 😜 😜 😜 😜 😜 😜 😜 😜

VIEW CACHING5th Win

💵 CACHE IT! 💵If you can’t speed it up

CACHING IN RAILS IS SOOOO EASY!

<% @products.each do |product| %> <% cache product do %> <%= render product %> <% end %> <% end %>

MODEL CACHING6th Win

Rails.cache.fetchIS YOUR FRIEND

cache.write('today', 'Monday') cache.fetch('today') # => "Monday"

cache.fetch('city') # => nil cache.fetch('city') do 'Duckburgh' end cache.fetch('city') # => "Duckburgh"

class Product < ActiveRecord::Base def competing_price Rails.cache.fetch([cache_key,“competing_price”], expires_in: 12.hours) do Competitor::API.find_price(id) end end end

JS AND CSS7th Win

DELETE WHAT YOU DON’T NEED

PROFILE NOW

FIX YOUR DATABASE

💵 💵 💵 💵 💵 💵 💵

THANK YOU!@mraaroncruz