• 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 Google Cloud

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

Establishing a Robust Monitoring Foundation with Google Cloud Operations Suite

Maintaining the health and performance of a Python application cluster backed by PostgreSQL on Google Cloud Platform (GCP) demands a proactive, multi-layered monitoring strategy. Relying solely on basic uptime checks is insufficient for production environments. We need to delve into metrics, logs, and traces to preemptively identify and resolve issues before they impact end-users. Google Cloud’s Operations Suite (formerly Stackdriver) provides a powerful, integrated platform for this purpose. This guide focuses on configuring and leveraging its core components: Cloud Monitoring, Cloud Logging, and Cloud Trace.

Monitoring Python Application Performance with Cloud Monitoring

For Python applications, we’ll focus on key performance indicators (KPIs) such as request latency, error rates, and resource utilization (CPU, memory). Cloud Monitoring agents can be deployed to collect these metrics. For custom application-level metrics, the Cloud Monitoring client libraries are indispensable.

Custom Metrics for Python Applications

Let’s instrument a hypothetical Flask application to send custom metrics. We’ll track the number of successful and failed API requests.

Example Flask Application Snippet

from flask import Flask, request, jsonify
from google.cloud import monitoring_v3
import time
import os

app = Flask(__name__)

# Configure Google Cloud Monitoring client
project_id = os.environ.get("GOOGLE_CLOUD_PROJECT")
client = monitoring_v3.MetricServiceClient()
project_name = f"projects/{project_id}"

def write_metric(metric_type, value, labels=None):
    if labels is None:
        labels = {}
    
    series = monitoring_v3.Point()
    series.interval.end_time.seconds = int(time.time())
    series.interval.end_time.nanos = int(time.time() * 1e9) % 1e9
    series.value.double_value = float(value)
    
    metric = monitoring_v3.Metric()
    metric.type = metric_type
    for key, val in labels.items():
        metric.labels[key] = val
        
    try:
        client.create_time_series(
            request={
                "name": project_name,
                "time_series": [
                    {
                        "metric": metric,
                        "resource": {
                            "type": "gce_instance", # Or your specific resource type
                            "labels": {
                                "project_id": project_id,
                                "instance_id": os.environ.get("INSTANCE_ID", "unknown"), # Needs to be set
                                "zone": os.environ.get("INSTANCE_ZONE", "unknown") # Needs to be set
                            }
                        },
                        "points": [series],
                    }
                ],
            }
        )
        print(f"Successfully wrote metric: {metric_type} with value {value}")
    except Exception as e:
        print(f"Error writing metric {metric_type}: {e}")

@app.route('/api/data', methods=['GET'])
def get_data():
    try:
        # Simulate some work
        time.sleep(0.1) 
        
        # Simulate a potential error
        if request.args.get('fail') == 'true':
            raise ValueError("Simulated API failure")
            
        write_metric("custom.googleapis.com/myapp/api_requests_total", 1, {"status": "success"})
        return jsonify({"message": "Data retrieved successfully"})
        
    except Exception as e:
        write_metric("custom.googleapis.com/myapp/api_requests_total", 1, {"status": "error"})
        return jsonify({"error": str(e)}), 500

if __name__ == '__main__':
    # Ensure INSTANCE_ID and INSTANCE_ZONE are set in your environment
    # For GCE instances, these are often available automatically.
    # For GKE, you might need to extract them from pod metadata.
    if "INSTANCE_ID" not in os.environ or "INSTANCE_ZONE" not in os.environ:
        print("WARNING: INSTANCE_ID and INSTANCE_ZONE environment variables are not set. Metrics might not be correctly attributed.")
    app.run(debug=True, host='0.0.0.0', port=8080)

To make this work, ensure your application’s service account has the Monitoring Metric Writer role. Also, you’ll need to set the GOOGLE_CLOUD_PROJECT, INSTANCE_ID, and INSTANCE_ZONE environment variables. For Compute Engine instances, these are often pre-populated. For GKE, you’ll need to extract them from the pod’s metadata.

Setting Up Alerting Policies

Once custom metrics are flowing, we can define alerting policies in Cloud Monitoring. For instance, we can alert if the error rate for our /api/data endpoint exceeds a certain threshold.

# Example Alerting Policy Configuration (Conceptual - done via GCP Console or gcloud CLI)

