Slide 1

Slide 1 text

Brian Morearty | December 2, 2021 | UC Berkeley CS 169 @BMorearty [email protected] Why Rails? And can it scale?

Slide 2

Slide 2 text

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.

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

Why Rails?

Slide 5

Slide 5 text

Two words: VELOCITY and JOY 🥰

Slide 6

Slide 6 text

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.

Slide 7

Slide 7 text

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.

Slide 8

Slide 8 text

- 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.”

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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.

Slide 12

Slide 12 text

Some ways Rails improves your velocity No waiting for compiler xkcd by Randall Munroe is licensed under CC BY-NC 2.5

Slide 13

Slide 13 text

Some ways Rails improves your velocity The Rails console • A great REPL. • Code playground. See what queries execute, etc.

Slide 14

Slide 14 text

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)

Slide 15

Slide 15 text

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.

Slide 16

Slide 16 text

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)

Slide 17

Slide 17 text

CRUD Basic Airbnb Resources

Slide 18

Slide 18 text

CRUD Basic Shopify Resources

Slide 19

Slide 19 text

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:

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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)

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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 (?, ?)

Slide 29

Slide 29 text

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.

Slide 30

Slide 30 text

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; }

Slide 31

Slide 31 text

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.)

Slide 32

Slide 32 text

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”

Slide 33

Slide 33 text

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'

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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 %>

Slide 36

Slide 36 text

Why Not Rails?

Slide 37

Slide 37 text

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.

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

Yes ok but can Rails scale????

Slide 40

Slide 40 text

• Guy learning to be a startup founder:

Slide 41

Slide 41 text

Can Rails scale? • Let’s distinguish between two kinds of scaling: • Technical scaling • Organizational scaling

Slide 42

Slide 42 text

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.

Slide 43

Slide 43 text

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?

Slide 44

Slide 44 text

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?

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

Async scalability possibilities Samuel Williams has demonstrated a Ruby server handling one million simultaneous WebSocket connections using the Async gem.

Slide 47

Slide 47 text

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.

Slide 48

Slide 48 text

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.

Slide 49

Slide 49 text

Paul Graham, founder, YCombinator “Do things that don’t scale.”

Slide 50

Slide 50 text

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.”

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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