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

Why Rails? And Can It Scale?

Why Rails? And Can It Scale?

December 2, 2021 talk I gave to the CS 169 (Software Engineering) class.

I talk about two questions: why Rails? And can Rails scale?

Links to sources (last slide):

The Rails Doctrine, by David Heinemeier Hansson: https://rubyonrails.org/doctrine/

Ruby on Rails Guides: https://guides.rubyonrails.org/

When Should You NOT Use Rails?, by Noah Gibbs: https://codefol.io/posts/when-should-you-not-use-rails/

Selecting a programming language can be a form of premature optimization, by Brett Cannon: https://snarky.ca/programming-language-selection-is-a-form-of-premature-optimization/

The Practical Effects of the GVL on Scaling in Ruby, by Nate Berkopec: https://www.speedshop.co/2020/05/11/the-ruby-gvl-and-scaling.html

RubyConf Taiwan 2019 - The Journey to One Million, by Samuel Williams: https://www.youtube.com/watch?v=Dtn9Uudw4Mo

Ruby 3.0 and the new FiberScheduler interface, by Wander Hillen: https://www.wjwh.eu/posts/2020-12-28-ruby-fiber-scheduler-c-extension.html

Opening The Ruby Concurrency Toolbox, by Alex Braha Stoll: https://www.honeybadger.io/blog/ruby-concurrency-parallelism/

Masters of Scale podcast episode 92, interview with Michael Seibel: https://mastersofscale.com/#/michael-seibel/

Shopify’s massive storefront rewrite, The Changelog episode #416: https://changelog.com/podcast/416

Brian Morearty

December 02, 2021
Tweet

More Decks by Brian Morearty

Other Decks in Programming

