Slow Queries
Queries taking longer than expected. Start with pressure metrics, then narrow down.
Start here when queries are slow
Observe JSON endpoints require an operator session or accepted credential by default; curl examples below use a bearer token.
Quick diagnosis
Section titled “Quick diagnosis”# Check pressure metricscurl -s -H "Authorization: Bearer <token>" http://localhost:8080/observe/pressure | jq
# Check health statuscurl -s -H "Authorization: Bearer <token>" http://localhost:8080/observe/doctor | jq '.checks[] | select(.status != "healthy")'Symptom: All queries are slow
Section titled “Symptom: All queries are slow”Check 1: Query permit pressure
Section titled “Check 1: Query permit pressure”curl -s -H "Authorization: Bearer <token>" http://localhost:8080/observe/pressure | jq '.metrics[] | select(.name == "query_permit") | .value'| Value | Meaning | Action |
|---|---|---|
| < 0.8 | Normal | Not the bottleneck |
| 0.8-0.95 | High | Queries queuing — check database |
| > 0.95 | Saturated | Increase database.channel-pool-size |
Check 2: Database connection pool
Section titled “Check 2: Database connection pool”curl -s http://localhost:8080/metrics \ | grep -E 'scribe_db_connections_(pending|active)|scribe_db_pool_pressure'If scribe_db_connections_pending > 0:
- Queries are waiting for database connections
- Increase pool size or reduce query concurrency
Check 3: Memory pressure
Section titled “Check 3: Memory pressure”curl -s -H "Authorization: Bearer <token>" http://localhost:8080/observe/pressure | jq '.metrics[] | select(.name == "jvm_memory") | .value'If > 0.85: Increase heap size (-Xmx)
Symptom: Specific queries are slow
Section titled “Symptom: Specific queries are slow”Check 1: Slow query analysis (pg_stat_statements)
Section titled “Check 1: Slow query analysis (pg_stat_statements)”If pg_stat_statements is enabled, the built-in monitoring shows slow queries:
curl -s -H "Authorization: Bearer <token>" http://localhost:8080/observe/stats/queries | jqExample response:
{ "queries": [ { "queryId": "1234567890", "calls": 15420, "meanMs": 45.2, "totalMs": 697344.0, "rows": 30840, "queryPreview": "Directory lookup by distinguished name (redacted)" } ], "timestamp": "2026-01-24T12:00:00Z"}If you see "error": "query statistics are unavailable", see Enabling query statistics below.
Check 2: Missing indexes
Section titled “Check 2: Missing indexes”curl -s -H "Authorization: Bearer <token>" http://localhost:8080/observe/hints | jq '.hints[] | select(.severity == "warning")'Add the suggested indexes.
When a Query Diagnostic Report is available, open the Corpus context section before changing configuration. It shows how many entry types and attributes were considered, whether the captured statistics are fresh, and whether support has enough context to replay the case at a comparable scale. Missing context does not mean the report is unusable; it tells you which data point was unavailable when the report was captured.
Check 3: Query complexity
Section titled “Check 3: Query complexity”Enable tracing to identify slow stages:
SCRIBE_TRACES_ENABLED=true ./identity-scribeCheck spans for bottlenecks:
Query.Execute— Database execution timeQuery.Plan— Planning overhead (complex filters)Query.Map— Result mapping (too many attributes)
Common fixes
Section titled “Common fixes”| Problem | Solution |
|---|---|
| High permit pressure | Increase database.channel-pool-size |
| Pending DB connections | Increase database.max-pool-size |
| Memory pressure | Increase -Xmx heap size |
| Missing indexes | Add indexes per /observe/hints |
| Complex filters | Add type constraint, simplify filter |
| Large result sets | Use pagination (paged results, cursors) |
Enabling query statistics
Section titled “Enabling query statistics”pg_stat_statements records per-query execution statistics. IdentityScribe's Queries tabs (Slow Log, Stats) and the /observe/stats/queries endpoint use it when available.
Enabling it takes two steps, in this order:
- Preload the library — add
pg_stat_statementsto PostgreSQL'sshared_preload_libraries, then restart the server. Preloading activates the statistics collector and only takes effect at startup. - Enable the extension — enable the
pg_stat_statementsextension once in the IdentityScribe database. In development and staging IdentityScribe can do this for you (see below).
Enabling the extension before the library is preloaded leaves it half-enabled — it exists, but every read fails. Always preload and restart first.
Once both steps are done the Queries tabs populate automatically. Enabling the extension after IdentityScribe is already running needs no restart or redeploy — the next refresh picks it up.
Self-hosted PostgreSQL
Section titled “Self-hosted PostgreSQL”Append to postgresql.conf (keep any value that is already there — don't overwrite it):
shared_preload_libraries = 'pg_stat_statements'Restart PostgreSQL, then enable the pg_stat_statements extension in the IdentityScribe database — or set database.auto-create-query-stats-extension = true (see below) to let IdentityScribe enable it for you.
On Postgres.app (macOS) the config file is at ~/Library/Application Support/Postgres/var-<version>/postgresql.conf — confirm the exact path with SHOW config_file;, edit it, then restart from the Postgres.app menu.
Docker
Section titled “Docker”Add the preload flag to the Postgres service command and recreate the container:
services: postgres: image: postgres:18-alpine command: ["postgres", "-c", "shared_preload_libraries=pg_stat_statements"]Managed PostgreSQL services
Section titled “Managed PostgreSQL services”| Provider | How to enable |
|---|---|
| AWS RDS | Add pg_stat_statements to the parameter group's shared_preload_libraries, reboot the instance |
| Azure Database | Server Parameters → shared_preload_libraries |
| Google Cloud SQL | Database Flags → cloudsql.enable_pg_stat_statements |
| DigitalOcean | Enabled by default on managed databases |
After the reboot, enable the pg_stat_statements extension once against the IdentityScribe database.
Let IdentityScribe enable the extension (dev / staging)
Section titled “Let IdentityScribe enable the extension (dev / staging)”For non-production environments, IdentityScribe can enable the extension for you at startup once the library is preloaded:
database.auto-create-query-stats-extension = trueThe default is auto — on in development, off everywhere else — so you can opt staging in while production stays untouched. It still requires the library to be preloaded (step 1) and a database role allowed to create extensions; if either is missing, startup logs an actionable note and continues without the feature. Leave it off in production until you have assessed the impact of pg_stat_statements on your workload.
Verification
Section titled “Verification”curl -s -H "Authorization: Bearer <token>" http://localhost:8080/observe/stats/queries | jqA populated queries array confirms it is working. "error": "query statistics are unavailable" means the extension is either not installed, or installed but not preloaded — the Queries tab shows which, with a one-click guide.
Without pg_stat_statements
Section titled “Without pg_stat_statements”If you cannot enable pg_stat_statements, you can still diagnose slow queries using:
- Tracing: Enable
SCRIBE_TRACES_ENABLED=trueto see per-query execution times - Hints: Check
/observe/hintsfor missing index recommendations - Database slow-query logs: Follow your provider's logging guide to capture requests over your chosen duration threshold
Related
Section titled “Related”- Health and Monitoring — Dashboards and operational workflows
- Signals — Golden signals and error classification
- PromQL Recipes — Alert queries and dashboards
- Logging and Traces — Trace layout and debugging