# Alert on high error rate for /api/data endpoint
Policy Name: High API Error Rate - /api/data
Condition:
  - Metric: custom.googleapis.com/myapp/api_requests_total
  - Filter: status="error"
  - Aggregation:
    - Aligner: SUM
    - Reducer: SUM
    - Group By: [resource.instance_id]
  - Threshold:
    - Trigger: ABOVE
    - Value: 5 (errors per minute)
  - Duration: 5 minutes
Notification Channels: [Your PagerDuty, Slack, or Email channel]

Similarly, you can set up alerts for high CPU utilization, low memory, or excessive request latency.

Deep Dive into PostgreSQL Cluster Monitoring with Cloud Monitoring

Monitoring PostgreSQL clusters, especially in a distributed or highly available setup (e.g., using Patroni or Cloud SQL HA), requires a focus on database-specific metrics. Cloud Monitoring can ingest these metrics via the Ops Agent or custom exporters.

Leveraging the Ops Agent for PostgreSQL Metrics

The Ops Agent is the recommended way to collect system and application metrics. It can be configured to scrape PostgreSQL metrics using the built-in PostgreSQL receiver or by integrating with tools like pg_exporter.

# ops-agent.yaml (snippet for PostgreSQL monitoring)
metrics:
  receivers:
    postgresql:
      type: prometheus
      collection_interval: 60s
      endpoint: "http://localhost:9187/metrics" # Assuming pg_exporter is running on port 9187
      # Or if using built-in receiver (less common for detailed metrics)
      # endpoint: "unix:/var/run/postgresql/.s.PGSQL.5432" 
      
  service:
    pipelines:
      postgresql:
        receivers: [postgresql]

If you’re using pg_exporter (a Prometheus exporter for PostgreSQL), you’ll need to install and configure it separately. Ensure it’s accessible by the Ops Agent.

Key PostgreSQL Metrics to Monitor

  • pg_stat_activity metrics: Number of active connections, idle connections, query execution times.
  • Replication lag: For HA setups, monitor pg_stat_replication for write_lag and flush_lag.
  • Cache hit ratio: blks_hit vs blks_read from pg_stat_database.
  • Transaction rates: xact_commit and xact_rollback.
  • Lock contention: Monitor pg_locks for long-held or blocking locks.
  • Disk I/O: Use system metrics (via Ops Agent) for iostat, vmstat.
  • Replication slots: Monitor pg_replication_slots for active status and lag_bytes.

Create dashboards in Cloud Monitoring to visualize these metrics. For example, a dashboard showing replication lag across all replicas is crucial for high availability.

Alerting on PostgreSQL Cluster Health

Critical alerts for PostgreSQL include:

  • Replication lag exceeding a defined threshold (e.g., 1 minute).
  • High number of active connections nearing the configured max_connections limit.
  • Low cache hit ratio (e.g., below 95%).
  • Excessive long-running queries or lock waits.
  • Replication slot not active or lagging significantly.
# Example PostgreSQL Alerting Policy (Conceptual)

Policy Name: PostgreSQL Replication Lag Critical
Condition:
  - Metric: postgresql.googleapis.com/database/replication_lag_bytes # Example metric type
  - Filter: replica_name="your_replica_name" AND status="lagging"
  - Aggregation:
    - Aligner: MEAN
    - Reducer: MEAN
  - Threshold:
    - Trigger: ABOVE
    - Value: 1073741824 # 1 GB
  - Duration: 10 minutes
Notification Channels: [Critical DB Alert Channel]

Centralized Logging with Cloud Logging

Effective logging is paramount for debugging and auditing. Cloud Logging provides a centralized repository for logs from your Python applications and the underlying infrastructure.

Configuring Python Application Logging

Use Python’s standard logging module and configure it to send logs to Cloud Logging. The google-cloud-logging library simplifies this.

import logging
from google.cloud import logging as cloud_logging
import google.cloud.logging.handlers
import os

# Initialize Cloud Logging client
client = cloud_logging.Client()

# Get the default logger
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

# Create a Cloud Logging handler
handler = google.cloud.logging.handlers.CloudLoggingHandler(client, name="my-app-log")
logger.addHandler(handler)

# Add a standard stream handler for local debugging if needed
# stream_handler = logging.StreamHandler()
# logger.addHandler(stream_handler)

