• 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 Linode

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

Establishing a Robust Monitoring Baseline for Python Applications

Effective server monitoring begins with understanding the health and performance of your core application. For Python applications, this means going beyond basic CPU and memory checks to inspect the application’s internal state, request latency, and error rates. We’ll focus on a practical approach using Prometheus and its Node Exporter for system metrics, coupled with a Python-specific exporter.

System Metrics with Node Exporter

Prometheus’s Node Exporter is the de facto standard for collecting hardware and OS metrics. On your Linode instances running your Python app, ensure Node Exporter is installed and running. A common setup involves running it as a systemd service.

Installation and Service Configuration (Ubuntu/Debian)

Download the latest release from the Prometheus GitHub repository. For example:

wget https://github.com/prometheus/node_exporter/releases/download/v1.7.0/node_exporter-1.7.0.linux-amd64.tar.gz
tar xvfz node_exporter-1.7.0.linux-amd64.tar.gz
sudo mv node_exporter-1.7.0.linux-amd64/node_exporter /usr/local/bin/

Create a systemd service file for Node Exporter:

[Unit]
Description=Node Exporter
Wants=network-online.target
After=network-online.target

[Service]
User=nobody
Group=nobody
Type=simple
ExecStart=/usr/local/bin/node_exporter

[Install]
WantedBy=multi-user.target

Then, enable and start the service:

sudo systemctl daemon-reload
sudo systemctl enable node_exporter
sudo systemctl start node_exporter
sudo systemctl status node_exporter

Verify that Node Exporter is accessible by navigating to http://your_linode_ip:9100/metrics in your browser. This endpoint will expose a wealth of system metrics.

Application-Specific Metrics with Prometheus Client Libraries

To gain visibility into your Python application’s performance, integrate the Prometheus Python client library. This allows you to expose custom metrics like request counts, response times, and error rates directly from your application.

Installation

pip install prometheus_client

Example Integration (Flask Application)

Here’s a basic example of how to instrument a Flask application. We’ll create a `/metrics` endpoint that serves Prometheus-formatted metrics.

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

app = Flask(__name__)

# Define custom 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 currently active users')

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

@app.route('/data')
def get_data():
    start_time = time.time()
    # Simulate fetching data
    time.sleep(random.uniform(0.5, 1.5))
    duration = time.time() - start_time
    REQUEST_COUNT.labels(method='GET', endpoint='/data', status_code=200).inc()
    REQUEST_LATENCY.labels(method='GET', endpoint='/data').observe(duration)
    return {"data": "some_data"}

@app.route('/error')
def trigger_error():
    # Simulate an error
    time.sleep(0.2)
    REQUEST_COUNT.labels(method='GET', endpoint='/error', status_code=500).inc()
    return "Internal Server Error", 500

@app.route('/metrics')
def metrics():
    # Simulate active users (e.g., based on session count)
    ACTIVE_USERS.set(random.randint(10, 100))
    return Response(generate_latest(), mimetype='text/plain')

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

In this example:

  • REQUEST_COUNT tracks the number of requests, categorized by HTTP method, endpoint, and status code.
  • REQUEST_LATENCY measures the duration of requests to specific endpoints.
  • ACTIVE_USERS is a gauge representing a dynamic value, like the number of concurrent users.
  • The /metrics endpoint exposes these metrics in Prometheus format.

Ensure your Python application is configured to expose this /metrics endpoint and that it’s accessible by your Prometheus server. You’ll need to configure Prometheus to scrape this endpoint.

Monitoring PostgreSQL Clusters with Prometheus

PostgreSQL, being a critical data store, requires dedicated monitoring. The postgres_exporter is an excellent tool for exposing PostgreSQL metrics in a Prometheus-compatible format. For high availability, you’ll likely be running a PostgreSQL cluster, which adds complexity to monitoring.

Setting up Postgres Exporter

Download and install the postgres_exporter. Similar to Node Exporter, it’s often run as a systemd service.

Installation (Example)

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

Database Connection and Configuration

The exporter needs credentials to connect to your PostgreSQL instances. It’s best practice to create a dedicated monitoring user in PostgreSQL with minimal privileges. The exporter can read connection strings from an environment variable or a file.

-- Connect to your PostgreSQL instance
-- Example using psql
psql -U postgres -h your_pg_host

-- Create a monitoring user
CREATE USER monitor WITH PASSWORD 'your_secure_password';

-- Grant read-only access to essential system catalogs and statistics views
GRANT SELECT ON pg_stat_activity TO monitor;
GRANT SELECT ON pg_stat_replication TO monitor;
GRANT SELECT ON pg_stat_database TO monitor;
GRANT SELECT ON pg_stat_statements TO monitor; -- If pg_stat_statements is enabled
GRANT SELECT ON pg_settings TO monitor;
GRANT SELECT ON pg_locks TO monitor;
GRANT SELECT ON pg_stat_user_tables TO monitor;
GRANT SELECT ON pg_stat_user_indexes TO monitor;
-- Add other necessary grants based on your monitoring needs and exporter configuration

Create a .pgpass file for the user running the exporter (e.g., nobody) to avoid embedding passwords directly in service files or command lines.

