Skip to content

Production Deployment

Radim Řehůřek edited this page Jun 20, 2013 · 16 revisions

Starting a rails app with rails server works fine for development and quick-and-dirty demo, but is not the way you'd want to do a production deployment or expose the app to the world.

We'll cover some basics of Rails 'environments', mention Passenger as one easy way to deploy a Rails app, and then discuss a few specific issues of Umlaut. Those already very familiar with Rails deployment may still want to check out the Umlaut section at the bottom.

Rails environments

Rails apps are set up to be run under a number of 'environments' -- by default, development, test, and production. See more at the Rails Getting Started Guide.

Databases

If you look in your ./config/database.yml, you'll see you (potentially) have a database defined for each environment. You'd generally want a different db for development and production, not share the same db.

When you ran rake db:migrate to create the Umlaut db schema, it by default ran under the development app environment. To create the app's schema in the configured production database, you set the RAILS_ENV env variable before running the command, like so:

RAILS_ENV=production rake db:migrate
  • If you have installed a new version of Umlaut with new schema changes, you can run this again.
  • But it may be tricky to do that in production with zero downtime, although it can be done with very minimal downtime. Run the migrations in the new version of the app, then immediately restart the app (see below under Passenger).

Config

As in all standard Rails apps, some environment-specific config can be found in ./config/environments/development.rb or ./config/environments/production.rb, respectively. You can look in there to see how standard Rails apps are configured differently in development vs production, and change settings if desired.

(You can create your own new environment, like 'staging', pretty much simply by creating a ./config/environments/staging.rb and referencing a db in ./config/database.yml).

In other places where you need to decide inline what to do based on environment, you can check the Rails.env variable, which will have a string in it. For instance, maybe in config in ./app/controllers/UmlautController. Not ideal, but it works.

Asset Pipeline

Rails 3.1+ plus apps by default use an Asset Pipeline (see Rails Guide) to compile, aggregate, and fingerprint CSS, JS, and images, before delivery to the browser.

The asset pipeline compilation process requires a JS runtime -- the gem 'therubyracer' line in your Gemfile is one way to make one available to the app, if there wasn't a host-installed JS runtime findable by rails.

In development, for convenience, the asset pipeline compiles assets on demand, and doesn't actually aggregate them. However, in production, the default settings expect precompiled assets to be available, and will not compile them 'on demand' in a running app.

