Last Tuesday, a query that had been fast for months suddenly started eating CPU. The on-call engineer spent forty minutes reading EXPLAIN output before realizing a column type change had killed the index. I have been there too, and it is why I built a sentinel that does not wait to be asked. The tool does not replace a DBA; it shortens the gap between noticing a slow query and understanding why it is slow.
MonkeyCode is an open-source AI coding assistant that currently offers free model access and a free server option. Disclosure: This article was prepared as part of MonkeyCode's product outreach. The free server is not a production environment, but it is an ideal place to run a monitoring tool that only needs to read query statistics. The sentinel I describe below pulls the slowest queries from pg_stat_statements, formats them with their execution plans, and asks a free model to explain the root cause in plain language.
The Architecture of a Self-Explaining Sentinel
A self-explaining sentinel has four parts: a collector, a formatter, a model caller, and a storage layer. The collector reads cumulative statistics, so it does not need to run continuously. The formatter turns raw query text and plan JSON into a prompt that asks for specific advice. The model caller is the only piece that depends on MonkeyCode's free model access, and the storage layer keeps every recommendation for later review.
Step 1: Prepare the Free Server Database
Create a database on the free server and enable pg_stat_statements. The extension ships with standard PostgreSQL, so you only need to adjust shared_preload_libraries and restart the instance. The snippet below also creates a table for storing recommendations, which keeps the output of every model call in one place.
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
CREATE TABLE IF NOT EXISTS query_advice (
id BIGSERIAL PRIMARY KEY,
query_id BIGINT,
query_text TEXT,
plan JSONB,
advice TEXT,
impact_estimate TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
Step 2: Write the Collector
The collector queries pg_stat_statements for the top queries by total execution time, then fetches a fresh EXPLAIN (FORMAT JSON) for each one. It writes the raw data to a staging list so the model caller can process them later. Filtering out system queries is important, because you want advice about application SQL, not about internal catalog lookups.
import json
import psycopg2
def collect_slow_queries(dsn: str, limit: int = 10) -> list[dict]:
with psycopg2.connect(dsn) as conn:
with conn.cursor() as cur:
cur.execute("""
SELECT queryid, query, calls, total_exec_time
FROM pg_stat_statements
WHERE query NOT LIKE '%%pg_%%'
ORDER BY total_exec_time DESC
LIMIT %s
""", (limit,))
rows = cur.fetchall()
items = []
for queryid, query, calls, total_ms in rows:
cur.execute("EXPLAIN (FORMAT JSON) " + query)
plan = cur.fetchone()[0]
items.append({
'query_id': queryid,
'query': query,
'calls': calls,
'total_ms': total_ms,
'plan': plan,
})
return items
Step 3: Ask the Free Model for an Explanation
This is where the free model access comes in. The function below sends the query text and the plan JSON to a model endpoint and asks for a structured answer. The endpoint and key are placeholders because MonkeyCode's exact interface may change; adapt them to the project's documentation.
import os
import requests
def ask_model(query: str, plan: dict) -> dict:
prompt = f"""
Explain why this SQL query is slow and suggest a concrete fix.
Query: {query}
Plan: {json.dumps(plan)}
Respond with JSON: {{"advice": "...", "impact": "high|medium|low"}}
"""
resp = requests.post(
os.environ["MODEL_ENDPOINT"],
headers={"Authorization": f"Bearer {os.environ['MODEL_KEY']}"},
json={"messages": [{"role": "user", "content": prompt}]},
timeout=30,
)
return resp.json()["choices"][0]["message"]["content"]
A typical response looks like this: the model points out a sequential scan on a large table, suggests a composite index, and marks the impact as high. You do not need to trust it blindly; the value is that it gives you a starting point for your own investigation.
Step 4: Store and Rank the Advice
Parse the model response and insert it into the query_advice table. A simple SELECT then ranks recommendations by the impact field, so the most valuable fixes appear first. This turns a pile of raw plans into a prioritized backlog.
def store_advice(dsn: str, item: dict, advice: dict) -> None:
with psycopg2.connect(dsn) as conn:
with conn.cursor() as cur:
cur.execute("""
INSERT INTO query_advice (query_id, query_text, plan, advice, impact_estimate)
VALUES (%s, %s, %s, %s, %s)
""", (item['query_id'], item['query'], json.dumps(item['plan']),
advice['advice'], advice['impact']))
SELECT impact_estimate, advice, created_at
FROM query_advice
ORDER BY CASE impact_estimate WHEN 'high' THEN 0 WHEN 'medium' THEN 1 ELSE 2 END;
Step 5: Schedule and Review
Run the collector and the model caller from a cron job every hour. The free server can handle this load easily, and the free token allowance covers a small number of queries per hour. Review the advice table once a day and apply only the changes that survive your own reasoning.
*/60 * * * * cd /opt/sentinel && python3 run.py
Who Should Not Use This Approach
Do not point this sentinel at a database containing regulated personal data, because the free server is not a production environment. Do not treat model advice as truth; it is a hypothesis that needs a human to verify. Teams that already have a dedicated DBA might find the advice too generic, and teams that cannot tolerate any external call should skip the model step entirely.
The Position, Restated
A zero-dollar server can be more than a test sandbox; it can run a quiet assistant that explains the worst parts of your database. The value is not in the free tokens themselves but in the habit of asking why a query is slow before you rewrite it. If you want to try this pattern, MonkeyCode's free server and free model access are a low-cost way to start, and the open-source project is worth reading before you trust it.
Top comments (0)