Ruby is

blazing fast ...

 

make ruby great again

APPLY FOR:

Ruby on Rails

API on Rails
Benchmark
How to speed up

API on Rails
Benchmark
How to speed up

# config/application.rb 

# require "rails"
# Pick the frameworks you want:
require "active_model/railtie"
# require "active_job/railtie"
require "active_record/railtie"
# require "action_controller/railtie"
# require "action_mailer/railtie"
# require "action_view/railtie"
# require "action_cable/engine"
# require "sprockets/railtie"
# require "rails/test_unit/railtie"
rails new awesome_ruby_app --api
class ApplicationController < JSONAPI::ResourceControllerMetal
  include Knock::Authenticable

  before_action :authenticate_user

  def head(type, options = {}) end # ...
end
gem "jsonapi-resources"

Reduce AR models instances

use views in database

Started GET "/mail-logs" for ::1 at 2017-03-17 18:50:46 +0100
Processing by MailLogsController#index as API_JSON
  MailLog Load (0.4ms)  
        SELECT  "mail_logs".* FROM "mail_logs" 
        ORDER BY "mail_logs"."number" 
        DESC LIMIT $1 OFFSET $2  [["LIMIT", 20], ["OFFSET", 0]]
   (0.3ms)  SELECT COUNT(*) FROM "mail_logs"
Completed 200 OK in 7ms (Views: 0.0ms)

API on Rails
Benchmark
How to speed up

Ruby 2.4.0

Rails 5.0.2

Puma 3.8.1

(single process == one core)

Apache Benchmark 2.3

(10000 request burst on index action)

MBP 2015