Transcript

  1. Brian Morearty | December 2, 2021 | UC Berkeley CS

    169 @BMorearty [email protected] Why Rails? And can it scale?
  2. Who Am I? Brian Morearty Retired software engineer. Cal grad

    (CS ’88). Using Ruby on Rails since 2007. Most recently at Airbnb, where I taught Rails to all new engineers for 5 years.
  3. Who Am I? Brian Morearty Retired software engineer. Cal grad

    (CS ’88). Using Ruby on Rails since 2007. Most recently at Airbnb, where I taught Rails to all new engineers for 5 years. Brian and friends, freshman year
  4. Velocity: you gotta build fast • When building a new

    product, Rails convention over con fi guration lets you focus on your product instead of making tons of technical decisions. • When working on an existing product, Rails is a very productive environment that enables you to move quickly. • Rich framework 🤑 saves you time: secure by default, great ORM, etc.
  5. Were Airbnb devs glad they started with Rails? Even though

    the company is switching to mostly Java services now? • One day I asked one of the fi rst Airbnb engineers this question. • His answer was de fi nitely yes. They were very happy with Rails. It enabled the team to move quickly (velocity) in the early days.
  6. - Noah Gibbs, When Should You NOT Use Rails? “If

    you’re building something quickly to see if anybody cares, I know of no framework that comes close to its productivity. Wait to rewrite (in a more rigid framework) until you’re successful and you can afford the drag on your development speed.”
  7. Some ways Rails improves your velocity Convention over con fi

    guration Who cares what format your database primary keys are described by? Does it really matter whether it's “id”, “postId”, “posts_id”, or “pid”? Is this a decision that’s worthy of recurrent deliberation? No. … There are thousands of such decisions that just need to be made once, and if someone else can do it for you, all the better. - David Heinemeier Hansson (DHH)
 The Rails Doctrine
  8. Some ways Rails improves your velocity Rails has default sub-frameworks

    • Integrated system with a default set of large-grained parts. Interfaces for interop. • E.g. a test framework (MiniTest), an ORM (ActiveRecord), a rendering framework (ERb), etc. • Substitutions are still possible, but not required. Like an OS with a default fi lesystem. • E.g., RSpec is more popular than the default MiniTest
  9. Some ways Rails improves your velocity Secure by default. Protects

    against threats like: • Session hijacking • Session fi xation • Cross-site request forgery (CSRF) • Logging sensitive data • Privilege escalation • SQL injection • Cross-site scripting (XSS) • CSS injection • Ajax injection • Header injection • Unsafe query generation • See Securing Rails Applications online for more info. Also see OWASP.
  10. Some ways Rails improves your velocity No waiting for compiler

    xkcd by Randall Munroe is licensed under CC BY-NC 2.5
  11. Some ways Rails improves your velocity The Rails console •

    A great REPL. • Code playground. See what queries execute, etc.
  12. Some ways Rails improves your velocity Rails Generators • Often-overlooked

    productivity feature of Rails • Improves consistent usage, makes it easier to switch between Rails apps • My favorites are sca ff old and model: • rails g scaffold ResourceName column:type column:type • Generates a model, controller, route, views, migration, and tests • rails g model ModelName column:type column:type • Generates a model, migration, and tests (for back-end data with no UI)
  13. Some ways Rails improves your velocity CRUD - Create, Read,

    Update, Delete • Let’s say you want to build a CRUD web app that reads and writes a SQL database. Rails is ideal for this. • Track listings, hosts, guests, reservations (Airbnb). • Track sellers, stores, products, inventory (Shopify). • Track payments, refunds, credit cards, vendors (Square). • Resources: things your app deals with. Usually stored in tables. • Rails also has rich support for RESTful APIs, e.g. for web and mobile front-ends to call.
  14. Non-CRUD Rails is not as obvious a fi t here

    • Rails has also used at the start by some apps you wouldn’t necessarily consider CRUD: • GitHub (still very committed to Rails) • Twitch (now Go microservices + React front end) • Hulu (now Next.js)
  15. ActiveRecord The Object-Relational Mapping (ORM) Layer • Enables CRUD at

    the data level. • My favorite part of Rails. The best ORM I’ve seen. Enables you to do a lot of things extremely concisely:
  16. ActiveRecord The Object-Relational Mapping (ORM) Layer • De fi ne

    1:1, 1:many, and even many:many associations between models • This code does so much behind the scenes. Including dynamically adding new methods. See following slides. class Enrollment < ApplicationRecord belongs_to :student belongs_to :course end class Student < ApplicationRecord has_many :enrollments, dependent: :destroy has_many :courses, through: :enrollments end class Course < ApplicationRecord has_many :enrollments, dependent: :destroy has_many :students, through: :enrollments end
  17. ActiveRecord The Object-Relational Mapping (ORM) Layer • Examples of convenience

    methods generated by associations: • Get a list of associations # List a student’s courses. # Instead of: @enrollments = Enrollment.where( student_id: @student.id ) @courses = Course.where( enrollment_id: @enrollments ) # You can write: @courses = @student.courses
  18. ActiveRecord The Object-Relational Mapping (ORM) Layer • Examples of convenience

    methods generated by associations: • Get a list of associations • Create an association # Enroll a student in a course. # Instead of: Enrollment.create( student_id: @student.id, course_id: @course.id ) # You can write: @student.courses << @course
  19. ActiveRecord The Object-Relational Mapping (ORM) Layer • Examples of convenience

    methods generated by associations: • Get a list of associations • Create an association • Delete an association # Unenroll. Instead of: @enrollment = Enrollment.where( student_id: @student.id, course_id: @course.id ).first @enrollment.destroy # You can write: @student.courses.destroy(@course)
  20. ActiveRecord The Object-Relational Mapping (ORM) Layer • Examples of convenience

    methods generated by associations: • Get a list of associations • Create an association • Delete an association • Delete one side and all join records # Remove course and all # its enrollments. # Instead of: @enrollments = Enrollment.where( course_id: @course.id ) @enrollments.each do |enrollment| enrollment.destroy end @course.destroy # You can write: @course.destroy
  21. ActiveRecord The Object-Relational Mapping (ORM) Layer • De fi ne

    data validation rules (string not empty, number in a range, value is unique, etc. + custom validations) class Student < ApplicationRecord validates :name, presence: true, uniqueness: true end class Order < ApplicationRecord validates :card_number, presence: true, if: -> { payment_type == "card" } end
  22. ActiveRecord The Object-Relational Mapping (ORM) Layer • Migrate data when

    you change the DB schema • The change method to the right also generates a reverse migration to undo your schema change in development. class CreateStudents < ActiveRecord::Migration[7.0] def change create_table :students do |t| t.string :name t.timestamps end end end
  23. ActiveRecord The Object-Relational Mapping (ORM) Layer • Trigger callbacks when

    data changes/ validates/deletes/ commits/loads etc. class Enrollment < ApplicationRecord belongs_to :student belongs_to :course after_create do student.increase_tuition! end end
  24. ActiveRecord The Object-Relational Mapping (ORM) Layer • Perform e ffi

    cient queries automatically • E.g., don’t do a 3-way outer join which would explode memory. • Also, guess what: this scales better than a join. Tables can be in di ff erent DBs. # Get all students and their courses Student.all.includes(:courses) SELECT "students".* FROM "students" SELECT "enrollments".* FROM "enrollments" WHERE "enrollments"."student_id" IN (?, ?) SELECT "courses".* FROM "courses" WHERE "courses"."id" IN (?, ?)
  25. Joy “Optimized for programmer happiness” • Ruby ❤ • Concise

    and consistent • Everything is an object. Even classes are instances. (Of what?? The Class class!) • ClassName.new instead of new ClassName because new is just a method on the class instance, not a keyword.
  26. Ruby Joy: Expressiveness • Example: declaring a backing variable, a

    getter, and a setter. Java vs. Ruby. # Ruby attr_accessor :name // Java private String name; public String getName() { return this.name; } public void setName( String name ) { this.name = name; }
  27. Joy: Monkeypatching You can extend classes from outside—even system classes

    • ActiveSupport makes great use of this feature. • E.g., 3.seconds returns a Duration instance • 2.weeks.from_now returns a Time instance (Time type includes the date). • So much more readable and less error-prone than: Time.now + 60 * 60 * 24 * 7 * 2 • Vs. for example importing a StringUtils class. Or worse yet, importing StringUtils and StringUtil. (Really happened to me.)
  28. But isn’t monkeypatching unsafe? • Dude. Sane programmers don’t modify

    the behavior of system classes. They extend it. • Add new methods, add behavior to existing methods. • You’re writing code. A lot of things are unsafe. • “Sharp knives”
  29. Rails Joy: Autoloading • In my opinion this isn’t fun

    or helpful: require 'version' require 'last_exception' require 'forwardable' require 'helpers/base_helpers require 'helpers/documentatio require 'helpers' require 'basic_object' require 'prompt' require 'code_object' require 'exceptions' require 'hooks' require 'input_completer' require 'command' require 'class_command' require 'block_command'
  30. Rails Joy: Autoloading • Rails has fi le location conventions

    that let it auto-load any class the fi rst time you reference it. • There’s a con fi gurable Autoload Path of directories to look in. # Auto-loads Student # from app/models/student.rb @students = Student.all
  31. Rails Joy: Expressiveness • See ActiveRecord examples above. Associations, validation,

    concise queries, etc. • Another example: views <%# Instead of this: %> <% @students.each |student| %> <%= render partial: "student", locals: { student: student } %> <% end %> <%# Just do this: %> <%= render @students %>
  32. Some reasons not to use Rails • Team has expertise

    in another framework. • Very big team. • Runtime perf matters more than developer productivity. • Not a CRUD app. E.g., don’t write a game in Rails. • Not a web app. • You need safe multithreading. • Rails has threads but uses manual synchronization primitives like most languages. Error-prone! • Use Rust or Erlang/Elixir instead. • Need a robust, mature async framework? • Ruby has async but NodeJS is more mature for that.
  33. Another reason not to use Rails: Your app is CPU-bound

    (compute-heavy), not I/O bound • In that case use a blazing fast language. Rust, maybe Go or Kotlin. • But most web apps are I/O-bound, waiting for the DB instead of calculations. • “Consider optimizing for developer time, not computation costs”
 — Brett Cannon, Selecting a programming language can be a form of premature optimization • “Process based concurrency scales very well.... On large deployments, most web applications are CPU-bottlenecked, not memory.”
 — Nate Berkopec, The Practical E ff ects of the GVL on Scaling in Ruby
  34. Can Rails scale? • Let’s distinguish between two kinds of

    scaling: • Technical scaling • Organizational scaling
  35. Can Rails scale technically? • For B2B, absolutely. • For

    B2B+B2C it also scales. • Shopify. GitHub. Airbnb. • Interesting case of the Shopify storefront. • Not if you need Twitter-sized B2C scale though.
  36. Can Rails scale technically? Some technical scalability issues we solved

    at Airbnb • Simultaneous MySQL connections. Fixed with a DB proxy. • DB tables grew. Fixed with vertical partitioning. • Joins no longer possible. No problemo in Rails. • Vertical and horizontal partitioning (sharding) now built-in to Rails. • Do you really want to solve all this for your product? Or do you want Velocity?
  37. Airbnb issue solved: blocking I/O • Problem: we called an

    API that could randomly, in bursts, take 30 sec to respond. • Scalability problem because too many Rails processes blocked, couldn’t take more requests. • Solution: asynchronous I/O. Using the Async gem. Like NodeJS, only better. Can Rails scale technically?
  38. How to use Async? • Easy peasy • Lemon squeezy

    • Second form blocks current fi ber but yields control back to a main event loop that listens for API requests and creates new fi bers to handle them. • Not faster. But more scalable. Fibers take much less memory than threads or proceses. # Instead of this: Genesys.slow_api_call # You do this: Async do Genesys.slow_api_call end.wait
  39. Async scalability possibilities Samuel Williams has demonstrated a Ruby server

    handling one million simultaneous WebSocket connections using the Async gem.
  40. Can Rails scale organizationally? It doesn’t do as well here

    • At Airbnb, too many engineers doing Continuous Deployment together. Long wait times. • Hard to hire that many Rails devs. • Similar story at Twitch: didn’t scale organizationally. (According to a guy on Hacker News who worked there.) • GitHub and Shopify are large and still committed to Rails, so it can be done.
  41. Don’t assume your team will be huge • A Rails

    monolith: fantastic for small teams. (Under ~200 devs). • Bigger product w/multiple teams, services helpful. But also a pain. • Your product team will probably stay under 200. Vast majority do.
  42. Michael Seibel, Managing Director at Y Combinator, co-founder of Twitch,

    most in fl uential early advisor to Airbnb “People are 10x more afraid of scaling too late than scaling too early, which always blows my mind. Scaling too late isn't fatal. It could be bad, but it's not fatal. Scaling too early is almost always fatal.”
  43. Sources @BMorearty [email protected] Slides at speakerdeck.com/bmorearty (In addition to my

    own experience 😘 ) • The Rails Doctrine, by David Heinemeier Hansson • Ruby on Rails Guides • When Should You NOT Use Rails?, by Noah Gibbs • Selecting a programming language can be a form of premature optimization, by Brett Cannon • The Practical E ff ects of the GVL on Scaling in Ruby, by Nate Berkopec • RubyConf Taiwan 2019 - The Journey to One Million, by Samuel Williams • Ruby 3.0 and the new FiberScheduler interface, by Wander Hillen • Opening The Ruby Concurrency Toolbox, by Alex Braha Stoll • Masters of Scale podcast episode 92, interview with Michael Seibel • Shopify’s massive storefront rewrite, The Changelog episode #416
  44. Sources @BMorearty [email protected] Slides at speakerdeck.com/bmorearty (In addition to my

    own experience 😘 ) • The Rails Doctrine, by David Heinemeier Hansson • Ruby on Rails Guides • When Should You NOT Use Rails?, by Noah Gibbs • Selecting a programming language can be a form of premature optimization, by Brett Cannon • The Practical E ff ects of the GVL on Scaling in Ruby, by Nate Berkopec • RubyConf Taiwan 2019 - The Journey to One Million, by Samuel Williams • Ruby 3.0 and the new FiberScheduler interface, by Wander Hillen • Opening The Ruby Concurrency Toolbox, by Alex Braha Stoll • Masters of Scale podcast episode 92, interview with Michael Seibel • Shopify’s massive storefront rewrite, The Changelog episode #416