Skip to content

Instantly share code, notes, and snippets.

@oggy
Created February 21, 2013 22:31
Show Gist options
  • Save oggy/5008990 to your computer and use it in GitHub Desktop.
Save oggy/5008990 to your computer and use it in GitHub Desktop.
Demonstrates the superiority of Goliath over Unicorn for a single-process app handling file uploads. Also a fine excuse to use gems named Unicorn, Thor, and Goliath in a single ruby script.
#!/usr/bin/env ruby
# Demonstrates the superiority of Goliath over Unicorn for a single-process app
# handling file uploads.
#
# This program runs in 3 modes:
#
# ./goliath-vs-unicorn goliath - Runs the goliath server on the default port (9000)
# ./goliath-vs-unicorn unicorn - Runs the unicorn server on the default port (8080)
# ./goliath-vs-unicorn upload - Upload 5 files in parallel to the port given by -p
#
# The goliath and unicorn servers run an app that simply write the uploaded file
# to disk in the current directory.
#
# The upload mode will upload the 5 files in parallel threads, in 10 1MB chunks
# with 1 second sleeps between each chunk. This simulates multiple clients
# performing file uploads to a web server in parallel. To view progress on the
# client-side, a dot is printed after each chunk is written to the socket, and
# the HTTP response is printed when each request is complete.
#
# When running against Unicorn, the requests are seen to become serialized in
# the request queue, and the whole process takes around 5x longer. Goliath
# handles all 5 requests in parallel due to its evented architecture.
require 'net/http'
require 'goliath/api'
require 'goliath/runner'
require 'unicorn'
require 'thor'
class App < Thor
NUM_FILES = 5
CHUNKS = 10
desc 'goliath', 'Run the goliath server'
def goliath
api = Api.new
runner = Goliath::Runner.new([], api)
runner.app = Goliath::Rack::Builder.build(Api, api)
runner.run
end
desc 'unicorn', 'Run the unicorn server'
def unicorn
app = Api.new
Unicorn::HttpServer.new(app).start.join
end
desc 'upload PORT', 'Upload files'
def upload(port)
posters = (0...NUM_FILES).map do |i|
Thread.new { post_file(i, port) }
end
posters.each(&:join)
end
private
def post_file(i, port)
res = Net::HTTP.start('localhost', port) do |http|
path = self.class.output_path(i)
request = Net::HTTP::Post.new("/#{i}")
request['Content-length'] = 10*(2**20)
request.body_stream = SlowStream.new(CHUNKS) { print '.'; $>.flush }
http.request(request)
end
p res
end
def self.output_path(i)
'out.%d.txt' % i
end
end
class SlowStream
def initialize(chunks=chunks, delay=1, &callback)
@chunks = chunks
@delay = delay
@callback = callback
@chunks_remaining = chunks
end
def read(size)
return nil if @chunks_remaining == 0
sleep @delay
@callback.call
@chunks_remaining -= 1
'.'*(2**20)
end
end
class Api < Goliath::API
def response(env)
accept_file
[200, {}, 'hi']
end
def call(env)
accept_file(env)
[200, {'Content-length' => '2', 'Content-type' => 'text/plain'}, ['hi']]
end
def accept_file(env)
i = env['PATH_INFO'][/\d+/].to_i
File.open(App.output_path(i), "w") do |file|
input = env['rack.input']
input.rewind
while (chunk = input.read(100))
file.write(chunk)
end
end
end
end
App.start
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment