Using the Octopus gem!
TOC
Heroku allows you to easily add horizontal scaling to your application by way of adding additional dynos to meet capacity. Heroku Postgres too allows you to horizontally scale your database by adding followers to your primary database, streaming write-ahead-logs to each follower, keeping it up to date. While these followers are great for analytical purposes and as hot-standby databases, you can also use them as a part of your application for handling read-only queries1 for your data.
You can do this manually with ActiveRecord, establishing the connection to a follower for a given query, but I recommend the Octopus gem. This gem adds sharding and replication features to ActiveRecord, and in this post I’ll add that gem to a Ruby on Rails application to leverage a follower databases, distributing read operations to the follower. Horizontal scaling ftw ☜(゚ヮ゚☜).
Prerequisites:
In a normal Octopus setup you hard code your shard and replication definitions in a file called
. Because Heroku advocates 12 Factor applications, hardcoding your database information is not going to work. We need a a dynamic 1
config/shards.yml
file, as well as an initializer file, specially catered to the Heroku environment.1
shards.yml
Start by adding the Ocotpus gem to your
and running 1
Gemfile
1
bundle install
Next, addd the dynamic
:1
config/shards.yml
This dynamically populates the appropriate configurations for Octopus to use your follower(s) for read operations.
Get the file by running this from your project root:
Next, add this initializer to
:1
config/initializers
The initializer adds some convenience methods like
and setups some additional logging.1
Octopus.followers
Get the file by running this from your project root:
Commit those changes in git and test out your setup locally. In development mode Octopus will simulate 2 followers for convenience:
With this setup, your followers will not be marked as ‘fully replicated’; a fully replicated application will send all writes to the primary database, and send all reads to followers. This might be A Bad Idea™, depending on your application. Because of this we set
in the dynamic 1
fully_replicated: false
. You have to add a 1
config/shards.yml
call to each model you want to be a replicated model, or explicitly query them using methods to have queries sent to your followers.1
replicated_model
This will configure the
class to be replicated, and read queries will be sent to the follower.1
Person
You can explicitly use a follower as well:
(ノ^_^)ノ Hooray, you’re set! You’re now distributing reads to a follower database.
You can choose the followers you want to use for responding to read request with the environment variables
and 1
SLAVE_ENABLED_FOLLOWERS
. Whitelist followers you want or blacklist the followers you don’t want:1
SLAVE_DISABLED_FOLLOWERS
You should do this.
Without these variables set, all followers will be used for reads. This may be undesirable for you. One example of usage is when adding an additional follower to a live application, where the above ENV vars are used to ensure the new follower is excluded until it is sufficiently caught up to master for duty. Other people may have forks or just random databases attached to the app. Without a white/black list, those random possibly databases can be used for reads.
Scaling dynos on Heroku is an easy and great way to scale your application, but the scaling doesn’t have to stop there. By horizontally scaling your databases, you distribute the workload placed on the primary database and build in resiliency for your application. By using Octopus to handle the connections, you gain some fault tolerance across several followers, and flexibility with which ones to use.
Thanks to Evan Prothro for writing the original dynamic shards.yml file and octopus.rb initializer file, as well as the original wiki guide for setting up Octopus on Heroku which the blog post is heavily based on. Thanks also to everyone who has contributed to the Octopus gem.
Postgres followers are read-only and cannot be written to. ↩
I made one for you: catsby/distributed-reads ↩