Technology

The Postgres Alerts You Don't Know, You're Missing

May 1, 20265 min read
The Postgres Alerts You Don't Know, You're Missing

Most teams create an Amazon RDS database, connect their application, and assume everything is safe because AWS is managing it. But"managed" does not mean "fully monitored." RDS automatically sends basic metrics to CloudWatch — CPU usage, memory, storage, connections, and replica lag. These are useful, but they only show the basic health of the database infrastructure.

The real problem starts when critical database-level signals are missed. Replicas drift hours behind the primary. The Postgres process restarts in the middle of the night without anyone noticing. None of these conditions crash the database. None of them fill the logs. None of them are guaranteed to surface in the default CloudWatch metrics. And if alerts are not created for them, small issues can silently grow into real production failures while every dashboard on screen still looks green.

The same gap exists for teams running Postgres on their own EC2 or except there, even the basic metrics aren't published for free.

So, what does Amazon RDS monitor by default, which critical alerts are usually missing, and how can you close those gaps before they turn into real production failures?

The Five Alarms Every Production Postgres Should Have:

Every production Postgres setup needs a small set of alerts that catch failure modes that are easy to miss during normal operations. The obvious outages are straightforward to detect, but the more dangerous issues are often silent: the database remains reachable, logs stay clean, and the system appears healthy until the impact surfaces later as degraded performance, stale reads, or a full production incident.

So, these are the top 5 metrices which we have to use the setup

Alarm

Watches

Triggers when

Postgres Down

Database availability

Connections drop to 0

Too Many Connections

Connection count

> 80% of max_connections

Not Enough Connections

Connection count

Below normal baseline

Replication Lag

Replica delay

Lag > 30s sustained

Postgres Restarted

Restart events

Postmaster start time changes

Note- Flow of Replication :

  YOUR APP
     │
     │── WRITE ──▶ PRIMARY DB
     │                 │
     │                 │ Step 1: Write to WAL
     │                 │ Step 2: Update data file
     │                 │ Step 3: Confirm to app
     │                 │
     │                 │── WAL stream ──▶ READ REPLICA
     │                                        │
     │                                        │ Step 5: Receive WAL
     │                                        │ Step 6: Apply change
     │                                        │ Step 7: Now in sync
     │
     │── READ ───▶ READ REPLICA
                      (gets fresh data after ReplicaLag clears)

If you decide to set up replication on your RDS instance, meaning you create a read replica or use replication slots, AWS will automatically start sending some extra metrics (eg. ReplicaLag) to CloudWatch on top of the ones above. You do not need to configure anything extra for these either. Just enabling the replication feature is enough and AWS handles the rest.

How to Actually Wire These Alerts Up

There are three different approaches you can take here. You can use the native CloudWatch metrics AWS already publishes, write a Lambda function that pushes its own metrics, or run postgres_exporter to expose Postgres data on a "/metrics" endpoint that any scraper(Datadog, Cloudwatch, Prometheus) can read.

Approach 1: Native CloudWatch

If you're on RDS and you want the fastest possible setup, you're already most of the way there.

AWS automatically publishes a baseline of metrics for every RDS instance — CPU usage, memory, free storage space, connection count, replica lag, and a handful of others. These metrics start flowing into CloudWatch the moment your database is up. You don't install anything. You don't run any agents. You just open the CloudWatch console, or write a few lines of Terraform, and create alarms on the metrics that are already there.

Metric / Signal

Alerts to set

DatabaseConnections

Postgres Down when it drops to 0, Too Many Connections when it goes too high, Not Enough Connections when it falls below your

normal baseline

ReplicaLag

Replication Lag when lag stays above your threshold for too long

RDS event subscriptions

Postgres Restarted when an unexpected restart event is emitted

Approach 2: AWS Lambda + custom SQL

The Lambda has two ways to grab the data you need, and the same function can use both. The first way is Performance Insights — PI exposes a set of database-level counters that RDS collects in the background, and the Lambda calls pi.get_resource_metrics() to pull whatever it needs. The second way is custom SQL — the Lambda opens a connection to Postgres using psycopg2, runs the queries you've written against the pg_stat_* views, and pushes the results to CloudWatch with cloudwatch.put_metric_data(). EventBridge fires the function every five minutes, and from there you alarm on the custom metrics the same way you would on any AWS-published one.

The reason most RDS shops end up here is the control. There's no fixed metric list. The Lambda only publishes what you tell it to. Add a query, get a new metric. Delete a query, the metric stops flowing. And because there's no extra infrastructure — no EC2, no agent, no exposed port — the whole thing is a few Terraform resources and about a hundred lines of Python that lives in your repo like any other small service.

Approach 3: postgres_exporter

If your Postgres is not on RDS, or your team already uses Prometheus, Grafana, or Datadog, postgres_exporter is usually the standard choice.

It is a small open-source Go service that connects to Postgres with a monitoring user, runs a set of built-in queries, and exposes the results on a "/metrics" endpoint, usually on port 9187. A scraper then collects those metrics and sends them to whatever monitoring system you are using.

What makes this approach useful is that it fits the way Prometheus-style monitoring already works. The exporter does not push anything out. It just waits for something to scrape it. That means Prometheus, Datadog, or the CloudWatch agent can all read from the same exporter if you want them to.

On RDS, things look a little different. You can't install anything on the RDS instance itself, because RDS is fully managed by AWS, no shell access, no way to drop a binary or a container onto the database host. So if you want postgres_exporter on RDS, you have to run it somewhere else. The standard pattern is to launch a small EC2 instance, or an ECS container, in the same VPC as your RDS database, run the exporter there, and point it at the RDS endpoint. The exporter connects to RDS over the private network on port 5432, and your scraper connects to the exporter on port 9187.

Alarm

Built-in metric in postgres_exporter

Postgres Down

pg_up

Too Many Connections

not exposed by default (Custom query)

Not Enough Connections

not exposed by default (Custom query)

Replication Lag

pg_replication_lag

Postgres Restarted

pg_postmaster_start_time_seconds

About queries.yml: this is where your own SQL queries live, and the exporter runs them on the same schedule as the built in. If you'd rather skip the built in metrics entirely and write custom queries for every alarm yourself, including the ones already covered by defaults so, you can. The exporter is happy to run only what you give it.

Author

Akshat Gupta

Akshat Gupta

Software Engineer