Skip to content

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 = $1

The 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;-- off

Django (≥ 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  = True

Install 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 = :sqlcommenter

The 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}", params

Set 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=traceparent

How 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

Try it on your database

TuneStack analyzes your real query workload — not hypothetical examples.

See it on the demo dashboard →
Sqlcommenter for Postgres: Attribute Queries to Call-Sites | TuneStack