def process_request(request_id):
    try:
        logger.info(f"Processing request {request_id}", extra={"json_fields": {"request_id": request_id}})
        # Simulate work
        if request_id == "fail-me":
            raise ValueError("Simulated processing error")
        logger.info(f"Successfully processed request {request_id}")
    except Exception as e:
        logger.error(f"Error processing request {request_id}: {e}", exc_info=True, extra={"json_fields": {"request_id": request_id, "error_type": type(e).__name__}})

# Example usage
if __name__ == '__main__':
    # Ensure GOOGLE_CLOUD_PROJECT is set
    if not os.environ.get("GOOGLE_CLOUD_PROJECT"):
        print("WARNING: GOOGLE_CLOUD_PROJECT environment variable not set. Cloud Logging might not function correctly.")
        
    process_request("req-123")
    try:
        process_request("fail-me")
    except:
        pass # Error already logged

The extra={"json_fields": ...} argument allows you to add structured metadata to your log entries, making them searchable and filterable in Cloud Logging. Ensure the service account running your application has the Logs Writer role.

Log-based Metrics and Alerts

Cloud Logging allows you to create metrics based on log content. This is powerful for tracking events that might not be captured by standard application metrics.

# Example Log-based Metric Configuration (Conceptual - done via GCP Console or gcloud CLI)

Metric Name: Application Errors Count
Log Filter:
  textPayload=~"Error processing request" OR severity=ERROR
Metric Type: Counter
Units: 1
Description: Counts the number of error log entries from the application.

You can then create alerting policies based on these log-based metrics, similar to metric-based alerts.

Distributed Tracing with Cloud Trace

For complex, distributed systems, understanding request flow and identifying bottlenecks across services is crucial. Cloud Trace provides distributed tracing capabilities.

Instrumenting Python Applications for Tracing

Use the google-cloud-trace Python client library to instrument your application. This library automatically integrates with common frameworks like Flask and Django.

from flask import Flask, request
from google.cloud import trace_v2
from google.cloud.trace_v2.services import trace_service
from google.cloud.trace_v2.types import Span, TimeEvent, TraceSpan, SpanKind, Attributes, AttributeValue
import time
import os

app = Flask(__name__)

# Initialize Cloud Trace client
project_id = os.environ.get("GOOGLE_CLOUD_PROJECT")
client = trace_service.TraceServiceClient()
project_name = f"projects/{project_id}"

# Ensure trace agent is running or configured to send spans
# For GKE/GCE, the Ops Agent can often handle this.
# For local development, you might need to set GOOGLE_CLOUD_TRACE_ENABLED=true
# and potentially GOOGLE_CLOUD_TRACE_AGENT_ENDPOINT

def create_span(name, start_time, end_time, labels=None, parent_span_id=None):
    if labels is None:
        labels = {}
    
    span_id = str(int(time.time() * 1e6)) # Simple unique ID
    
    span = Span(
        span_id=span_id,
        display_name=name,
        start_time=start_time,
        end_time=end_time,
        kind=SpanKind.SPAN_KIND_UNSPECIFIED, # Or SERVER, CLIENT, etc.
        attributes=Attributes(
            attribute_map={
                key: AttributeValue(string_value=value) for key, value in labels.items()
            }
        )
    )
    
    if parent_span_id:
        span.parent_span_id = parent_span_id
        
    return span