If you run rake assets:precompile in your app, it will compile assets to ./public/assets. You can run this in your app on the production server/location, or you can run it in another location and copy ./public/assets over to production location. (This way you wouldn't need a JS runtime at all on production server, as far as the asset pipeline is concerned).

Some people use a tool call Capistrano for automating Rails app deployment process, including things like precompiling assets when you deploy. jrochkind has never gotten into capistrano, finding the learning curve not worth whatever time it would save him for his relatively simple deploy process. But some people swear by it for Rails apps.

Testing setup under production environment

To make sure your app is functioning properly under its "production" environment, you can test things out by running:

rails server -e production

from your app directory. Except you'll need to do one thing first, in ./config/environments/production.rb change to:

  config.serve_static_assets =  true

(In a normal production deployment, you'd have a web server like apache or nginx serving static assets (images, css, js) over HTTP. Which is generally more efficient/performant than having the rails app do so. But when you start directly with rails server, we need to tell the rails server to do so.)

You would not want to actual deploy in production by starting with rails server, but it's a way to make sure everything is set up right that may be different in production vs development. (production db, compiled assets, etc)

Passenger

One popular pretty easy to set up Rails production deployment mechanism is to use Passenger.

That's what jrochkind uses. There are certainly other ways to deploy Rails apps too, but Passenger is an easy and popular one.

Passenger is an add-on to a web server, either apache or nginx. If you already have plans for an apache server on your production host or are generally more comfortable with apache, it's fine to go with that. (that's what jrochkind uses). If you have no preference, some people find nginx to be more performant and simpler. (There's also 'passenger standalone' which basically uses an embedded nginx).

Here's the Complete Passenger User's Guide for apache version.

Installing passenger and setting up your app to deploy under it is fairly straightforward.

Some other useful stuff about passenger:

  • One way to restart a running app deployed under passenger is by touch ./tmp/restart.txt in your app. Touched timestamp on restart.txt will tell Passenger to restart the app. You might want to do this after you deploy a new version, for instance.

  • Passenger will start up multiple instances of your app as needed to handle load. The passenger-status command (may need to run with sudo) will give you some status information on how many processes are currently running and what they're doing.

  • While Passenger will automatically start up more instances as needed, it takes time to spin up a new instance, and there is a max number of instances configured. If passenger-status gives you a sense of how many instances you typically need, you can set Passenger config settings to determine whether and how many instances are automatically started up even before the first request, and the maximum number of instances Passenger is willing to spin up.

  • You can set up Passenger to deploy your app to a sub-uri of your web host, rather than taking over the whole web host at root URI. See deploying Rails to a sub uri in Pasenger apache docs. However, as of this writing (Rails 3.1.3), there is a bug in Rails where the asset pipeline won't work properly in this scenario. It's possible to locally "monkey patch" Rails in your app to work in this scenario, see for instance this stack overflow.

Umlaut-Specific Concerns

Concurrency

Umlaut does use multi-threaded concurrency under the hood, including with ActiveRecord. This seems to work fine with Passenger, and probably should work fine with other deployment options, but something to be aware of.

It's not typical for Rails apps to use multi-threaded concurrency the way Umlaut does, but it still seems to jrochkind to be the best feasible solution to Umlaut's particular problem space.

At any given point, there could be background threads running in Umlaut performing background service plugins. I am not sure if passenger restart will keep from shutting down an instance until bg threads complete, or will shut down killing bg threads. Umlaut should be fairly tolerant of background service plugin threads dying unexpectedly. Effected requests waiting on bg responses (with spinners in UI) will time them out after configured background_service_timeout, and note in errors section that they've been timed out; if the request is reloaded after configured requeue_failedtemporary_services_in, those service plugins should be re-executed.

NOTE WELL Rails 4 in-development ActiveRecord handling of concurrency is under constant evolution, we'll have to see what happens and how it effects Umlaut.

Patch to ActiveRecord ConnectionPool

Be aware, Umlaut replaces ActiveRecord's ConnectionPool implementation with a better one that actually seems to work properly under concurrency. See ./active_record_patch/connection_pool.rb in Umlaut source. Based on code from https://github.com/rails/rails/pull/6492,

Number of instances

Umlaut's requests are somewhat slow, probably mostly because an Umlaut request depends on slow external third party APIs. (But it's certainly possible some of this is Umlaut's fault in particular, perhaps with concurrency).

Either way, this might mean you need more running instances in your cluster (for instance max or min instances configured with Passenger) than you might expect for an ideal/model/typical Rails app.

Number of connections to your database

Umlaut's use of concurrency with ActiveRecord, and the way ActiveRecord handles multi-threading, means that an individual app instance needs to be configured with a larger number of connections to the database server in the connection pool than usual for a Rails app.

In a deployment environment, there will be generally multiple instances of the app running, each of which needs that number of connections.

You should ensure your mysql server is configured to support sufficient connections. For instance, if there can be 10 umlaut instances running each with a connection pool of 15, that would require 150 connections to the mysql server (plus whatever is required by additional applications using that server).

Service Logging

Umlaut outputs some unusual lines regarding tracking Service plugin execution to the logs. See Logs

Nightly maintenance

Don't forget to run the nightly maintenance rake task in your production instance too. It will load a bit of data from SFX (if so configured to do so), as well as delete old un-needed data from production databases. A cronjob exec line might look like:

cd /my/installed/production/umlaut_app && RAILS_ENV=production rake umlaut:nightly_maintenance

Robots.txt

You may wish to include a robots.txt to stop Google (et al) from trying to spider the potentially infinite number of possible citation pages (or the finite but very very high number of titles listed in your A-Z list).

If you are deploying your app at web server root, just add a file at your application's ./public/robots.txt