Test

Augment Ruby web apps on Fly.io

Fly.io offers a number of native features that can improve the perceived speed and observability of web applications with minimal configuration. This gem automates some of the work required to take advantage of these features.

Regional replicas

Running database replicas alongside your apps in multiple regions is quick and easy with Fly's Postgresql cluster. This can increase the perceived speed of read-heavy applications.

The catch: in most primary/replica setups, you have one writeable primary located in a specific region. Fly solves this by allowing requests to be replyed, at the routing layer, in another region.

This repository includes the fly-ruby gem which will utomatcally route requests that write to the database to the primary region. It should work with any Rack-compatible Ruby framework.

Currently, it does this by:

  • modifying the DATABASE_URL to point apps to their local regional replica
  • replaying non-idempotent (post/put/patch/delete) requests in the primary region
  • catching Postgresql exceptions caused by writes to a read-only replica, and replaying these requests in the primary region

Requirements

You should have setup a postgres cluster on Fly. Then:

  • ensure that your Postgresql and application regions match up
  • ensure that no backup regions are assigned to your application
  • attach the Postgres cluster to your application with fly postgres attach

Finally, set the PRIMARY_REGION environment variable in your app fly.toml to match the primary database region.

Installation

Add to your Gemfile and bundle install:

gem "fly-ruby"

If you're on Rails, the middleware will insert itself automatically at the top of the Rack middleware stack.

Configuration

Most values used by this middleware are configurable. On Rails, this might go in an initializer like config/initializers/fly.rb

Fly.configure do |c|
  c.replay_threshold_in_seconds = 10
end

See the source code for defaults and available configuration options.

Known issues

This middleware send all requests to the primary if you do something like update a user's database session on every GET request.

If your replica becomes writeable for some reason, your custer may get out of sync.

TODO

Here are some ideas for improving this gem.

  • Add a helper to invoke ActiveJob, and possibly AR read/write split support, to send GET-originated writes to the primary database in the background