Requests per second:    250.07 [#/sec] (mean)
Time per request:       3.999 [ms] (mean)
Time per request:       3.999 [ms] (mean, across all concurrent requests)


Percentage of the requests served within a certain time (ms)
  50%      4
  66%      4
  75%      4
  80%      4
  90%      4
  95%      5
  98%      5
  99%      6
 100%     39 (longest request) 

250 req / s

3.999 ms / req (median)

API on Rails
Benchmark

How to speed up

Lets rewrite it all with

xyz framework

let use reverse proxy

and rewrite only heavy loaded API end points

Sinja 1.2.5

Sinatra 2.0.0.rc.1

Sequel 4.44.0

(ActiveSupport 5.0.2 -> jsonapi-serializers)

Puma 3.8.1

(single process == one core)

Apache Benchmark 2.3

(10000 request burst on index action)

MBP 2015'

# frozen_string_literal: true
require 'logger'
require 'sinatra'
require 'sequel'
require 'sinatra/jsonapi'

require 'sinja/sequel/helpers'

DB = Sequel.connect('postgres://localhost/cl-api_development')
DB.extension(:freeze_datasets)
DB.extension(:pagination)
DB.optimize_model_load = true

DB.loggers << Logger.new($stderr) if Sinatra::Base.development?

Sequel::Model.plugin :tactical_eager_loading

class MailLog < Sequel::Model
  many_to_one :category
  many_to_one :company
end

class Category < Sequel::Model
  one_to_many :mail_log
end

class Company < Sequel::Model
  one_to_many :mail_log
end

Sequel::Model.finalize_associations
Sequel::Model.freeze

class BaseSerializer
  include JSONAPI::Serializer
end

class MailLogSerializer < BaseSerializer
  attributes :number, :in_or_out, :document_date_on, :received_on, :external_number, :comment, :value_net, :value_vat

  has_one :category
  has_one :company
end

class CategorySerializer < BaseSerializer
  attributes :name

  has_many :mail_logs
end

class CompanySerializer < BaseSerializer
  attributes :name, :icon

  has_many :mail_logs
end

class MailLogs < Sinatra::Application
  register Sinatra::JSONAPI

  resource 'mail-logs' do
    index do
      MailLog.dataset
    end

  end
end

freeze_jsonapi

70 LOC of Ruby

Requests per second:    746.18 [#/sec] (mean)
Time per request:       1.340 [ms] (mean)
Time per request:       1.340 [ms] (mean, across all concurrent requests)
Transfer rate:          2731.85 [Kbytes/sec] received

Percentage of the requests served within a certain time (ms)
  50%      1
  66%      1
  75%      1
  80%      1
  90%      2
  95%      2
  98%      2
  99%      2
 100%      5 (longest request)

746 req / s

2.98x

1.340 ms / req (median)

2.98x

How it Compare to

 

Phoenix / Elixir

? ms / req (median)

tomorrow
3:00 PM

Ruby is

 fast enough

 

make ruby great again

Michał Czyż

 

@cs3b

Thank You!

Ruby is

 fast enough

 

part II

Ruby vs Erlang (Plug implementation - without Phoenix)

written by

@paweldude

Elixir 1.4.2 (no Phoenix)

Plug 1.3.4

Ecto 2.1.4

Poison 2.2.0

(limiting elixir to single core `--erl "+S 1"`)

Apache Benchmark 2.3

(10000 request burst index)

MBP 2015

defmodule PlugBench.Repo do
  use Ecto.Repo, otp_app: :plug_bench
end

defmodule PlugBench.Category do
  use Ecto.Schema

  schema "categories" do
    field :name, :string

    has_many :mail_log, PlugBench.MailLog
  end
end

defmodule PlugBench.CategorySerializer do
  use JaSerializer

  location "/categories/:id"

  attributes [:name]
end


defmodule PlugBench.Company do
  use Ecto.Schema

  schema "companies" do
    field :name, :string
    field :icon, :string

    has_many :mail_log, PlugBench.MailLog
  end
end

defmodule PlugBench.CompanySerializer do
  use JaSerializer

  location "/companies/:id"

  attributes [:name]
end


defmodule PlugBench.MailLog do
  use Ecto.Schema

  @derive {Poison.Encoder, only: [:number, :in_our_out, :document_date_on, :received_on, :external_number, :comment, :value_net, :value_vat, :lp, :inserted_at, :updated_at]}

  schema "mail_logs" do
    field :number, :string
    field :in_or_out, :string
    field :document_date_on, :date
    field :received_on, :date
    field :external_number, :string
    field :comment, :string
    field :value_net, :decimal
    field :value_vat, :decimal
    field :lp, :integer

    belongs_to :category, PlugBench.Category
    belongs_to :company, PlugBench.Company
  end
end

defmodule PlugBench.MailLogSerializer do
  use JaSerializer

  location "/mail-logs/:id"
  attributes [:name, :number, :in_or_out, :document_date_on, :received_on,
              :external_number, :comment, :value_net, :value_vat, :lp]

  has_one :company,
    serializer: PlugBench.CompanySerializer,
    include: true,
    field: :company_id

  has_one :category,
    serializer: PlugBench.CategorySerializer,
    include: true,
    field: :category_id
end

defmodule PlugBench.Endpoint do
  import Plug.Conn

  use Plug.Router

  plug :match
  plug Plug.Logger
  plug Plug.Parsers, parsers: [:urlencoded],
                     pass: ["*/*"],
                     accept: ["*/*"]
  plug :dispatch

  get "/mail-logs" do
    # it's not jsonapi yet
    send_resp(conn, 200, PlugBench.Repo.all(PlugBench.MailLog) |> Poison.encode!)
  end

  match _ do
    conn
      |> put_resp_content_type("application/json")
      |> send_resp(410, ~s({"status": "bad request"}))
  end
end

105 LOC of Elixir

Requests per second:    1306.28 [#/sec] (mean)
Time per request:       0.766 [ms] (mean)
Time per request:       0.766 [ms] (mean, across all concurrent requests)
Transfer rate:          1865.99 [Kbytes/sec] received

Percentage of the requests served within a certain time (ms)
  50%      1
  66%      1
  75%      1
  80%      1
  90%      1
  95%      1
  98%      2
  99%      2
 100%     60 (longest request)

1306 req / seconds

0.766 ms / req (median)

Michał Czyż

 

@cs3b

Thank You!

Ruby is

 fast enough

 

make ruby great again

Ruby is Fast Enough - serving JSONAPI at speed

By Michał Czyż

Ruby is Fast Enough - serving JSONAPI at speed

rails ruby jsonapi-resources puma apache benchmark ab sinja sinatra sequel single process wrocloverb

  • 3,224