Sqlcommenter for Postgres: Attribute Queries to Call-Sites
pg_stat_statements tells you which query shapes burn the most database time, but not which controller, route, or background job emitted them. Sqlcommenter solves that by prepending a structured SQL comment to every statement your ORM sends — and Postgres preserves that comment verbatim, so it shows up in your slow-query log and in TuneStack's call-site attribution card.
What a sqlcommenter-tagged statement looks like
/*controller='checkout',action='create',framework='django'*/
SELECT * FROM orders WHERE customer_id = $1The comment is a list of key='url-encoded-value' pairs separated by commas. The format is defined by the OpenCensus sqlcommenter spec and accepted by Postgres without any server-side configuration.
Postgres setup
Make sure pg_stat_statements retains comments. The default settings do this; double-check that pg_stat_statements.track_utility is off and pg_stat_statements.track is top (both defaults).
SHOW pg_stat_statements.track; -- top
SHOW pg_stat_statements.track_utility;-- offDjango (≥ 3.2)
Django ships sqlcommenter as part of django.db.backends.utils. Enable it in settings.py:
MIDDLEWARE = [
"google.cloud.sqlcommenter.django.middleware.SqlCommenter",
# ... your existing middleware
]
SQLCOMMENTER_WITH_ROUTE = True
SQLCOMMENTER_WITH_FRAMEWORK = True
SQLCOMMENTER_WITH_CONTROLLER = True
SQLCOMMENTER_WITH_ACTION = True
SQLCOMMENTER_WITH_DB_DRIVER = TrueInstall the package with pip install sqlcommenter-django. Statements now carry the route, controller, action, framework name, and driver — TuneStack groups them into one call-site key per request handler.
Rails (≥ 7.0)
Rails 7 ships query log tags natively. Enable in config/application.rb:
config.active_record.query_log_tags_enabled = true
config.active_record.query_log_tags = [
:application,
:controller,
:action,
:job,
]
config.active_record.query_log_tags_format = :sqlcommenterThe default format is Rails-style; flipping to :sqlcommenter emits the same k='v' grammar so TuneStack's parser picks it up.
SQLAlchemy (2.x)
Use the opencensus-ext-sqlalchemy shim or attach an event hook manually. Minimal manual setup:
from sqlalchemy import event
from urllib.parse import quote_plus
import contextvars
current_call_site = contextvars.ContextVar("call_site", default={})
@event.listens_for(engine, "before_cursor_execute", retval=True)
def add_sqlcommenter(conn, cursor, statement, params, ctx, executemany):
meta = current_call_site.get()
if not meta:
return statement, params
pairs = ",".join(
f"{k}='{quote_plus(str(v))}'" for k, v in sorted(meta.items())
)
return f"/*{pairs}*/ {statement}", paramsSet current_call_site from your request middleware (FastAPI, Flask, etc.) before each handler runs.
OpenTelemetry instrumentation
The OpenTelemetry Python auto-instrumentation for Psycopg / SQLAlchemy can inject a traceparent sqlcommenter pair so each query is joinable against the trace that issued it. Enable with:
OTEL_PYTHON_INSTRUMENTATION_COMMENTER=true
OTEL_PYTHON_INSTRUMENTATION_COMMENTER_OPTIONS=traceparentHow TuneStack consumes the metadata
The collector reads pg_stat_statements on a timer, extracts the leading or trailing block comment from each query, and emits a canonical alphabetically-sorted call-site string — so two snapshots with the same metadata in different key order dedup to the same bucket. The dashboard's Call-site attribution card aggregates total execution time and call counts per call-site, and surfaces the unattributed share so you can find services that haven't enabled sqlcommenter yet.
What if my values contain quotes or commas?
Sqlcommenter values are URL-encoded before being wrapped in single quotes. A controller name like order/cancel becomes order%2Fcancel on the wire and decodes back when the collector parses it. Most ORM helpers do this automatically.
Caveats
- Cache key churn: if your pool uses prepared- statement caching (PgBouncer transaction mode does not), the comment will be part of the cache key. Most stacks aren't affected.
- Comment size: Postgres preserves comments up to
track_activity_query_size(default 1024 bytes). Long traceparent + full route strings can push the actual SQL out of the log. Either raise the limit or trim the metadata set. - Don't leak user input: never put user-supplied values (usernames, IDs) into the comment. The call-site is the route name; everything below that lives in query parameters.
Related: Workload Attribution · pg_stat_statements · Reading EXPLAIN Plans