• Skip to secondary menu
  • Skip to main content
  • Skip to primary sidebar
  • Home
  • Projects
  • Products
  • Themes
  • Tools
  • Request for Quote

Vengala Vinay

Having 9+ Years of Experience in Software Development

  • Home
  • WordPress
  • PHP
    • Codeigniter
  • Django
  • Magento
  • Selenium
  • Server
Home » Server Monitoring Best Practices: Keeping Your Python App and PostgreSQL Clusters Alive on AWS

Server Monitoring Best Practices: Keeping Your Python App and PostgreSQL Clusters Alive on AWS

Proactive PostgreSQL Monitoring with CloudWatch and Prometheus

Maintaining the health and performance of PostgreSQL clusters on AWS, especially within a high-traffic Python application environment, demands a multi-layered monitoring strategy. Relying solely on basic AWS health checks is insufficient. We need granular visibility into database-specific metrics, query performance, and resource utilization. This section details a robust approach using AWS CloudWatch for foundational metrics and Prometheus with exporters for deeper PostgreSQL insights.

CloudWatch Alarms for Core PostgreSQL Metrics

AWS RDS provides a set of essential metrics via CloudWatch. Setting up alarms on these metrics is the first line of defense against common issues like high CPU, low disk space, or network saturation. For PostgreSQL, specific metrics like DatabaseConnections, CPUUtilization, FreeStorageSpace, and ReadIOPS/WriteIOPS are critical.

Here’s a sample CloudWatch alarm configuration for high database connections, which can indicate connection leaks or insufficient connection pooling in your Python application:

aws cloudwatch put-metric-alarm \
    --alarm-name "RDS-PostgreSQL-HighConnections-Alarm" \
    --alarm-description "Alarm when PostgreSQL database connections exceed 80% of the maximum allowed." \
    --metric-name "DatabaseConnections" \
    --namespace "AWS/RDS" \
    --statistic Average \
    --period 300 \
    --threshold 80 \
    --comparison-operator GreaterThanThreshold \
    --dimensions "Name=DBInstanceIdentifier,Value=your-rds-instance-identifier" \
    --evaluation-periods 2 \
    --datapoints-to-alarm 2 \
    --treat-missing-data notBreaching \
    --alarm-actions arn:aws:sns:us-east-1:123456789012:your-sns-topic-for-alerts

Replace your-rds-instance-identifier and your-sns-topic-for-alerts with your specific AWS resource identifiers. Similar alarms should be configured for CPUUtilization (e.g., sustained > 85%), FreeStorageSpace (e.g., < 20GB), and IOPS metrics if you're hitting provisioned limits.

Deep Dive with Prometheus and `pg_exporter`

While CloudWatch provides a good overview, it lacks the depth needed for advanced PostgreSQL tuning. Prometheus, coupled with the pg_exporter, offers granular insights into query performance, replication status, cache hit ratios, and more. This setup is particularly valuable for identifying slow queries originating from your Python application.

Deploying `pg_exporter` on EC2

A common pattern is to run pg_exporter on a dedicated EC2 instance within the same VPC as your RDS instances. This instance will scrape metrics from your RDS cluster and expose them to your Prometheus server.

First, install pg_exporter. You can download the latest release from its GitHub repository or build it from source.

wget https://github.com/prometheus-community/postgres_exporter/releases/download/v0.12.0/postgres_exporter-v0.12.0.linux-amd64.tar.gz
tar xvfz postgres_exporter-v0.12.0.linux-amd64.tar.gz
sudo mv postgres_exporter-v0.12.0.linux-amd64/postgres_exporter /usr/local/bin/
rm -rf postgres_exporter-v0.12.0.linux-amd64.tar.gz postgres_exporter-v0.12.0.linux-amd64

Next, configure the exporter to connect to your RDS instance. Create a .pgpass file for passwordless authentication or use environment variables. For RDS, it’s often easier to create a dedicated read-only user with specific privileges.

# Example .pgpass file (ensure permissions are 0600)
hostname:port:database:username:password
your-rds-endpoint.region.rds.amazonaws.com:5432:postgres:exporter_user:your_secure_password

You’ll need to grant the exporter_user necessary permissions on your RDS instance. Connect to your PostgreSQL instance and run:

CREATE USER exporter_user WITH PASSWORD 'your_secure_password';
GRANT CONNECT ON DATABASE postgres TO exporter_user;
GRANT USAGE ON SCHEMA pg_catalog TO exporter_user;
GRANT SELECT ON pg_stat_activity TO exporter_user;
GRANT SELECT ON pg_stat_database TO exporter_user;
GRANT SELECT ON pg_stat_replication TO exporter_user;
GRANT SELECT ON pg_locks TO exporter_user;
GRANT SELECT ON pg_stat_statements TO exporter_user; -- If pg_stat_statements is enabled
-- Add other necessary views/tables as per pg_exporter documentation

Now, run the exporter. You can specify the database connection string via the DATA_SOURCE_NAME environment variable or a command-line flag. It’s best practice to run this as a systemd service.

# Create a systemd service file
sudo nano /etc/systemd/system/postgres_exporter.service

# Add the following content:
[Unit]
Description=PostgreSQL Exporter
Wants=network-online.target
After=network-online.target

[Service]
User=prometheus # Or a dedicated user
Group=prometheus
Type=simple
Environment="DATA_SOURCE_NAME=postgresql://exporter_user:[email protected]:5432/postgres?sslmode=require"
ExecStart=/usr/local/bin/postgres_exporter \
  --web.listen-address=":9187" \
  --extend.query-path="/etc/postgres_exporter/queries.yaml" \
  --log.level="info"

[Install]
WantedBy=multi-user.target

# Reload systemd, enable and start the service
sudo systemctl daemon-reload
sudo systemctl enable postgres_exporter
sudo systemctl start postgres_exporter
sudo systemctl status postgres_exporter

The --extend.query-path allows you to add custom PostgreSQL queries to be scraped. A common addition is enabling pg_stat_statements for query analysis.

# /etc/postgres_exporter/queries.yaml
# Example custom query for pg_stat_statements
pg_stat_statements:
  query: "SELECT * FROM pg_stat_statements"
  metrics:
    - stat_calls:
        usage: "COUNTER"
        description: "Number of times statement was executed."
    - total_exec_time:
        usage: "COUNTER"
        description: "Total time spent in statement, in seconds."
    - rows:
        usage: "COUNTER"
        description: "Number of rows returned or affected by statement."
    - shared_blks_hit:
        usage: "COUNTER"
        description: "Number of shared blocks hit in cache."
    - shared_blks_read:
        usage: "COUNTER"
        description: "Number of shared blocks read from disk."
    - queryid:
        usage: "LABEL"
        description: "Statement ID."
    - query:
        usage: "LABEL"
        description: "The text of the statement."

Ensure pg_stat_statements is enabled in your RDS parameter group. You can do this by setting pg_stat_statements.track = all and shared_preload_libraries = 'pg_stat_statements'. A database restart is required for these changes to take effect.

Configuring Prometheus to Scrape `pg_exporter`

On your Prometheus server, add a scrape configuration for the pg_exporter instance.

# prometheus.yml
scrape_configs:
  - job_name: 'postgres_exporter'
    static_configs:
      - targets: ['ec2-instance-ip:9187'] # Replace with your EC2 instance's private IP
    metrics_path: /metrics
    # Optional: Add relabeling if needed, e.g., to add instance labels
    relabel_configs:
      - source_labels: [__address__]
        target_label: instance
        regex: '([^:]+):.*'
        replacement: '$1'

After updating prometheus.yml, reload the Prometheus configuration.

Monitoring Python Application Performance with Prometheus

To correlate database performance with your Python application’s behavior, instrument your application with the prometheus_client library. This allows you to track request latency, error rates, and other application-specific metrics, which can then be joined with PostgreSQL metrics in Grafana.

Instrumenting a Flask Application

Here’s a basic example of how to instrument a Flask application to expose Prometheus metrics.

from flask import Flask, Response
from prometheus_client import generate_latest, Counter, Histogram, Gauge
import time
import random

app = Flask(__name__)

# Define metrics
REQUEST_COUNT = Counter('http_requests_total', 'Total HTTP Requests', ['method', 'endpoint', 'status_code'])
REQUEST_LATENCY = Histogram('http_request_duration_seconds', 'HTTP Request Latency', ['method', 'endpoint'])
ACTIVE_USERS = Gauge('app_active_users', 'Number of active users')

@app.route('/')
@REQUEST_LATENCY.labels(method='GET', endpoint='/').time()
def index():
    REQUEST_COUNT.labels(method='GET', endpoint='/', status_code=200).inc()
    # Simulate some work
    time.sleep(random.uniform(0.1, 0.5))
    return "Hello, World!"