# ~/.pgpass
your_pg_host:5432:*:monitor:your_secure_password

Set appropriate permissions for the .pgpass file:

chmod 600 ~/.pgpass

Systemd Service for Postgres Exporter

Create a systemd service file. You’ll need to specify the connection string for each PostgreSQL instance you want to monitor. For a cluster, you’ll typically run an exporter instance for each node, or configure it to connect to a load balancer/VIP if applicable.

[Unit]
Description=PostgreSQL Exporter
Wants=network-online.target
After=network-online.target

[Service]
User=nobody
Group=nobody
Type=simple
# Example for a single PostgreSQL instance
# Replace 'your_pg_host' and 'your_pg_database'
# The exporter will use ~/.pgpass for authentication if not specified here
ExecStart=/usr/local/bin/postgres_exporter --web.listen-address=":9187" --extend.queries="file:queries.yaml" --pg.host="your_pg_host" --pg.port="5432" --pg.database="your_pg_database" --pg.user="monitor"

# For multiple instances, you might run multiple services or use a configuration file
# Example for a primary and replica, assuming different hosts/ports
# ExecStart=/usr/local/bin/postgres_exporter --web.listen-address=":9187" --pg.host="primary_host" --pg.port="5432" --pg.database="postgres" --pg.user="monitor"
# ExecStart=/usr/local/bin/postgres_exporter --web.listen-address=":9188" --pg.host="replica_host" --pg.port="5432" --pg.database="postgres" --pg.user="monitor"

# If using a connection string directly (less secure, avoid in production if possible)
# ExecStart=/usr/local/bin/postgres_exporter --web.listen-address=":9187" --pg.dsn="postgresql://monitor:your_secure_password@your_pg_host:5432/postgres?sslmode=disable"

[Install]
WantedBy=multi-user.target

Enable and start the service:

sudo systemctl daemon-reload
sudo systemctl enable postgres_exporter
sudo systemctl start postgres_exporter
sudo systemctl status postgres_exporter

Verify the exporter is running by accessing http://your_linode_ip:9187/metrics. You’ll need to configure Prometheus to scrape this endpoint for each PostgreSQL instance.

Monitoring PostgreSQL Clusters: Key Metrics and Considerations

When monitoring PostgreSQL clusters, focus on metrics that indicate performance, availability, and potential issues:

  • Replication Lag: Crucial for HA. Look for pg_replication_lag_seconds (or similar, depending on exporter version and configuration). High lag means replicas are falling behind the primary, increasing risk during failover.
  • Connection Usage: pg_stat_activity_count and pg_connection_pool_max_connections. Monitor for excessive connections that could exhaust resources.
  • Query Performance: pg_stat_statements_by_query_total_time_seconds, pg_stat_statements_by_query_calls. Identify slow or frequently executed queries. Ensure pg_stat_statements is enabled in postgresql.conf.
  • Disk I/O and Space: While Node Exporter covers disk I/O, monitor PostgreSQL-specific tablespace usage and free space.
  • Locking: pg_locks_count. Excessive locks can halt application progress.
  • WAL (Write-Ahead Log): Monitor WAL generation rate and archive status.
  • Cache Hit Ratio: pg_stat_database_blks_hit vs. pg_stat_database_blks_read. A low hit ratio indicates insufficient memory allocated to PostgreSQL buffers.

Example Custom Queries (queries.yaml)

The postgres_exporter allows custom queries via a queries.yaml file. This is powerful for tailoring monitoring to your specific needs.

# Example queries.yaml
metrics:
  - name: pg_replication_lag_seconds
    query: |
      SELECT
        COALESCE(pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) / 1024.0 / 1024.0, 0) AS lag_mb
      FROM pg_stat_replication
      WHERE application_name = 'your_app_name_for_replication'; # Adjust if needed
    type: GAUGE
    labels:
      - application_name

  - name: pg_total_connections
    query: SELECT count(*) FROM pg_stat_activity;
    type: GAUGE

  - name: pg_deadlocks_total
    query: SELECT deadlocks FROM pg_stat_database WHERE datname = current_database();
    type: COUNTER

Remember to configure the --extend.queries flag in your postgres_exporter service to point to this file.

Prometheus Configuration and Alerting

With your exporters in place, the next step is to configure Prometheus to scrape them and set up alerting rules to proactively identify issues.

Prometheus Scrape Configuration

Your prometheus.yml file needs to include scrape configurations for your Node Exporters and Postgres Exporters.

global:
  scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
  evaluation_interval: 15s # Evaluate rules every 15 seconds. Default is every 1 minute.

scrape_configs:
  # Scrape Prometheus itself
  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090']

  # Scrape Node Exporter for Python App Servers
  - job_name: 'python_app_nodes'
    static_configs:
      - targets:
          - 'app_server_1_ip:9100'
          - 'app_server_2_ip:9100'
          # Add all your application server IPs

  # Scrape Postgres Exporter for PostgreSQL Cluster
  - job_name: 'postgres_cluster'
    static_configs:
      - targets:
          - 'pg_node_1_ip:9187' # Assuming exporter on port 9187 for each PG node
          - 'pg_node_2_ip:9187'
          - 'pg_node_3_ip:9187'
          # Add all your PostgreSQL node IPs running the exporter

  # Scrape Python Application Metrics
  - job_name: 'python_app_metrics'
    static_configs:
      - targets:
          - 'app_server_1_ip:5000' # Assuming your Flask app runs on port 5000 and exposes /metrics
          - 'app_server_2_ip:5000'

