Thin cluster for Rails

472 views
Skip to first unread message

stephe...@gmail.com

unread,
Jan 5, 2008, 5:16:16 PM1/5/08
to thin-ruby
For those brave enough to start testing thin on a production or
staging server, I've prepared a quick rake task to make a thin
cluster.

To run it:

rake thin:cluster:start
rake thin:cluster:stop

For the start task, you can pass in the RAILS_ENV, the SIZE of the
cluster (default 4), and the starting PORT (default 3000 for
development, 8000 otherwise).

rake thin:cluster:start RAILS_ENV=production SIZE=10 PORT=8000

The code! (add to a .rake file in your lib/tasks directory):

namespace :thin do
namespace :cluster do

desc 'Start thin cluster'
task :start => :environment do
`cd #{RAILS_ROOT}`
port_range = RAILS_ENV == 'development' ? 3 : 8
(ENV['SIZE'] ? ENV['SIZE'].to_i : 4).times do |i|
Thread.new do
port = ENV['PORT'] ? ENV['PORT'].to_i + i : ("#{port_range}
%03d" % i)
str = "thin start -d -p#{port} -Ptmp/pids/thin-#{port}.pid"
str += " -e#{RAILS_ENV}"
puts "Starting server on port #{port}..."
`#{str}`
end
end
end

desc 'Stop all thin clusters'
task :stop => :environment do
`cd #{RAILS_ROOT}`
Dir.new("#{RAILS_ROOT}/tmp/pids").each do |file|
Thread.new do
if file.starts_with?("thin-")
str = "thin stop -Ptmp/pids/#{file}"
puts "Stopping server on port #{file[/\d+/]}..."
`#{str}`
end
end
end
end

end
end

macournoyer

unread,
Jan 5, 2008, 5:28:04 PM1/5/08
to thin-ruby
hey stephen,

that's awesome! nice job!

I'd like to provide this built in the thin script soon. James proposed
that we use the god gem to monitor the servers rather then just start
and stop. I really like the idea :
http://groups.google.com/group/thin-ruby/browse_thread/thread/8812d6a0ebbf200b

Let me know what you think about this!

On Jan 5, 5:16 pm, "stephen.ce...@gmail.com" <stephen.ce...@gmail.com>
wrote:

stephe...@gmail.com

unread,
Jan 5, 2008, 5:42:00 PM1/5/08
to thin-ruby
I wasn't familiar with the god gem, but I like the idea. The
dependency shouldn't be a huge issue as long as it remains transparent
to the thin end user (and as long as documentation is plentiful!).

On Jan 5, 4:28 pm, macournoyer <macourno...@gmail.com> wrote:
> hey stephen,
>
> that's awesome! nice job!
>
> I'd like to provide this built in the thin script soon. James proposed
> that we use the god gem to monitor the servers rather then just start
> and stop. I really like the idea :http://groups.google.com/group/thin-ruby/browse_thread/thread/8812d6a...

James Golick

unread,
Jan 5, 2008, 6:15:14 PM1/5/08
to thin...@googlegroups.com
Exactly - I mean, if people want to use monit (or even a different god conf), or anything else, they still can.

On Jan 5, 2008 5:42 PM, stephe...@gmail.com <stephe...@gmail.com> wrote:

I wasn't familiar with the god gem, but I like the idea. The
dependency shouldn't be a huge issue as long as it remains transparent
to the thin end user (and as long as documentation is plentiful!).

On Jan 5, 4:28pm, macournoyer <macourno...@gmail.com> wrote:
> hey stephen,
>
> that's awesome! nice job!
>
> I'd like to provide this built in the thin script soon. James proposed
> that we use the god gem to monitor the servers rather then just start
> and stop. I really like the idea : http://groups.google.com/group/thin-ruby/browse_thread/thread/8812d6a...
>
> Let me know what you think about this!
>

Brian Tol

unread,
Jan 6, 2008, 2:43:41 PM1/6/08
to thin-ruby
Here's a 'thin_cluster' script that works a lot like mongrel_rails
cluster::*. It can use existing mongrel_cluster.yml files, in fact.
FWIW, I thought I'd pass it along. Your millage will vary.

#!/usr/bin/env ruby
# <tt>thin_cluter start</tt>: Starts the Rails app in the current
directory with a cluster of thins.
# Run <tt>thin_cluter -h</tt> to get more usage.
require File.dirname(__FILE__) + '/../lib/thin'
require 'optparse'

options = {
'host' => '0.0.0.0',
'port' => 3000,
'env' => ENV['RAILS_ENV'] || "development",
'root' => Dir.pwd,
'log_file' => 'log/thin.log',
'pid_file' => 'tmp/pids/thin.pid',
'timeout' => 60,
'servers' => 2
}
cli_options = {}


config_filename = 'config/thin_cluster.yml'

opts = OptionParser.new do |opts|
opts.banner = "Usage: thin_cluster [options] start|stop|restart"

opts.separator ""
opts.separator "Cluster options:"

opts.on("-C", "--config", "Config file to use",
"(defaults to: #{config_filename})")
{ |user_config_filename|
config_filename = user_config_filename if File.exist?
(user_config_filename)
}

opts.on("-o", "--host HOST", "listen on HOST (default:
0.0.0.0)") { |host| cli_options['host'] = host }
opts.on("-p", "--port PORT", "use PORT (default:
3000)") { |port| cli_options['port'] = port }
opts.on("-e", "--env ENV", "Rails environment (default:
development)") { |env| cli_options['env'] = env }
opts.on("-c", "--chdir PATH", "listen on HOST (default: current
dir)") { |dir| cli_options['root'] = dir }
#opts.on("-d", "--daemonize", "Run daemonized in the
background") { cli_options['daemonize'] = true }
opts.on("-l", "--log-file FILE", "File to redirect output",
"(default:
#{options['log_file']})") { |file| cli_options['log_file'] = file }
opts.on("-P", "--pid-file FILE", "File to store PID",
"(default:
#{options['pid_file']})") { |file| cli_options['pid_file'] = file }
opts.on("-t", "--timeout SEC", "Request or command timeout in
sec",
"(default:
#{options['timeout']})") { |sec| cli_options['timeout'] = sec }
opts.on("-u", "--user NAME", "User to run daemon as (use with -
g)") { |user| cli_options['user'] = user }
opts.on("-g", "--group NAME", "Group to run daemon as (use with -
u)") { |group| cli_options['group'] = group }

opts.separator ""
opts.separator "Common options:"

opts.on_tail("-D", "--debug", "Set debbuging on") { $DEBUG = true }

opts.on_tail("-h", "--help", "Show this message") do
puts opts
exit
end

opts.on_tail('-v', '--version', "Show version") do
puts Thin::SERVER
exit
end

opts.parse! ARGV
end

if File.exist?(config_filename)
config_file_options = YAML.load_file(config_filename)
options.merge! config_file_options if config_file_options
end

options.merge! cli_options

def start_cluster(options)
port = options["port"].to_i - 1
pid = options["pid_file"].split(".")

puts "Starting #{options["servers"]} Thin servers..."

1.upto(options['servers'].to_i) do |i|
argv = [ 'thin' ]
argv << "start"
argv << "-d"
argv << "-e #{options["environment"]}" if options["environment"]
argv << "-p #{port+i}"
argv << "-o #{options["host"]}" if options["host"]
argv << "-l #{options["log_file"]}" if options["log_file"]
argv << "-P #{pid[0]}.#{port+i}.#{pid[1]}"
argv << "-c #{options["root"]}" if options["root"]
argv << "-t #{options["timeout"]}" if options["timeout"]
#argv << "-r #{options["root"]}" if options["root"]
#argv << "-n #{options["num_procs"]}" if options["num_procs"]
#argv << "-B" if options["debug"]
argv << "--user #{options["user"]}" if options["user"]
argv << "--group #{options["group"]}" if options["group"]
cmd = argv.join " "

puts cmd if $DEBUG
output = `#{cmd}`
unless $?.success?
puts cmd unless $DEBUG
puts output
end
end

end

def stop_cluster(options)
port = options["port"].to_i - 1
pid = options["pid_file"].split(".")

puts "Stopping #{options["servers"]} Thin servers..."

1.upto(options['servers'].to_i) do |i|
argv = [ "thin" ]
argv << "stop"
argv << "-P #{pid[0]}.#{port+i}.#{pid[1]}"
argv << "-c #{options["root"]}" if options["root"]
cmd = argv.join " "
puts cmd if $DEBUG
output = `#{cmd}`
unless $?.success?
puts cmd unless $DEBUG
puts output
end
end

end

case ARGV[0]

when 'start'
start_cluster(options)

when 'stop'
stop_cluster(options)

when 'restart'
stop_cluster(options)
start_cluster(options)

when nil
puts "Command required"
puts opts
exit 1

else
abort "Invalid command : #{ARGV[0]}"

end

jag

unread,
Feb 29, 2008, 5:05:28 AM2/29/08
to thin-ruby
Excellent work Stephen!

posted the script + instructions on my blog as i'm playing with THIN
for a new project, credited your name.

Let me know if you want it linking to your site.

Sincerely,


John

jag

unread,
Feb 29, 2008, 5:06:51 AM2/29/08
to thin-ruby
blog by the way, is at...

http://www.red91.com

and your article is at...

http://www.red91.com/articles/2008/02/27/mongrel-on-a-diet-fedora-cleanups

Again, good work!

macournoyer

unread,
Feb 29, 2008, 8:17:38 AM2/29/08
to thin-ruby
That's very old stuff jag (one month old, aye!) :)

Thin now has builtin cluster support throught the --servers option:

thin start -s3

to start a cluster of 3 servers

On Feb 29, 5:06 am, jag <indieh...@gmail.com> wrote:
> blog by the way, is at...
>
> http://www.red91.com
>
> and your article is at...
>
> http://www.red91.com/articles/2008/02/27/mongrel-on-a-diet-fedora-cle...
>
> Again, good work!

jag

unread,
Apr 11, 2008, 8:43:34 AM4/11/08
to thin-ruby
thanks macournoyor, gathered that after i played around with the
options some more.

to update, i've moved all my rails projects over to thin + nginx and
they're handling the load much better; I was getting worried for a
moment with scaling issues whether or not to move over to merb but i
was thankful to find this lighter web server.

all the best,



John.

wuz

unread,
Apr 13, 2008, 10:40:37 PM4/13/08
to thin-ruby
it is a nice issue,
and
I have to use
thin stop -s3 -p 5000
when I use
thin start -s3 -p 5000
to start.
Reply all
Reply to author
Forward
0 new messages