Kirk Haines
wyhaines@gmail.com
kirk-haines@cookpad.com
RubyKaigi 2018
It's Rubies All The Way Down
The evolution of the Ruby (web) stack and an exploration of just how much of the complex modern stack can be done (effectively) with Ruby software, right now.
Kirk Haines
• Rubyist since 2001
• First professional Ruby web app in 2002
• Dozens of sites and apps, and several web servers since
kirk-haines@cookpad.com
wyhaines@gmail.com
@wyhaines
What is a "Stack"?
The "Stack" is the software components that live around our application, enabling it to be accessed by end users, and enabling it's own function.
- Front end proxy/load balancing
- Static asset caching layer
- Web server for static assets
- App container + app itself
- Database
- Other data stores (key/value stores)
- Deployment Architecture
- Configuration Management
- Log management
The Ruby Bits
Traditionally, only some of this is done with Ruby
Front end proxy/load balancingStatic asset caching layerWeb server for static assets- App container + app itself
DatabaseOther data stores (key/value stores)- Deployment Architecture (w/ Capistrano)
- Log management (w/ Fluentd)
- Configuration Management (Chef, Itamae)
I've done other parts in Ruby, for Production apps, for a long time. So, let's see how we got to where we are with the modern Ruby stack, and then see how far we can push it in doing those other parts with Ruby.
Ruby For The Long Haul
https://www.ruby-lang.org/en/downloads/releases/
I started here....
Web Development Was Different Then
There were no real "frameworks".
There were a few tools.
Stack options were limited.
Amrita
CGI
mod_ruby
Apache based stack
Just Templating & Embedding & CGI
Amrita
CGI
mod_ruby
Author: seki <seki@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>
Date: Sun Nov 17 16:11:40 2002 +0000
* add ERB
XHTML/HTML Templating
Webserver <-> Executable protocol
Embed Ruby inside Apache
Before Rails
Before ERB
A "Ruby" Stack in 2002?
It was a hodge podge
Apache
Ruby
Script
CGI
Apache
mod_ruby
Your App
IOWA?
Avi Bryant proof-of-concept framework
- Written in 2001
- Proof of concept web development framework
- Internet Objects for Web Applications
- Component/Object based
- Separation of concerns
- Magic server side state management
- Fast, easy, delightful
- IOWA apps ran as separate, persistent processes, and handled requests via a socket...
- Communication channels supported:
- iowa.cgi -- any web server that supported CGI
- mod_iowa -- Apache only
- IOWAServlet -- WEBrick servlet to attach IOWA app to a web server
- Impractical for real world apps in the state that I found it in
- Avi took the concepts and implemented them in Smalltalk as the Seaside Framework
IOWA!
Architecture was much nicer than other Ruby options
Premonitions
Listening on sockets
Attaching to a web server directly
Took it over in 2002, hacked, and released a commercial application, running on Ruby, in 2002
The Ruby "stack" was evolving
Evolving Ruby Stack
Apache
w/ mod_iowa
IOWA
App
on socket
IOWA
App
on socket
IOWA
App
+ WEBrick
IOWA
App
+ WEBrick
Apache
w/ proxypass
That looks kind of modern...
Rails!
Rails stimulated the ecosystem
Early Rails stacks still Apache centric (sometimes lighttpd)
Rails apps did run as discrete processes
FCGI
MySQL
FCGI Bitterness
Deployment, and server management headaches
FCGI and friends (SCGI, for example) was dominant for a while, but there was a lot of unhappiness with it
Surely there must be a better way?
Mongrel
The New Hotness
https://groups.google.com/forum/#!msg/comp.lang.ruby/-cI5PtqDc1o/FXRdN9UKPE0J
Announced Jan 19, 2006
Web/Application server written in Ruby
Used a C/Ragel derived HTTP parser
Promised Speed, and delivered
Quickly became the new paradigm
Mongrel
The Ruby/Rails Stack Evolved
Rails + Mongrel
Rails +
Mongrel
Apache
w/ proxypass
Rails + Mongrel
Rails +
Mongrel
NGINX +
proxy
Briefly Back in IOWAland
IOWA supported multiple deployment schemes
- CGI
- mod_iowa
- Mongrel
- WEBrick
- FCGI
- HTTPMachine (EventMachine based HTTP server)
- Built in HTTP server leveraging Mongrel's parser + EventMachine
IOWA implemented a common request specification
Not Rack; predated Rack, but similar goals
Some IOWA code ended up in Rack
Rack
March 3rd 2007: First Public Release 0.1
Agnostic interface between Ruby apps and the outside world.
It didn't change the Ruby deployment landscape immediately, but it was a key enabler
Mongrel supported Rack
Other app servers (like Thin) started being developed
Ruby devs were getting more options for their stack
More Ruby In My Stack!
Or
Why Server Side State Isn't All Puppies and Rainbows
IOWA has server side state management
REALLY nice to eliminate programmer work
However, state was isolated to a single process
All requests for a given session need to go to the same backend process
This requires sticky sessions
There was no good solution
So, the answer? Write it myself! A sticky session supporting load balancing reverse proxy, in Ruby!
More Ruby In My Stack!
Basic Webserver & Caching Reverse Proxy w/ IOWA Session Support, in Ruby?
You can't do that with Ruby
Ruby is too slow
You need C/C++/Erlang/other-fast-thing
More Ruby In My Stack!
Won't It Be Too Slow?
10k 1020 byte files, concurrency of 25; with KeepAlive
Requests per second: 26245.09 [#/sec] (mean)
10k 78000 byte files, concurrency of 25; with KeepAlive
Requests per second: 8273.98 [#/sec] (mean)
10k 78000 byte files with etag, concurrency of 25; with KeepAlive
Requests per second: 25681.19 [#/sec] (mean)
Disclosure: This was timed on a $10/month Digital Ocean instance, running
Ruby 2.5.1. The original benchmarks using Ruby 1.8.7 on cheap dedicated 2008 hardware topped out at about 15000/second.
More Ruby In My Stack!
And It Isn't Too Slow
A pure Ruby stack can do everything I need
Rack got support for Swiftiply around version 0.9
One simple, consistent stack, whether IOWA or Rails
Epiphany!!!
Pure Ruby Stack
Swiftiply
IOWA
App
IOWA
App
IOWA
App
Rails
App
Rails
App
Analogger
Circa 2009
Analogger?
Swiftiply
IOWA
App
IOWA
App
IOWA
App
Rails
App
Rails
App
Analogger
Asynchronous Logger
- Many sites; many processes; logs everywhere!
- Not fun when trying to find a problem or analyze traffic
- No other good solution in 2007
- Write it in Ruby?
Asynchronous Logger
- Asynchronous; performance trumps risk of loss of a few messages on catastrophic process/server failure
- API compatible with Ruby Logger
- Can it be fast if it's Ruby?
Asynchronous Logger
Ruby Is Fast Enough
Analogger Speedtest -- larger messages
Testing 100000 messages of 100 bytes each.
Message rate: 130665/second (0.765315254)
Analogger Speedtest -- Fail Analogger, continue logging locally, and monitor for Analogger return, then drain queue of local logs
Testing 100000 messages of 100 bytes each.
Message rate: 61992/second (1.613111685)
Ruby Logger Speedtest (local file logging only) -- larger messages
Testing 100000 messages of 100 bytes each.
Message rate: 72811/second (1.37341377)
Asynchronous Logger
Ruby Is Fast Enough
Performance tested on $10/month Digital Ocean instance
Inexpensive dedicated 2007 era hardware, with Ruby 1.8.6/7,
delivered about 60% of those numbers. i.e. even a decade ago it was fast enough.
Still Running Today...
Swiftiply
IOWA
App
IOWA
App
IOWA
App
Rails
App
Rails
App
Analogger
Dozens of sites and apps,
large and small, running Ruby dominated production stacks for the last decade.
Room For More Ruby?
- Front end proxy/load balancing
- Static asset caching layer
- Web server for static assets
- App container + app itself
- Database
- Other data stores (key/value stores)
- Deployment
- Configuration Management
- Log management
You CAN do most of this with Ruby in 2018
Front End Proxy/LB
em-proxy
- EM-Proxy is a DSL for writing proxies
- It's been around for a long time
- It's fast
With comments, and an example/test, it's less than 200 lines to write a multi-strategy load balancing proxy with Ruby.
Front End Proxy/LB
em-proxy
# Wrapping the proxy server
#
module Server
def run(host='0.0.0.0', port=9999)
puts ANSI::Code.bold { "Launching proxy at #{host}:#{port}...\n" }
Proxy.start(:host => host, :port => port, :debug => false) do |conn|
Backend.select do |backend|
conn.server backend, :host => backend.host, :port => backend.port
conn.on_connect &Callbacks.on_connect
conn.on_data &Callbacks.on_data
conn.on_response &Callbacks.on_response
conn.on_finish &Callbacks.on_finish
end
end
end
module_function :run
end
Front End Proxy/LB
WEBrick
Really
WEBrick is powerful, but often ignored
require 'webrick'
require 'webrick/httpproxy'
It's capable, and it's been a part of Ruby since 2002.
Static Asset Caching
Anything that works for a proxy
- Really a special purpose proxy
- Store HTTP responses, and deliver stored response instead of proxying, for matching requests
- Just a proxy, with extra intelligence to deal with cache control and expiration of cached items
Static Asset Caching
Puma/Rack?
- Puma is a capable web server
- Rack lets us write most anything and have it work with Puma
- Reasonable static asset caching on the quick and dirty with Puma and Rack?
Static Asset Caching
Shellac
- Puma + Rack makes it easy
- More lines dedicated to the CLI than to the proxy itself
- Simple to customize and expand it
- Puma is fast
- Rack isn't quite so fast
- So, no awe inspiring speed, but maybe fast enough?
Static Asset Caching
Shellac
$ bundle exec bin/shellac -s hash -b 0.0.0.0:8080 \
-r 'rubykaigi2018.demos4.us::\?(.*)$::https://github.com/#{$1}' \
-t 2:20 -w 1
Requests per second: 1861.69 [#/sec] (mean)
Time per request: 53.715 [ms] (mean)
Time per request: 0.537 [ms] (mean, across all concurrent requests)
Transfer rate: 175182.14 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.5 0 9
Processing: 2 53 3.4 53 65
Waiting: 1 53 3.8 53 65
Total: 10 53 3.0 53 65
It's probably fast enough! (Ruby 2.5.1; $10 Digital Ocean instance)
Static Asset Caching
Shellac
* Disclaimer
(i.e. the fine print)
Shellac was knocked together as a proof of concept demo for this talk, and ABSOLUTELY needs more work and more features, and more than a few bug fixes before I'd ever use it for any real task.
Webserver For Static Assets
Ruby has a bunch of options
#! /bin/sh
read get docid junk
cat `echo "$docid" | \
ruby -p -e '$_=$_.gsub(/^\//,"").gsub(/[\r\n]/,"").chomp'`
while :; do netcat -l -p 5000 -e ./super_simple.sh; done
Just kidding! Don't do this. Please! (It is, more or less, HTTP 0.9 compliant, though...)
Webserver For Static Assets
WEBrick
$ ruby -run -e httpd -- -p 8080 .
This actually runs a WEBrick server, and it's reasonably quickly.
Requests per second: 2732.33 [#/sec] (mean)
Time per request: 3.660 [ms] (mean)
Time per request: 0.366 [ms] (mean, across all concurrent requests)
Transfer rate: 717.77 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.1 0 3
Processing: 1 4 0.5 3 17
Waiting: 0 2 0.6 2 16
Total: 1 4 0.6 4 18
Webserver For Static Assets
Puma + Rack
use Rack::Static,
:urls => ["/"],
:root => "."
run lambda { |env|
[
200,
{
'Content-Type' => 'text/html',
'Cache-Control' => 'public, max-age=86400'
},
( File.open('index.html', File::RDONLY) rescue nil )
]
}
Requests per second: 3075.24 [#/sec] (mean)
Time per request: 3.252 [ms] (mean)
Time per request: 0.325 [ms] (mean, across all concurrent requests)
Transfer rate: 12237.88 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.1 0 2
Processing: 1 3 0.4 3 23
Waiting: 0 3 0.3 3 12
Total: 1 3 0.4 3 23
puma -b tcp://127.0.0.1:5000 -w 1 -t 2:16 ../config.ru
Webserver For Static Assets
Puma + Rack vs NGINX
Requests per second: 3075.24 [#/sec] (mean)
Time per request: 3.252 [ms] (mean)
Time per request: 0.325 [ms] (mean, across all concurrent requests)
Transfer rate: 12237.88 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.1 0 2
Processing: 1 3 0.4 3 23
Waiting: 0 3 0.3 3 12
Total: 1 3 0.4 3 23
Puma
Requests per second: 4545.14 [#/sec] (mean)
Time per request: 2.200 [ms] (mean)
Time per request: 0.220 [ms] (mean, across all concurrent requests)
Transfer rate: 18730.94 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 0 0.1 0 4
Processing: 0 2 0.4 2 7
Waiting: 0 2 0.4 2 7
Total: 1 2 0.4 2 7
NGINX
Database
This section will be short....
To my knowledge, nobody has used Ruby to implement a database (yet?)
I have a crazy idea (that I might hack on the flight back home)
Ruby +
distributed gossip type protocol +
SQLite ==
Distributed "Ruby" SQL DB!
Key / Value Store
ROMA
ROMA is a distributed key value store, written in Ruby
- Fault tolerant
- Pluggable back end storage modules
- Easy to understand code -- it's not a lot of code
- Memcached API compatible
Key / Value Store
ROMA
ROMA-1
ROMA-2
ROMA-3
$ simple_bench -t 8 -r -n 1000 10.136.73.13:11211
qps=556 max=0.151159 min=0.000541 ave=0.001797
qps=637 max=0.196910 min=0.000487 ave=0.001569
qps=605 max=0.119492 min=0.000540 ave=0.001651
qps=446 max=0.527537 min=0.000553 ave=0.002238
qps=535 max=1.563985 min=0.000483 ave=0.001866
qps=531 max=0.879976 min=0.000606 ave=0.001883
qps=599 max=0.231225 min=0.000534 ave=0.001668
Each node is a $5 Digital Ocean instance.
Writing persistent data (survives restart/reboot)
across at least 2 nodes.
Key / Value Store
ROMA
Rakuten claims performance > 10000 queries per second on in-memory data stores, using AWS instances for nodes (2015 slides)
Distributed nature and redundancy supports rolling restarts
Key / Value Store
ROMA
Ships with support for multiple storage engines:
- In Memory Hash
- DBM
- Tokyo Cabinet
- SQLite
- Groonga
Extending it is as simple as writing a Ruby module to provide the implementation
Deployment
Of Course, there is Capistrano....
Capistrano is a framework for building automated deployment scripts.
- Ubiqitous
- Battle Tested
Deployment
Easy Git integrated deploys?
kirkhaines@MacBook-Pro ~/.ghq/github.com/wyhaines/demo-website (staging) $ git push
Git Repo
ProductionServer
ProductionServer
Staging
Server
Deployment
Easy Git integrated deploys?
kirkhaines@MacBook-Pro ~/.ghq/github.com/wyhaines/demo-website (production) $ git push
Git Repo
ProductionServer
ProductionServer
Staging
Server
Deployment
Easy Git integrated deploys?
- On commit, deploy to N servers
- Commits to different branches may be deployed to different servers
- Easy to maintain implementation
Deployment
Easy Git integrated deploys?
Requires explicit specification of push target:
git remote add production demo@server_domain_or_IP:proj
Deployment
Serf is a nice tool
By Hashicorp
- Lightweight
- Distributed, gossip protocol daemon
- Transmit events and data, trigger commands, get results
- Nice tool for server orchestration
- Not Ruby, but it facilitates easy Ruby use for server orchestration and monitoring tasks.
Deployment
Easy Membership Queries With Serf
$ serf members -rpc-auth="blahblahblah"
db-1-nyc1 10.136.13.216:7946 alive role=database
app-demo-1-nyc1 10.81.218.50:7946 alive role=app
app-demo-2-nyc1 10.236.115.112:7946 alive role=app
http-1-nyc1 10.205.169.211:7946 alive role=nginx
Deployment
Easy Command/Query Invocation With Serf
$ serf query -rpc-auth="blahblahblah" load-average
Query 'load-average' dispatched
Ack from 'db-1-nyc1'
Ack from 'app-1-nyc1'
Ack from 'http-1-nyc1'
Ack from 'app-2-nyc1'
Response from 'app-2-nyc1': 16:54:16,up,105,days,,20:48,,0,users,,load,average:,0.00,,0.00,,0.00
Response from 'app-1-nyc1': 16:54:16,up,108,days,,1:37,,1,user,,load,average:,0.00,,0.00,,0.00
Response from 'http-1-nyc1': 16:54:16,up,111,days,,1:39,,0,users,,load,average:,0.00,,0.00,,0.00
Response from 'db-1-nyc1': 16:54:16,up,105,days,,23:39,,1,user,,load,average:,0.00,,0.00,,0.00
Total Acks: 4
Total Responses: 4
Deployment
Easy Command/Query Invocation With Serf
$ serf query -rpc-auth="blahblahblah" list-handlers
Query 'list-handlers' dispatched
Ack from 'deploy-1-nyc1'
Ack from 'app-1-nyc1'
Ack from 'app-2-nyc1'
Ack from 'http-1-nyc1'
Response from 'app-1-nyc1': query: list-handlers
query: describe-handler
query: load-average
query: df
query: mpstat
query: ping
event: git-index-deploy
Response from 'db-1-nyc1': query: list-handlers
query: describe-handler
query: load-average
query: df
query: mpstat
query: ping
Response from 'app-2-nyc1': query: list-handlers
query: describe-handler
query: load-average
query: df
query: mpstat
query: ping
event: git-index-deploy
Response from 'http-1-nyc1': query: list-handlers
query: describe-handler
query: load-average
query: df
query: mpstat
query: ping
Total Acks: 4
Total Responses: 4
Deployment
Easy Command/Query Invocation With Serf
$ serf query -rpc-auth="blahblahblah" describe-handler git-index-deploy
Query 'describe-handler' dispatched
Ack from 'deploy-1-nyc1'
Ack from 'app-1-nyc1'
Ack from 'http-1-nyc1'
Ack from 'app-2-nyc1'
Response from 'app-1-nyc1': Expects a hash code in the payload which will
be queried using git-index. A 'git fetch --all && git pull' will be
executed on all matching repositories.
Response from 'app-2-nyc1': Expects a hash code in the payload which will
be queried using git-index. A 'git fetch --all && git pull' will be
executed on all matching repositories.
Total Acks: 4
Total Responses: 2
Deployment
Serf
Configuration
Is Simple
{
"protocol": 5,
"bind": "0.0.0.0",
"advertise": "10.136.13.216",
"rpc_addr": "0.0.0.0:7373",
"rpc_auth": "blahblahblah",
"enable_syslog": true,
"log_level": "info",
"replay_on_join": true,
"snapshot_path": "/etc/serf/snapshot",
"tags": {
"role": "app"
},
"retry_join": [
"db-1-nyc1",
"app-2-nyc1",
"http-1-nyc1"
],
"event_handlers" : [
"/usr/share/rvm/wrappers/ruby-2.5.1/serf-handler"
]
}
Deployment
Serf-Handler is a Framework and DSL for defining handlers for Serf queries and events
require 'serf/handler/events/load_average'
require 'serf/handler/events/df'
require 'serf/handler/events/mpstat'
require 'serf/handler/events/ping'
require 'serf/handler/events/git-index-deploy'
Wrote it on the airplane coming home from the Cookpad Bristol Office
Simple configuration - Just require something that uses the DSL
Several bundled handlers come with the gem
Deployment
Serf-Handler DSL is simple
require 'serf/handler' unless Object.const_defined?(:Serf) &&
Serf.const_defined?(:Handler)
include Serf::Handler
describe "Return the 1-minute, 5-minute, and 15-minute load averages as a",
"comma separated list of values."
on :query, 'load-average' do |event|
`/usr/bin/uptime`.gsub(/^.*load\s+averages:\s+/,'').split.join(',').strip
end
load_average.rb
Deployment
git-index-deploy.rb
require 'serf/handler' unless Object.const_defined?(:Serf) && Serf.const_defined?(:Handler)
include Serf::Handler
describe "Expects a hash code in the payload which will be queried using",
"git-index. A 'git fetch --all && git pull' will be executed on all",
"matching repositories. Deployment hooks are available in order to",
"execute arbitrary code during the deploy process. If any of the",
"following files are found in the REPO root directory, they will be",
"executed in the order described by their name.\n",
".serf-before-deploy\n",
".serf-after-deploy\n",
".serf-on-deploy-failure\n",
".serf-on-deploy-success\n"
on :event, 'git-index-deploy' do |event|
user = `whoami` # Serf's executable environments are stripped of even basic information like HOME
dir = `eval echo "~#{user}"`.strip
`git-index -d #{dir}/.git-index.db -q #{event.payload}`.split(/\n/).each do |match|
hash,data = match.split(/:\s+/,2)
path,url = data.split(/\|/,2)
ENV['SERF_DEPLOY_PAYLOAD'] = event.payload
ENV['SERF_DEPLOY_HASH'] = hash
ENV['SERF_DEPLOY_PATH'] = path
ENV['SERF_DEPLOY_URL'] = url
success = Dir.chdir(path)
break unless success
if FileTest.exist?(File.join(path, ".serf-before-deploy"))
system(File.join(path, ".serf-before-deploy"))
end
success = system("git fetch --all && git pull")
if success && FileTest.exist?(File.join(path, ".serf-on-deploy-success"))
system(File.join(path, ".serf-on-deploy-success"))
elsif !success && FileTest.exist?(File.join(path, ".serf-on-deploy-failure"))
system(File.join(path, ".serf-on-deploy-failure"))
end
if FileTest.exist?(File.join(path, ".serf-after-deploy"))
system(File.join(path, ".serf-after-deploy"))
end
end
end
Deployment
Oh yeah! git-index
Simple tool to make/maintain a database of git repositories
Calculates a "fingerprint" using the combination of the hashes of the first two commits in the repo -- it's enough to be reliably unique.
Deployment
Combine these with a git hook or github/gitlab web hook, and et voila! Ruby powered git based deploys.
kirkhaines@MacBook-Pro ~/.ghq/github.com/wyhaines/demo-website (production) $ git push
Git Repo
ProductionServer
ProductionServer
Staging
Server
Configuration Management
This doesn't need much comment. Chef and Itamae are both Ruby based and capable.
Log Management
I still think Analogger is pretty sweet if you just need something fast and reliable to get all of your logs into one place.
Log Management
Fluentd is Ruby (Thanks Treasure Data!) and it's great
Other Stuff?
A Good Example of Ruby Power to Fill Niches
A tool to keep a folder or folders synchronized between multiple discrete systems, in a few hundred lines of Ruby.
Bastion Host/Logged SSH
Bolwerk (mostly vapor-ware right now)
At EngineYard I wrote essentially an SSH proxy with full stream logging and 2-factor authentication, implemented in Ruby
- Applying lessons learned to reimplement a much improved version
- Fully searchable full stream logging of SSH sessions
- Transparent and flexible
- All Ruby, pluggable, and easily extensible
Still a private project, but hopefully it'll land on Github soon
Wrapping Up
This chain of thought was inspired by a couple real-world, mostly Ruby stacks that have been in production with few changes for 7+ years.
- Swiftiply
- IOWA
- Analogger
- ROMA
- MySQL
- Chef (solo)
- Serf-Handler + Git-Index (+ Serf)
- em-proxy
- originally Mongrel, and now Puma, with Rails
- Analogger
- MySQL
- Chef (solo)
- Serf-Handler + Git-Index (+ Serf)
Wrapping Up
With some work, one could conceivably fill all the niches with Ruby:
- em-proxy based load balancer
- Shellac based static asset caching
- Your favorite framework + app container
- Fluentd or Analogger logging aggregation
- ROMA key/value store
- Kirk's Crazy Gossiping Distributed SQLite Server
- Chef or Itamae
- Serf-Handler + Git-Index (+ Serf)
- Bolwerk for your bastion server/access control
Would You Want To?
- Performance of the Ruby implementations can be completely reasonable
- Ruby solutions are often less feature rich than the non-ruby options, but they are also more targeted at the key use-case, and are often very extensible for your exact needs.
- Using them can make the architecture simpler to maintain (I never switched off Swiftiply and Analogger because nothing with comparable capability was nearly so easy to set up or configure for my needs at the time)
Why Might This Be Crazy?
- You need every nanosecond of performance
- You have very complex requirements that can be handled with an off-the-shelf component
- Security risks -- more eyes on code mean finding vulnerabilities is more likely, but getting them fixed is also more likely
- There should be a valid reason for not using a perfectly serviceable tool like NGINX -- do it in Ruby if you are also improving some aspect of the use case, for you.
Thanks to Cookpad!
For everyone who signed up, come to our party tonight!
https://www.eventbrite.com.au/e/cookpad-x-rubykaigi-2018-day-2-party-tickets-46009089425
We are hiring! Find one of us, or come to our booth
kirk-haines@cookpad.com / wyhaines@gmail.com / @wyhaines everywhere