After updating prometheus.yml, reload Prometheus configuration:

curl -X POST http://localhost:9090/-/reload

Alerting Rules

Define alerting rules in a separate file (e.g., alerts.yml) and configure Prometheus to load them.

groups:
- name: python_app_alerts
  rules:
  - alert: HighRequestLatency
    expr: avg by (job, instance) (rate(http_request_duration_seconds_bucket{le="5"}[5m])) > 0.95
    for: 5m
    labels:
      severity: warning
    annotations:
      summary: "High request latency on {{ $labels.instance }}"
      description: "More than 95% of requests on {{ $labels.instance }} are taking longer than 5 seconds."

  - alert: HighErrorRate
    expr: sum by (job, instance) (rate(http_requests_total{status_code=~"5.."}[5m])) / sum by (job, instance) (rate(http_requests_total[5m])) > 0.05
    for: 5m
    labels:
      severity: critical
    annotations:
      summary: "High HTTP error rate on {{ $labels.instance }}"
      description: "More than 5% of requests on {{ $labels.instance }} are returning 5xx errors."

  - alert: AppServerDown
    expr: up{job="python_app_nodes"} == 0
    for: 1m
    labels:
      severity: critical
    annotations:
      summary: "Application server {{ $labels.instance }} is down"
      description: "The application server {{ $labels.instance }} has been unreachable for 1 minute."

- name: postgres_cluster_alerts
  rules:
  - alert: ReplicationLagging
    expr: pg_replication_lag_seconds > 600 # Lagging by more than 10 minutes
    for: 5m
    labels:
      severity: critical
    annotations:
      summary: "PostgreSQL replication lag on {{ $labels.instance }}"
      description: "PostgreSQL replication lag on {{ $labels.instance }} has exceeded 10 minutes."

  - alert: HighPostgresConnections
    expr: pg_total_connections > 200 # Example threshold, tune based on your setup
    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: PostgresServerDown
    expr: up{job="postgres_cluster"} == 0
    for: 1m
    labels:
      severity: critical
    annotations:
      summary: "PostgreSQL node {{ $labels.instance }} is down"
      description: "The PostgreSQL node {{ $labels.instance }} has been unreachable for 1 minute."

Ensure your prometheus.yml includes the path to your alert rules file:

rule_files:
  - "alerts.yml"
  # - "other_rules/*.yml"

Reload Prometheus again after adding the alert rules file.

Advanced Considerations and Best Practices

Beyond the basics, several advanced strategies can enhance your monitoring posture.

Centralized Logging

Metrics are crucial, but logs provide context. Implement a centralized logging solution (e.g., ELK stack, Loki, Splunk) to aggregate logs from all your application servers and PostgreSQL instances. This allows for easier debugging and correlation between metrics and events.

Health Checks and Synthetic Monitoring

Proactively test your application’s availability and functionality. Implement HTTP health check endpoints in your Python app (e.g., /healthz) that check database connectivity and other critical dependencies. Use tools like Prometheus Blackbox Exporter or external services to periodically probe these endpoints and critical application flows.

Resource Limits and Autoscaling

On Linode, leverage their autoscaling capabilities or implement your own based on Prometheus metrics. For example, scale up your Python application instances when CPU utilization or request queue length exceeds certain thresholds. For PostgreSQL, consider read replicas for scaling read-heavy workloads.

Security of Monitoring Endpoints

Ensure your monitoring endpoints (/metrics, exporter ports) are secured. If they are exposed externally, consider using firewall rules, VPNs, or authentication mechanisms. For internal networks, ensure only your Prometheus server can access these ports.

Distributed Tracing

For complex microservice architectures or deep performance analysis, integrate distributed tracing (e.g., Jaeger, Zipkin). This allows you to follow a request across multiple services, pinpointing latency bottlenecks with high precision. Libraries like OpenTelemetry can help instrument your Python applications.

By implementing these monitoring strategies, you can build a resilient and observable system, ensuring your Python applications and PostgreSQL clusters remain healthy and performant on Linode.

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

  • Disaster Recovery 101: Architecting Auto-Failovers for Redis and PHP Deployments on OVH
  • How We Audited a High-Traffic WooCommerce Enterprise Stack on Google Cloud and Mitigated Race conditions during high-concurrency payment processing
  • Disaster Recovery 101: Architecting Auto-Failovers for Elasticsearch and Magento 2 Deployments on DigitalOcean
  • An Auditor’s Checklist for Securing WordPress Backends on OVH
  • Step-by-Step: Diagnosing Perl script high CPU throttling due to unoptimized regular expressions on AWS Servers

Copyright © 2026 · Vinay Vengala