On RDS you enable slow query logging through the DB parameter group, not by editing config files: set slow_query_log = 1 and long_query_time on MySQL, or log_min_duration_statement on Postgres. Both are dynamic parameters, so no reboot is required if the instance already uses a custom parameter group. The logs land under Logs & events on the instance page, and the step most teams skip — exporting them to CloudWatch Logs — is what makes them actually searchable when something is slow at 2 a.m.
What’s happening
RDS does not give you SSH, so everything that would normally be a my.cnf or postgresql.conf edit goes through parameter groups. Instances start life on the default parameter group, which is immutable — if yours is still on default.mysql8.0 or default.postgres16, your first move is creating a custom group and attaching it (attaching a new parameter group requires a reboot for the association itself; the logging parameters afterwards do not). We covered the wider discipline of managing these in parameter group tuning.
On MySQL, the relevant parameters are slow_query_log (0/1), long_query_time (seconds, fractional allowed — production-useful values are 0.5-2, not the ancient default of 10), and optionally log_queries_not_using_indexes. log_output decides whether entries go to FILE (readable and exportable, use this) or TABLE (mysql.slow_log, which quietly eats storage and cannot be exported to CloudWatch).
On Postgres, there is no separate slow log file — slow statements go into the main postgres log when they exceed log_min_duration_statement (milliseconds; 500-2000 is a sane production range; 0 logs everything and will flood a busy instance). Two supporting extensions do the heavy lifting: pg_stat_statements for cumulative statistics (enabled by adding it to shared_preload_libraries — that one is static and needs a reboot) and auto_explain if you want plans logged with the slow statements.
One honest caveat: logging is not free. long_query_time = 0 or log_min_duration_statement = 0 on a high-throughput instance adds measurable I/O and can itself degrade performance. Log the slow tail, not the firehose.
What to check first
- Which parameter group the instance uses, and whether it is a default group — RDS > Databases > your-instance > Configuration.
- Whether logging is already on — plenty of instances have it enabled by a predecessor and nobody knew.
log_outputon MySQL — if it isTABLE, past slow queries are inmysql.slow_log, and storage may be quietly filling.- Whether Performance Insights is enabled — for “what is slow right now”, it answers faster than logs; the slow log’s value is the durable, detailed record.
How to verify
Confirm the current values without connecting to the database:
aws rds describe-db-parameters \
--db-parameter-group-name prod-pg16 \
--query "Parameters[?ParameterName=='log_min_duration_statement'].[ParameterValue,ApplyType]"
Or from a session:
-- MySQL
SHOW VARIABLES LIKE 'slow_query_log';
SHOW VARIABLES LIKE 'long_query_time';
SHOW VARIABLES LIKE 'log_output';
-- Postgres
SHOW log_min_duration_statement;
List and pull the log files themselves:
aws rds describe-db-log-files --db-instance-identifier prod-db
aws rds download-db-log-file-portion \
--db-instance-identifier prod-db \
--log-file-name slowquery/mysql-slowquery.log \
--output text > slowquery.log
The runbook
- Move to a custom parameter group if needed. Create it with
aws rds create-db-parameter-group, copy any existing customizations, attach it viamodify-db-instance, and reboot in a maintenance window. Do this once; it pays off for every future tuning change. - Set the parameters. MySQL:
slow_query_log = 1,long_query_time = 1,log_output = FILE. Postgres:log_min_duration_statement = 1000. Apply withaws rds modify-db-parameter-group --db-parameter-group-name prod-pg16 --parameters "ParameterName=log_min_duration_statement,ParameterValue=1000,ApplyMethod=immediate". Dynamic parameters take effect within moments; the console showsin-syncwhen done. - Export to CloudWatch Logs. In RDS > Databases > your-instance > Modify > Log exports, tick the slow query log (MySQL) or postgresql log (Postgres). This is the difference between grepping downloaded files and running a query over weeks of history. CloudWatch Logs ingestion and storage are billed per GB, which is another reason to log the tail, not everything.
- Read with tooling, not eyeballs. Aggregate before judging:
pt-query-digest slowquery.logfor MySQL groups identical statements and ranks by total time;pgbadgerdoes the same for Postgres logs. The query that matters is rarely the single slowest one — it is the 40 ms query executed two million times. - Search in CloudWatch Logs Insights for targeted questions. Example against an exported Postgres log:
fields @timestamp, @message
| filter @message like /duration:/
| parse @message "duration: * ms" as duration_ms
| sort duration_ms desc
| limit 25
- Act on the top offenders.
EXPLAINthem, index or rewrite, then watch the log volume shrink — the log itself is your before/after measurement.
When this is the wrong move
If the instance is on fire right now, do not start by enabling logs — Performance Insights already shows current top SQL with zero setup lag, and the slow log only captures statements that finish. A query storm of fast statements never appears in a slow log at any threshold; that diagnosis needs pg_stat_statements or performance_schema aggregates. And if you cannot afford the reboot to attach a custom parameter group this week, Performance Insights covers the gap until the maintenance window. The engine-specific parameter references live in the Amazon RDS documentation.
Common mistakes
- Setting
long_query_timeorlog_min_duration_statementto 0 in production “just to see everything” and creating an I/O problem on top of a latency problem. - Leaving
log_output = TABLEon MySQL:mysql.slow_loggrows inside your allocated storage and never reaches CloudWatch. - Enabling the log and never exporting it — RDS rotates local log files, and the evidence for last Tuesday’s incident is gone by Friday.
- Reading raw logs top-to-bottom instead of aggregating: the slowest single query is usually a weekly report, not your problem.
Prevention
- Keep slow query logging permanently on at a sane threshold (1s) on every production instance — the cost is small, the forensic value is large.
- Export to CloudWatch Logs with a retention policy (30-90 days) so cost stays bounded.
- Run a monthly digest (
pt-query-digest/pgbadger) and file tickets for the top three — this catches degradation while it is still cheap. - Pair the log with Performance Insights: PI for “now”, the slow log for “last month”.
- Re-check thresholds after major data growth; a threshold set at 10x typical latency stops catching anything once typical latency triples.
A slow query log that is on, exported, and actually read monthly turns database performance from a recurring emergency into a maintenance task. If nobody on your team truly owns the AWS environment end to end, that is the root of most of these problems — Stack Harbor runs AWS accounts daily under AWS Operations, and you can book an AWS review.