@app.route('/users', methods=['GET'])
@REQUEST_LATENCY.labels(method='GET', endpoint='/users').time()
def get_users():
    REQUEST_COUNT.labels(method='GET', endpoint='/users', status_code=200).inc()
    # Simulate user activity
    ACTIVE_USERS.inc()
    time.sleep(random.uniform(0.2, 0.8))
    ACTIVE_USERS.dec()
    return "User data"

@app.route('/metrics')
def metrics():
    return Response(generate_latest(), mimetype='text/plain')

if __name__ == '__main__':
    # In a production environment, use a proper WSGI server like Gunicorn
    # and run the metrics endpoint on a separate port or path.
    app.run(host='0.0.0.0', port=5000)

To make this production-ready, you would typically run the Flask app behind a WSGI server like Gunicorn and expose the metrics endpoint on a different port (e.g., 9091) or path. Then, configure Prometheus to scrape this metrics endpoint.

# prometheus.yml (additional job)
  - job_name: 'python_app'
    static_configs:
      - targets: ['your-app-instance-ip:9091'] # Assuming metrics are on port 9091
    metrics_path: /metrics

Alerting on Key Metrics with Alertmanager

Prometheus’s alerting rules, managed by Alertmanager, provide sophisticated alerting capabilities. Define rules that trigger based on combinations of application and database metrics.

# prometheus-rules.yml
groups:
- name: postgresql.rules
  rules:
  - alert: HighPostgresConnections
    expr: avg_over_time(pg_stat_activity_count{datname="your_database_name"} [5m]) > 80
    for: 5m
    labels:
      severity: warning
    annotations:
      summary: "High number of PostgreSQL connections on {{ $labels.instance }}"
      description: "PostgreSQL instance {{ $labels.instance }} has {{ $value }} active connections, exceeding the threshold."

  - alert: SlowPostgresQueries
    expr: rate(pg_stat_statements_total_exec_time{datname="your_database_name"}[5m]) / rate(pg_stat_statements_calls{datname="your_database_name"}[5m]) > 0.1
    for: 10m
    labels:
      severity: critical
    annotations:
      summary: "Slow PostgreSQL queries detected on {{ $labels.instance }}"
      description: "Average execution time for queries on {{ $labels.instance }} is {{ $value }}s, indicating potential performance issues."

- name: python_app.rules
  rules:
  - alert: HighPythonAppErrorRate
    expr: sum(rate(http_requests_total{status_code=~"5..|4.."}[5m])) by (endpoint) / sum(rate(http_requests_total[5m])) by (endpoint) > 0.05
    for: 5m
    labels:
      severity: warning
    annotations:
      summary: "High error rate in Python application on {{ $labels.endpoint }}"
      description: "The endpoint {{ $labels.endpoint }} is experiencing an error rate of {{ $value | humanizePercentage }}."

Ensure your Prometheus configuration includes these rules and that Alertmanager is configured to route these alerts to your desired notification channels (Slack, PagerDuty, email).

Grafana for Visualization and Dashboards

Grafana is essential for visualizing the metrics collected by Prometheus and CloudWatch. Create dashboards that combine application and database metrics to provide a holistic view of your system’s health. Key dashboards include:

  • PostgreSQL Overview (Connections, Replication Lag, Cache Hit Ratio, IOPS)
  • PostgreSQL Query Performance (Top N Slow Queries, Query Execution Times)
  • Python Application Performance (Request Latency, Error Rates, Throughput)
  • Resource Utilization (CPU, Memory, Disk I/O for EC2 instances running exporters/apps)

When building dashboards, always aim to correlate application behavior with database performance. For instance, a spike in application latency should be immediately cross-referenced with PostgreSQL query times and connection counts.

Primary Sidebar

A little about the Author

Having 9+ Years of Experience in Software Development.
Expertised in Php Development, WordPress Custom Theme Development (From scratch using underscores or Genesis Framework or using any blank theme or Premium Theme), Custom Plugin Development. Hands on Experience on 3rd Party Php Extension like Chilkat, nSoftware.

Recent Posts

  • Step-by-Step: Diagnosing thread pools deadlock during concurrent ActiveRecord transaction processing on Linode Servers
  • Securing Your E-commerce APIs: Preventing SQL Injection (SQLi) in customized checkout queries in WooCommerce Implementations
  • Disaster Recovery 101: Architecting Auto-Failovers for MySQL and Ruby Deployments on Linode
  • High-Throughput Caching Strategies: Scaling MySQL for Perl Application APIs
  • Disaster Recovery 101: Architecting Auto-Failovers for DynamoDB and Laravel Deployments on DigitalOcean

Copyright © 2026 · Vinay Vengala