Performance Promise
The performance_promise gem enables you to annotate and validate the performance of your Rails actions.
You can declare the performance characteristics of your Rails actions in code (right next to the action definition itself), and performance_promise will monitor and validate the promise. If the action breaks the promise, the performance_promise gem will alert you and provide a helpful suggestion with a passing performance annotation.
Example syntax:
class ArticlesController < ApplicationController
Performance :makes => 1.query,
:full_table_scans => [Article]
def index
# ...
end
Performance :makes => 1.query + Article.N.queries,
:full_table_scans => [Article, Comment]
def expensive_action
# ...
end
end
You may also choose to enable a default/minimum performance promise for all actions by turning on the untagged_methods_are_speedy config parameter (see here). You can reap all the benefits of performance validation without having to make any changes to code except tagging expensive actions.
Installation
You can install it as a gem:
gem install performance_promise
or add it to a Gemfile (Bundler):
group :development, :test do
gem 'performance_promise'
end
Building
You can build the gem yourself:
gem build performance_promise.gemspec
Configuration
For safety, performance_promise is disabled by default. To enable it, create a new file config/initializers/performance_promise.rb with the following code:
require 'performance_promise/performance.rb'
PerformancePromise.configure do |config|
config.enable = true
# config.validations = [
# :makes, # validate the number of DB queries made
# ]
# config.untagged_methods_are_speedy = true
# config.speedy_promise = {
# :makes => 2.queries,
# }
# config.allowed_environments = [
# 'development',
# 'test',
# ]
# config.logger = Rails.logger
# config.throw_exception = true
end
PerformancePromise.start
Then, extend your ApplicationController in app/controllers/application_controller.rb:
class ApplicationController < ActionController::Base
extend MethodDecorators
...
Usage
To understand how to use performance_promise, let's use a simple Blog App. A Blog has Articles, each of which may have one or more Comments.
Here is a simple controller:
class ArticlesController < ApplicationController
def index
@articles = Article.all
end
end
Assuming your routes and views are setup, you should be able to successfully visit /articles.
You can annotate this action with a promise of how many database queries the action will make so:
class ArticlesController < ApplicationController
Performance :makes => 1.query
def index
@articles = Article.all
end
end
Visit /articles to confirm that the view is rendered successfully again.
Now suppose, you make the view more complex, causing it to execute more database queries
Performance :makes => 1.query
def index
@articles = Article.all
@total_comments = 0
@articles.each do |article|
@total_comments += article.comments.length
end
puts @total_comments
end
Since the performance annotation has not been updated, visiting /articles now will throw an exception. The exception tells you that the performance of your view does not respect the annotation promise.

Let's update the annotation:
Performance :makes => 1.query + Article.N.queries
def index
@articles = Article.all
@total_comments = 0
@articles.each do |article|
@total_comments += article.comments.length
end
puts @total_comments
end
Now that you have annotated the action correctly, visiting /articles renders successfully.
The experienced code-reviewer however might ask the author to get rid of the N + 1 query here, and use .includes instead:
Performance :makes => 2.queries
def index
@articles = Article.all.includes(:comments)
@total_comments = 0
@articles.each do |article|
@total_comments += article.comments.length
end
puts @total_comments
end
And now, we've successfully caught and averted a bad code commit!
Advanced configuration
performance_promise opens up more functionality through configuration variables:
allowed_environments: array
By default, performance_promise runs only in development and testing. This ensures that you can identify issues when developing or running your test-suite. Be very careful about enabling this in production – you almost certainly don't want to.
throw_exception: bool
Tells performance_promise whether to throw an exception. Set to true by default, but can be overridden if you simply want to ignore failing cases (they will still be written to the log).
speedy_promise: hash
If you do not care to determine the exact performance of your action, you can still simply mark it as Speedy:
Speedy()
def index
...
end
A Speedy action is supposed to be well behaved, making lesser than x database queries, and taking less than y to complete. You can set these defaults using this configuration parameter.
untagged_methods_are_speedy: bool
By default, actions that are not annotated aren't validated by performance_promise. If you'd like to force all actions to be validated, one option is to simply default them all to be Speedy. This allows developers to make no change to their code, while still reaping the benefits of performance validation. If a view fails to be Speedy, then the developer is forced to acknowledge it in code.
FAQ
What is the strange syntax? Is it a function call? Is it a method?
We borrow the coding style from Python's decorators. This style allows for a function to be wrapped by another. This is a great use case for that style since it allows for us to express the annotation right above the function definition.
Credit goes to Yehuda Katz for the port of decorators into Ruby.
Will this affect my production service?
By default, performance_promise is applied only in development and test environments. You can choose to override this, but is strongly discouraged.
What are some other kinds of performance guarantees that I can make with
performance_promise?
In addition to promises about the number of database queries, you can also make promises on how long the entire view will take to render, and whether it performs any table scans.
Performance :makes => 1.query + Article.N.queries,
:takes => 1.second,
:full_tables_scans => [Article]
def index
...
end
If you come up with other validations that you think will be useful, please consider sharing it with the community by writing your own plugin here, and raising a Pull Request.
Is this the same as Bullet gem?
Bullet is a great piece of software that allows developers to help identify N + 1 queries and unused eager loading. It does this by watching your application in development mode, and alerting you when it does either of those things.
performance_promise can be tuned to not only identify N + 1 queries, but can also alert whenever there's any change in performance. It allows you to identify expensive actions irrespective of their database query profile.
performance_promise also has access to the entire database query object. In the future, performance_promise can be tuned to perform additional checks like how long the most expensive query took, whether the action performed any table scans (available through an EXPLAIN) etc.
Finally, the difference between bullet and performance_promise is akin to testing by refreshing your browser and testing by writing specs. performance_promise encourages you to specify your action's performance by declaring it in code itself. This allows both code-reviewers as well as automated tests to verify your code's performance.