Marginalia: SQL query comments that tell you which controller caused the slow query
Attach comments to ActiveRecord's SQL queries
At a glance
- What is it?
- A Basecamp gem that appends application, controller and action annotations to every ActiveRecord query so slow queries can be traced back to the code that caused them. It remains relevant below Rails 7, but the README states that from Rails 7 this job belongs to ActiveRecord::QueryLogs.
- Who is it for?
- Adopt Marginalia if you run Rails 5.2 through 6.1 and want slow query logs annotated with the controller and action behind them. Do not add it to a Rails 7 or later application, because the README states Marginalia is part of Rails itself from version 7 through ActiveRecord::QueryLogs and does not need to be included separately.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 60 days ago.
- What is it written in?
- Mainly Ruby, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem is a log full of identical SQL with no owner
A Rails application in production can produce SQL queries that look almost identical to each other. Marginalia attaches a trailing comment to each query naming the application, the controller and the action that produced it. The effect on a log line is this shape:
Account Load (0.3ms) SELECT `accounts`.* FROM `accounts`
WHERE `accounts`.`queenbee_id` = 1234567890
LIMIT 1
/*application:BCX,controller:project_imports,action:show*/The value is not cosmetic. Once the comment is present, the README notes you can feed the logs to a tool such as pt-query-digest to identify controllers and actions that are hotspots for slow queries. The gem was created at 37signals, where the application name in the example is BCX.
Installation is one line, and the version window starts at Rails 7 ending this
The gem is declared in the Gemfile:
# Gemfile
gem 'marginalia'The support statement is where the decision gets made. The README says it has been tested and used in production with the mysql2 and pg gems, is tested on Rails 5.2 through 6.1 and Ruby 2.6 through 3.0, and is also tested for sqlite3.
It then draws the line: as of Rails 7, Marginalia is part of Rails itself and does not need to be separately included. The linked class is ActiveRecord::QueryLogs. Rails version support follows the supported versions in the Ruby on Rails maintenance policy, and Ruby support follows maintained versions in the Ruby maintenance policy, so the window is defined by upstream rather than by the gem.
The repository layout is small, which fits the scope: lib/, test/, marginalia.gemspec, Rakefile and Gemfile, with no runtime configuration files to reconcile. The last push was on 2026-08-24 and there are no GitHub releases.
Marginalia::Comment.components decides what appears in every annotation
The annotation is assembled from a list of components, and the default is three symbols:
Marginalia::Comment.components = [:application, :controller, :action]which produces the comment body application:#{application_name},controller:#{controller.name},action:#{action_name}. The list is an array, so you can reorder it or drop entries from it. The application name itself is configurable in an initializer such as config/initializers/marginalia.rb:
Marginalia.application_name = "BCX"and it defaults to your Rails application name.
The array is also extensible, not just editable. You add a component by defining a module method on Marginalia::Comment that returns a string, then name that symbol in the list:
module Marginalia
module Comment
def self.mycommentcomponent
"TEST"
end
end
end
Marginalia::Comment.components = [:application, :mycommentcomponent]The calling controller is reachable from these methods through @controller, which is how a custom component can reach request state without the gem passing it in.
Nine extra components ship, and :line is the one with a twist
Beyond the three defaults the README lists implementation for :line, :controller_with_namespace, :job, :hostname, :pid, :db_host, :socket and :database. Several of those are infrastructure oriented: :hostname uses Socket.gethostname, :pid is the current process id, :db_host and :database carry configured database values, and :socket carries the configured database socket.
:job includes the classname of the ActiveJob being performed, which matters because background work has no controller. :controller_with_namespace gives the full classname including namespace, useful when several controllers share a short name.
:line is the file and line number of the calling query, and it is the only component with its own configuration key. Marginalia::Comment.lines_to_ignore takes a regular expression used to exclude parts of the stacktrace from the line comment. Without that filter the reported line points into framework internals rather than into the code you would edit, which is why the setting exists.
MySQL truncates past 1024 bytes, so prepend_comment moves the annotation
By default the comment goes at the end of the query. That is a problem on MySQL, which truncates the query text. The README is specific about where this bites: slow query logs, and the results of querying some InnoDB internal tables, where the query length exceeds 1024 bytes.
If the annotation is appended, it lands in the part that gets cut, and you lose exactly the information you added the gem for. The fix is one setting:
Marginalia::Comment.prepend_comment = trueThis is worth knowing before you spend an afternoon wondering why annotations vanished from a slow query log. It is also a reminder that the component list interacts with your database: a long set of components on a database that truncates means the useful parts should sit at the front.
with_annotation adds a one-off marker to a single query
Component based annotations describe the request or job as a whole. For marking one specific query, there is a block API:
Marginalia.with_annotation("foo") do
Account.where(queenbee_id: 1234567890).first
endThe query then carries both comments, with the inline one appended after the component comment:
/*application:BCX,controller:project_imports,action:show*/ /*foo*/Nesting with_annotation blocks concatenates the comment strings rather than replacing the outer one. That makes it possible to wrap a whole service in a marker and then mark individual queries inside it, at the cost of a string that grows with each level. Marginalia is not designed to be compared against, so this is a log reading feature only.
High cardinality components and prepared statements do not mix
There is one genuine failure mode documented, and it is worth reading before you add components. The README warns about prepared statements: if you use a component such as request_id, then every query becomes unique, so ActiveRecord creates a new prepared statement for each one, which can exhaust system resources.
The fix is on the Rails side rather than in the gem. The README links the configuration guide entry for disabling prepared statements on a PostgreSQL database, and the guidance is to do that if you want to use components with high cardinality values.
This is the cost of annotation granularity.
Running the tests means creating three databases first
The contributing instructions are specific enough to be useful. Bundling and creating the test databases come first:
bundle
rake db:mysql:create
rake db:postgresql:createThen a bare rake runs the tests across every adapter the project covers:
rakeThe adapters named are mysql, mysql2, postgresql and sqlite. So a full local run needs a MySQL server and a PostgreSQL server running, not just the gem installed. The README says the gem has been tested and used in production with mysql2 and pg, and it is also tested for sqlite3.
Licensing is MIT, which is the simplest case for redistribution: no copyleft obligation attaches to an application that merely depends on the gem.
Editorial conclusion
Adopt Marginalia if you run Rails 5.2 through 6.1 and want slow query logs annotated with the controller and action behind them. Do not add it to a Rails 7 or later application, because the README states Marginalia is part of Rails itself from version 7 through ActiveRecord::QueryLogs and does not need to be included separately. Verify first whether your slow query log truncates long queries, since MySQL does so past 1024 bytes and that is the case where Marginalia::Comment.prepend_comment changes where the annotation lands.
Frequently asked questions
Is Marginalia still needed on Rails 7?
No. The README states that as of Rails 7, Marginalia is part of Rails itself and does not need to be separately included. The equivalent is ActiveRecord::QueryLogs, and ActiveRecord::QueryLogs is the class the README links.
How do I add the marginalia gem to a Rails application?
Add gem 'marginalia' to the Gemfile. The README notes it has been tested on Rails 5.2 through 6.1 with Ruby 2.6 through 3.0, and with the mysql2 and pg gems.
Why are my Marginalia comments missing from the MySQL slow query log?
MySQL truncates query text past 1024 bytes, including in the slow query log, so an appended comment can be cut off. Setting Marginalia::Comment.prepend_comment = true moves the comment to the front of the query.
Which comment components does Marginalia provide beyond application, controller and action?
The README lists :line, :controller_with_namespace, :job, :hostname, :pid, :db_host, :socket and :database. You can also define your own by adding a module method to Marginalia::Comment and adding its symbol to Marginalia::Comment.components.
What breaks if I use a request_id comment component in Marginalia?
Every query becomes unique, so ActiveRecord creates a separate prepared statement per query and may exhaust system resources. The README points at the Rails configuration for disabling prepared statements on PostgreSQL.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/basecamp-marginalia)Was this useful?