@app.route('/api/trace-example', methods=['GET'])
def trace_example():
    request_start_time = time.time()
    
    # Start a root span for the request
    root_span_id = str(int(time.time() * 1e6))
    root_span_start = time.time()
    
    try:
        # Simulate external API call
        external_call_start = time.time()
        time.sleep(0.05) # Simulate latency
        external_call_end = time.time()
        external_span = create_span(
            name="ExternalService.GetData", 
            start_time=external_call_start, 
            end_time=external_call_end, 
            labels={"http.method": "GET", "http.url": "/external/data"},
            parent_span_id=root_span_id
        )
        
        # Simulate database query
        db_query_start = time.time()
        time.sleep(0.02) # Simulate latency
        db_query_end = time.time()
        db_span = create_span(
            name="PostgreSQL.Query", 
            start_time=db_query_start, 
            end_time=db_query_end, 
            labels={"db.statement": "SELECT * FROM users WHERE id = 1"},
            parent_span_id=root_span_id
        )
        
        request_end_time = time.time()
        root_span = create_span(
            name="GET /api/trace-example", 
            start_time=root_span_start, 
            end_time=request_end_time, 
            labels={"http.method": "GET", "http.url": "/api/trace-example"},
            parent_span_id=None # This is the root span
        )
        
        trace_id = f"{int(time.time() * 1e9):x}" # Generate a trace ID
        
        spans_to_write = [root_span, external_span, db_span]
        
        # Format spans for the API call
        formatted_spans = []
        for span in spans_to_write:
            formatted_spans.append(
                trace_v2.types.Span(
                    span_id=span.span_id,
                    display_name=span.display_name,
                    start_time=span.start_time,
                    end_time=span.end_time,
                    kind=span.kind,
                    attributes=span.attributes,
                    parent_span_id=span.parent_span_id
                )
            )

        request_body = {
            "project_id": project_id,
            "trace_id": trace_id,
            "spans": formatted_spans,
        }
        
        # Note: In a real scenario, you'd use the TraceServiceClient.batch_write method
        # This is a simplified representation for demonstration.
        print(f"Simulating trace write for trace_id: {trace_id}")
        # client.batch_write(name=project_name, spans=formatted_spans) # Actual API call
        
        return {"message": "Trace example executed"}
        
    except Exception as e:
        # Log error span if an exception occurs
        request_end_time = time.time()
        error_span = create_span(
            name="Error",
            start_time=request_end_time, # Span duration is minimal for error event
            end_time=request_end_time,
            labels={"error.message": str(e)},
            parent_span_id=root_span_id
        )
        # Add error span to the list and write trace
        spans_to_write.append(error_span)
        # ... (similar formatting and writing logic as above) ...
        raise e # Re-raise to ensure Flask returns 500

if __name__ == '__main__':
    if not project_id:
        print("WARNING: GOOGLE_CLOUD_PROJECT environment variable not set. Cloud Trace might not function correctly.")
    app.run(debug=True, host='0.0.0.0', port=8080)

For automatic instrumentation with frameworks like Flask, Django, or SQLAlchemy, the google-cloud-trace library often requires minimal configuration. Ensure your application’s service account has the Cloud Trace Agent role.

Integrating with Cloud SQL Proxy and HAProxy

When using Cloud SQL, the Cloud SQL Auth Proxy is essential for secure connections. Monitoring the proxy itself and the connections it manages is important. If you’re using HAProxy for load balancing your Python application instances, its logs and metrics should also be ingested.

Monitoring Cloud SQL Auth Proxy

The Cloud SQL Auth Proxy exposes metrics via an HTTP endpoint (defaulting to port 9300). You can configure the Ops Agent to scrape these metrics.

# ops-agent.yaml (snippet for Cloud SQL Proxy metrics)
metrics:
  receivers:
    cloudsql_proxy:
      type: prometheus
      collection_interval: 60s
      endpoint: "http://localhost:9300/metrics" # Adjust if proxy runs elsewhere or uses different port
      
  service:
    pipelines:
      cloudsql_proxy:
        receivers: [cloudsql_proxy]

Key metrics from the proxy include connection counts, latency, and errors.

Ingesting HAProxy Logs and Metrics

If HAProxy is used as a load balancer for your Python application instances (e.g., in front of GKE services or Compute Engine instances), configure it to log to standard output or a file that the Ops Agent can monitor. Similarly, HAProxy can expose Prometheus metrics.

# haproxy.cfg (snippet for Prometheus metrics)
stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
stats timeout 30s

# Prometheus exporter configuration
listen prometheus
    bind *:9101
    mode http
    stats enable
    stats uri /metrics
    stats refresh 10s
# ops-agent.yaml (snippet for HAProxy metrics)
metrics:
  receivers:
    haproxy:
      type: prometheus
      collection_interval: 30s
      endpoint: "http://localhost:9101/metrics" # Assuming HAProxy runs on the same host
      
  service:
    pipelines:
      haproxy:
        receivers: [haproxy]

Monitor HAProxy metrics like backend request rates, error rates, connection queues, and health check statuses. Alerts on backend health checks failing are critical.

Conclusion: A Unified Approach to Observability

By integrating Cloud Monitoring for metrics, Cloud Logging for logs, and Cloud Trace for distributed tracing, you establish a comprehensive observability stack for your Python applications and PostgreSQL clusters on Google Cloud. Proactive alerting based on these signals, coupled with structured logging and detailed tracing, empowers your DevOps team to maintain high availability, performance, and reliability. Regularly review your monitoring dashboards and alert configurations to adapt to evolving application behavior and infrastructure changes.

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