Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Facepalm To Foolproof - RailsConf 2016

Facepalm To Foolproof - RailsConf 2016

If you're new to Rails development, or just want some tips on deploying and running in production, this is the talk for you. Relying on real-world experience as part of the Heroku support team, we'll talk through common issues (and a few funny ones) we see when people take their "but it works in development!" app to a production environment.

Video: https://youtu.be/yDJV9mr--Yo

Jon McCartie

May 04, 2016
Tweet

More Decks by Jon McCartie

Other Decks in Technology

Transcript

  1. Facepalm to Foolproof
    Avoiding Common Production Pitfalls
    Jon McCartie
    @jmccartie

    View full-size slide

  2. Hi there! ! I’m Jon McCartie.
    I work at Heroku. I help people fix things.

    View full-size slide

  3. AirlessBNB
    Vacation Rentals In Outer Space

    View full-size slide

  4. $ ab -c 100 -n 500 http://airlessbnb.com/
    Requests per second: 41.53 [#/sec] (mean)
    Time per request: 2407.916 [ms] (mean)
    Time per request: 24.079 [ms]

    View full-size slide

  5. $ rails server -e production
    => Booting WEBrick

    View full-size slide

  6. # Gemfile
    gem 'puma'
    # Procfile
    web: bundle exec puma -C config/puma.rb

    View full-size slide

  7. $ ab -c 100 -n 500 http://airlessbnb.com/
    Requests per second: 41.53 [#/sec] (mean)
    Time per request: 2407.916 [ms] (mean)
    Time per request: 24.079 [ms]

    View full-size slide

  8. $ ab -c 100 -n 500 http://airlessbnb.com/
    Requests per second: 82.94 [#/sec] (mean)
    Time per request: 1205.721 [ms] (mean)
    Time per request: 12.057 [ms]

    View full-size slide

  9. Memory Enemies
    1. Memory leaks
    - https:/
    /github.com/schneems/derailed_benchmarks
    - https:/
    /blog.codeship.com/debugging-a-memory-leak-on-heroku/
    2. Too many web processes
    - how much memory does your app consume in one web worker?
    - how much memory do you have?

    View full-size slide

  10. $ free -m
    total used free
    Mem: 8014 7528 486
    -/+ buffers/cache: 2446 5567
    Swap: 511 7 504
    Watch Your Memory Usage

    View full-size slide

  11. $ free -m
    total used free
    Mem: 8014 7528 486
    -/+ buffers/cache: 2446 5567
    Swap: 511 7 504
    Watch Your Memory Usage

    View full-size slide

  12. $ free -m
    total used free
    Mem: 8014 7528 486
    -/+ buffers/cache: 2446 5567
    Swap: 511 7 504
    Watch Your Memory Usage

    View full-size slide

  13. $ free -m
    total used free
    Mem: 8014 7528 486
    -/+ buffers/cache: 2446 5567
    Swap: 511 7 504
    Watch Your Memory Usage

    View full-size slide

  14. http:/
    /airlessbnb.com/checkout

    View full-size slide

  15. # config/production.rb
    config.force_ssl = true

    View full-size slide

  16. server {
    listen 80;
    return 301 https://$host$request_uri;
    }
    nginx.conf

    View full-size slide


  17. 404 - Not Found

    View full-size slide

  18. # this
    /app/assets/images/logo.png
    # becomes this
    /public/assets/logo-a2c31b21374497ad4db1.png
    The Asset Pipeline

    View full-size slide

  19. # Nope

    # Yup
    <%= image_tag "logo.png" %>

    View full-size slide

  20. # Nope
    .logo
    background-image: url("/assets/logo.png")
    # Yup
    .logo
    background-image: image-url("logo.png")

    View full-size slide

  21. # config/initializers/assets.rb
    Rails.application.config.assets.precompile +=
    %w( ads.js ads.css)
    Precompile List

    View full-size slide

  22. "Is this happening a lot?"
    Uhhh… maybe?

    View full-size slide

  23. "But Rails already logs to production.log"
    But we have multiple machines…
    How large is that single production.log file?

    View full-size slide

  24. Logging
    A twelve-factor app never concerns itself with routing or
    storage of its output stream. It should not attempt to write
    to or manage logfiles. Instead, each running process writes its
    event stream, unbuffered, to stdout.
    http:/
    /12factor.net/

    View full-size slide

  25. # config/environment.rb
    config.logger = Logger.new(STDOUT)
    Logging

    View full-size slide

  26. $ ./bin/stop-production && \
    sleep 4 && \
    bundle exec ./bin/start-production

    View full-size slide

  27. $ mina deploy
    -----> Creating the build path
    -----> Cloning the Git repository
    -----> Installing gem dependencies using Bundler
    -----> Moving to releases/4
    -----> Symlinking to current
    -----> Launching
    -----> Done. Deployed v4

    View full-size slide

  28. $ heroku create sushi
    Creating sushi... done
    $ git push heroku master
    ----> Heroku receiving push
    ----> Rails app detected
    ----> Compiled slug size is 8.0MB
    sushi.herokuapp.com deployed to heroku

    View full-size slide

  29. $ cat config/s3.yml
    production:
    bucket: airlessbnb_production
    access_key_id: d6bb03e1e761d
    secret_access_key: 8921377d4ab6da9

    View full-size slide

  30. # config/secrets.yml (.gitignore!)
    development:
    some_api_key: SOMEKEY
    # Usage
    Rails.application.secrets.some_api_key
    Storing Credentials

    View full-size slide

  31. # Gemfile

    gem "dotenv-rails"

    # .env (.gitignore!)

    SECRET_KEY=foo
    # on heroku

    heroku config:add SECRET_KEY=foo
    Environment Variables

    View full-size slide

  32. # in your app

    ENV["foo"]
    # example
    REDIS = Redis.new(ENV["REDIS_URL"])
    Environment Variables

    View full-size slide

  33. # Small App (5,761 records)
    User Load (2.3ms) SELECT "users".* FROM "users" WHERE
    "users"."username" = "jmccartie"
    # Large App (231,138 records)
    User Load (51.7ms) SELECT "users".* FROM "users" WHERE
    "users"."username" = "jmccartie"

    View full-size slide

  34. # Small App (5,761 records)
    User Load (2.3ms) SELECT "users".* FROM "users" WHERE
    "users"."username" = "jmccartie"
    # Large App (231,138 records)
    User Load (51.7ms) SELECT "users".* FROM "users" WHERE
    "users"."username" = "jmccartie"

    View full-size slide

  35. # Small App (5,761 records)
    User Load (2.3ms) SELECT "users".* FROM "users" WHERE
    "users"."username" = "jmccartie"
    # Large App (231,138 records)
    User Load (51.7ms) SELECT "users".* FROM "users" WHERE
    "users"."username" = "jmccartie"

    View full-size slide

  36. class AddIndexForUsernameOnUsers < ActiveRecord::Migration
    def change
    add_index :users, :username
    end
    end
    Database Indexes

    View full-size slide

  37. # Before
    User Load (51.7ms) SELECT "users".* FROM "users"
    WHERE "users"."username" = "jmccartie"
    # After
    User Load (1.0ms) SELECT "users".* FROM "users"
    WHERE "users"."username" = "jmccartie"

    View full-size slide

  38. # Before
    User Load (51.7ms) SELECT "users".* FROM "users"
    WHERE "users"."username" = "jmccartie"
    # After
    User Load (1.0ms) SELECT "users".* FROM "users"
    WHERE "users"."username" = "jmccartie"

    View full-size slide

  39. # Problem
    SELECT * FROM users 

    WHERE thing = 'something' 

    AND other_thing = 'something_else'
    # Solution
    add_index :users, [:thing, :other_thing]
    Composite Indexes

    View full-size slide

  40. Intermission
    Just two more chapters in our story…

    View full-size slide

  41. So close…
    I can feel that $1B valuation just around the corner…

    View full-size slide

  42. # photos_controller.rb
    @photos = Photo.order(:name)
    # index.slim
    - @photos do |photo|
    = image_tag photo.url
    p= photo.user.username

    View full-size slide

  43. Photo Load (0.2ms) SELECT "photos".* FROM "photos" ORDER BY name
    User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = 3
    User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1
    User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = 5
    User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = 4
    User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = 6
    User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = 7
    User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = 8
    User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = 9

    View full-size slide

  44. # nope
    @photos = Photo.all
    # yup
    @photos = Photo.includes(:user)
    N+1 Queries

    View full-size slide

  45. # Old
    Photo Load (0.2ms) SELECT "photos".* FROM "photos" ORDER BY name
    User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = 3
    User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1
    User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = 5
    User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = 4
    User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = 2
    N+1 Queries

    View full-size slide

  46. # New
    Photo Load (0.2ms) SELECT "photos".* FROM "photos" LIMIT 0, 30
    User Load (0.4ms) SELECT "users".* FROM "users" WHERE
    "users"."id" IN (3, 1, 5, 4, 2)
    N+1 Queries

    View full-size slide

  47. # Gemfile
    gem 'bullet', group: :development
    N+1 Queries

    View full-size slide

  48. # Logs
    2016-04-25 20:40:17[INFO] N+1 Query: PATH_INFO: /
    photos; model: Photo => associations: [users]·
    Add to your finder: :include => [:users]
    N+1 Queries

    View full-size slide

  49. Request
    Controller
    Response
    S3

    View full-size slide

  50. @user.send_sms!(message)

    View full-size slide

  51. Request
    Controller
    Response
    SMS

    View full-size slide

  52. Background Workers

    View full-size slide

  53. @user.send_sms!(message)

    View full-size slide

  54. SendSmsJob.perform_later(@user, message)

    View full-size slide

  55. Request
    Controller
    Response
    SMS

    View full-size slide

  56. Request
    Controller
    Response
    SMS

    View full-size slide

  57. class SendSmsJob < ActiveJob::Base
    queue_as :default
    def perform(user, message)
    SmsProvider.send(user.phone, message)
    end
    end

    View full-size slide

  58. Running Your App In Production
    Keep learning

    View full-size slide

  59. Thank you!
    Jon McCartie
    @jmccartie
    bit.ly/railsconf16

    View full-size slide

  60. Heroku Office Hours
    Tomorrow, 4:10pm
    Heroku Booth
    Heroku + Rails Core Team

    View full-size slide