Disaster Recovery 101: Architecting Auto-Failovers for MongoDB and Shopify Deployments on Google Cloud
Automated MongoDB Failover with Google Cloud Load Balancing and Health Checks
Achieving true high availability for MongoDB deployments, especially those powering critical Shopify stores, necessitates an automated failover strategy. Relying on manual intervention during an outage is a recipe for extended downtime and lost revenue. This section details a robust, automated failover architecture leveraging Google Cloud’s native services.
Our strategy centers around a Google Cloud Load Balancer (GCLB) acting as the single entry point for all application traffic. The GCLB will continuously monitor the health of our MongoDB replica set members and automatically direct traffic away from unhealthy instances to healthy ones. This requires careful configuration of GCLB health checks and backend services.
Configuring GCLB Backend Services for MongoDB
We’ll define a backend service that points to our MongoDB replica set members. The key here is to use a health check that accurately reflects MongoDB’s operational status. For MongoDB, a simple TCP port check is often insufficient as a primary instance might be reachable but not accepting writes. A more sophisticated approach involves using a custom health check script or leveraging MongoDB’s built-in status commands.
For simplicity and effectiveness in many scenarios, we’ll start with a TCP health check on the MongoDB port (default 27017). This assumes that if the port is open and responsive, the instance is generally available. For more advanced scenarios, consider a custom health check that queries the replica set status.
Example: Creating a Backend Service and Health Check (gcloud CLI)
This command creates a global external HTTP(S) Load Balancer backend service. While we’re using MongoDB, we’ll configure it to use a TCP health check. The `protocol` for the backend service itself will be TCP, and the health check will also target the TCP port.
# Define your MongoDB instance IPs and port
MONGO_INSTANCES="10.128.0.10:27017,10.128.0.11:27017,10.128.0.12:27017"
MONGO_PORT="27017"
HEALTH_CHECK_NAME="mongodb-health-check"
BACKEND_SERVICE_NAME="mongodb-backend-service"
NETWORK_NAME="your-vpc-network" # e.g., default
# Create the health check
gcloud compute health-checks create tcp ${HEALTH_CHECK_NAME} \
--port=${MONGO_PORT} \
--description="TCP health check for MongoDB" \
--timeout=5s \
--check-interval=5s \
--unhealthy-threshold=3 \
--healthy-threshold=2
# Create the backend service
gcloud compute backend-services create ${BACKEND_SERVICE_NAME} \
--protocol=TCP \
--health-checks=${HEALTH_CHECK_NAME} \
--port-name=mongodb \
--description="Backend service for MongoDB replica set" \
--global
# Add backend instances to the backend service
# Assuming instances are already created and attached to the specified network
# You would typically add these as instance groups, but for direct IP configuration:
gcloud compute backend-services add-backend ${BACKEND_SERVICE_NAME} \
--global \
--instance-group=your-mongodb-instance-group-1 \
--instance-group-zone=us-central1-a # Adjust zone as needed
# Repeat for other instance groups or directly add network endpoint groups (NEGs)
# For direct IP configuration, you'd use Network Endpoint Groups (NEGs)
# Example for a zonal NEG:
# gcloud compute network-endpoint-groups create mongodb-neg-zone-a \
# --zone=us-central1-a \
# --network=${NETWORK_NAME} \
# --default-port=${MONGO_PORT} \
# --network-endpoint-type=GCE_VM_IP_PORT
#
# gcloud compute backend-services add-backend ${BACKEND_SERVICE_NAME} \
# --global \
# --network-endpoint-group=mongodb-neg-zone-a \
# --network-endpoint-group-zone=us-central1-a
# Create a URL map (simple for a single backend service)
URL_MAP_NAME="mongodb-url-map"
gcloud compute url-maps create ${URL_MAP_NAME} \
--default-service=${BACKEND_SERVICE_NAME}
# Create a target proxy
TARGET_PROXY_NAME="mongodb-tcp-proxy"
gcloud compute target-tcp-proxies create ${TARGET_PROXY_NAME} \
--backend-service=${BACKEND_SERVICE_NAME}
# Create a forwarding rule (assigning a static IP is recommended)
FORWARDING_RULE_NAME="mongodb-forwarding-rule"
STATIC_IP_NAME="mongodb-static-ip"
# Reserve a static IP address
gcloud compute addresses create ${STATIC_IP_NAME} --global
# Get the reserved IP address
STATIC_IP_ADDRESS=$(gcloud compute addresses describe ${STATIC_IP_NAME} --global --format='value(address)')
gcloud compute forwarding-rules create ${FORWARDING_RULE_NAME} \
--address=${STATIC_IP_ADDRESS} \
--global \
--target-tcp-proxy=${TARGET_PROXY_NAME} \
--ports=${MONGO_PORT}
This setup ensures that the GCLB will probe each MongoDB instance on port 27017. If an instance fails the health check (e.g., it becomes unresponsive or the MongoDB process crashes), GCLB will stop sending traffic to it. When the instance recovers and passes health checks again, GCLB will automatically re-include it in the pool of available backends.
Advanced Health Checks for MongoDB Replica Sets
For mission-critical applications, a simple TCP check might not be enough. A primary node that is unreachable or unable to accept writes will cause application failures even if the port is open. We can implement more intelligent health checks by using a custom script executed by GCLB’s health check mechanism or by leveraging a dedicated health check service.
One approach is to deploy a small, lightweight service on each MongoDB node that periodically queries the replica set status (e.g., using rs.status()) and exposes an HTTP endpoint (e.g., /health). This endpoint would return a 200 OK status if the node is healthy (e.g., it’s the primary and accepting writes, or it’s a secondary and in sync) and a non-200 status otherwise. GCLB can then be configured to use an HTTP health check against this endpoint.
Example: Python Health Check Service (Flask)
from flask import Flask, jsonify
from pymongo import MongoClient
from pymongo.errors import ConnectionFailure, OperationFailure
app = Flask(__name__)
# Configure your MongoDB connection string
# It's crucial that this service can connect to the replica set
MONGO_URI = "mongodb://localhost:27017/?replicaSet=yourReplicaSetName"
HEALTH_CHECK_PORT = 8080 # Port for the health check service
@app.route('/health', methods=['GET'])
def health_check():
try:
client = MongoClient(MONGO_URI, serverSelectionTimeoutMS=5000)
# The ismaster command is cheap and does not require auth.
client.admin.command('ismaster')
# More advanced check: ensure it's a primary or a healthy secondary
# This requires authentication if your replica set is secured.
# For simplicity, we'll rely on ismaster for now.
# For a full check, you might query rs.status() and analyze it.
return jsonify({"status": "healthy"}), 200
except ConnectionFailure:
return jsonify({"status": "unhealthy", "error": "MongoDB connection failed"}), 503
except OperationFailure as e:
# This could catch issues like authentication errors or other command failures
return jsonify({"status": "unhealthy", "error": f"MongoDB operation failed: {e.details}"}), 503
except Exception as e:
return jsonify({"status": "unhealthy", "error": f"An unexpected error occurred: {str(e)}"}), 500
if __name__ == '__main__':
# Run on all interfaces, accessible by GCLB health checker
app.run(host='0.0.0.0', port=HEALTH_CHECK_PORT)
To integrate this with GCLB:
- Deploy this Flask application on each MongoDB node. Ensure it runs on a port accessible by GCLB (e.g., 8080).
- Configure GCLB health checks to use HTTP on port 8080, path
/health. - Update the backend service to use an HTTP health check instead of TCP.
MongoDB Replica Set Configuration for Failover
The GCLB handles external traffic redirection. However, MongoDB’s internal failover mechanism is also critical. Ensure your replica set is configured with appropriate priorities and election timeouts.
// Example rs.conf() output snippet
{
"_id" : "yourReplicaSetName",
"version" : 1,
"protocolVersion" : 1,
"members" : [
{
"_id" : 0,
"host" : "mongo1.example.com:27017",
"priority" : 10, // Higher priority for preferred primary
"votes" : 1
},
{
"_id" : 1,
"host" : "mongo2.example.com:27017",
"priority" : 5,
"votes" : 1
},
{
"_id" : 2,
"host" : "mongo3.example.com:27017",
"priority" : 5,
"votes" : 1
}
// Add arbiters or hidden members as needed
],
"settings" : {
"electionTimeoutMillis" : 30000, // Default is 10 seconds, adjust cautiously
"heartbeatIntervalMillis" : 2000,
"catchUpTimeoutMillis" : 60000
}
}
Key Considerations:
- Priorities: Assign higher priorities to nodes that you prefer to be the primary.
- Votes: Ensure you have a majority of voting members to avoid split-brain scenarios. For a 3-node replica set, all 3 should vote.
electionTimeoutMillis: This value determines how long a node waits before initiating an election if it cannot reach the current primary. A shorter timeout leads to faster failover but can increase the risk of spurious elections if network partitions are transient. A longer timeout provides more stability but delays failover. The default is 10 seconds (10000ms).heartbeatIntervalMillis: The interval at which members send heartbeats. Shorter intervals detect failures faster but increase network traffic.
Automated Shopify Store Failover with GCLB and Cloud CDN
Shopify stores, while managed by Shopify, often rely on external integrations, custom themes, or backend services that might be hosted on Google Cloud. When these external components fail, it can manifest as a degraded or completely unavailable Shopify store experience for end-users. This section outlines how to architect for resilience of these *external* dependencies.
The primary goal is to ensure that if a critical backend service (e.g., a custom API, a product recommendation engine, or a payment gateway integration layer) becomes unavailable, the Shopify store can either gracefully degrade or serve cached content, minimizing user impact.
Leveraging Google Cloud Load Balancing for External Services
Similar to the MongoDB setup, we can use GCLB to front any custom backend services that your Shopify store interacts with. This provides a single, stable IP address for your store to connect to, and GCLB handles the failover between healthy instances of your backend service.
For Shopify, the critical aspect is often the *latency* and *availability* of these backend services. If a service is down, the Shopify page might hang while waiting for a response. GCLB’s health checks are paramount here.
Example: GCLB for a Shopify API Backend
# Assume you have a backend service (e.g., a Node.js API) running on GCE VMs
# or GKE pods.
API_HEALTH_CHECK_NAME="shopify-api-health-check"
API_BACKEND_SERVICE_NAME="shopify-api-backend-service"
API_FORWARDING_RULE_NAME="shopify-api-forwarding-rule"
API_STATIC_IP_NAME="shopify-api-static-ip"
API_PORT="8080" # Port your API backend listens on
# Create a health check (e.g., HTTP check on a /status endpoint)
gcloud compute health-checks create http ${API_HEALTH_CHECK_NAME} \
--port=${API_PORT} \
--request-path="/status" \
--check-interval=5s \
--timeout=5s \
--unhealthy-threshold=3 \
--healthy-threshold=2 \
--description="HTTP health check for Shopify API"
# Create backend service pointing to your API instances/NEGs
gcloud compute backend-services create ${API_BACKEND_SERVICE_NAME} \
--protocol=HTTP \
--health-checks=${API_HEALTH_CHECK_NAME} \
--port-name=http \
--description="Backend service for Shopify API" \
--global # Or --region=REGION for regional LB
# Add backends (e.g., instance groups or NEGs)
# gcloud compute backend-services add-backend ${API_BACKEND_SERVICE_NAME} \
# --global \
# --instance-group=your-api-instance-group \
# --instance-group-zone=us-central1-a
# Create URL map (if multiple backends, otherwise default to this one)
API_URL_MAP_NAME="shopify-api-url-map"
gcloud compute url-maps create ${API_URL_MAP_NAME} \
--default-service=${API_BACKEND_SERVICE_NAME}
# Create target proxy
API_TARGET_PROXY_NAME="shopify-api-http-proxy"
gcloud compute target-http-proxies create ${API_TARGET_PROXY_NAME} \
--url-map=${API_URL_MAP_NAME}
# Reserve static IP
gcloud compute addresses create ${API_STATIC_IP_NAME} --global
API_STATIC_IP_ADDRESS=$(gcloud compute addresses describe ${API_STATIC_IP_NAME} --global --format='value(address)')
# Create forwarding rule
gcloud compute forwarding-rules create ${API_FORWARDING_RULE_NAME} \
--address=${API_STATIC_IP_ADDRESS} \
--global \
--target-http-proxy=${API_TARGET_PROXY_NAME} \
--ports=80 # Or the port your Shopify store connects to
Your Shopify store’s integration code would then point to http://<API_STATIC_IP_ADDRESS> (or https:// if you set up SSL on the LB). If any instance of your API backend fails, GCLB will route traffic to healthy instances, ensuring minimal disruption.
Implementing Caching with Cloud CDN
For static assets or API responses that don’t change frequently, Google Cloud CDN can significantly improve performance and resilience. By caching content closer to your users, it reduces the load on your origin servers and provides a fallback if the origin is temporarily unavailable.
Cloud CDN integrates directly with Google Cloud Load Balancing. You enable it on your backend service.
Enabling Cloud CDN on a Backend Service
# Enable Cloud CDN for the API backend service
gcloud compute backend-services update ${API_BACKEND_SERVICE_NAME} \
--global \
--enable-cdn \
--cdn-policy-cache-mode=CACHE_ALL_STATIC \
--cdn-policy-client-ttl=3600 \
--cdn-policy-default-ttl=86400 \
--cdn-policy-max-ttl=31536000
Explanation of CDN Policies:
--enable-cdn: Turns on Cloud CDN for this backend service.--cdn-policy-cache-mode: Determines what gets cached.CACHE_ALL_STATICis a good starting point for assets. For API responses, you might needUSE_ORIGIN_HEADERSor custom cache keys.--cdn-policy-client-ttl: Time-to-live (TTL) for caches on client devices (browsers).--cdn-policy-default-ttl: Default TTL for caches if the origin doesn’t specify one.--cdn-policy-max-ttl: Maximum TTL allowed for any cached object.
By enabling Cloud CDN, if your API backend experiences an outage, Cloud CDN can continue to serve cached responses for a configured TTL, providing a degraded but functional experience to Shopify users. This is particularly useful for product listings, static content pages, or even cached API results.
Shopify Theme and App Considerations
It’s crucial to understand that Shopify’s core platform is managed by Shopify. This architecture focuses on the *external* components that your Shopify store relies on. For issues within Shopify’s platform itself, you would typically rely on Shopify’s own status pages and support.
However, custom themes and apps often make external API calls. Ensure that:
- API Endpoints are Robust: Any custom APIs your theme/apps call should be deployed with high availability and automated failover as described above.
- Error Handling: Implement robust error handling in your theme and app code. If an external API call fails, the page should not break entirely. It should display a user-friendly message (e.g., “Recommendations are currently unavailable”) rather than a blank page or a JavaScript error.
- CDN for Assets: Shopify itself uses a CDN for theme assets (images, CSS, JS). Ensure your theme is optimized to leverage this. For custom assets served from your own GCS buckets, use Cloud CDN.
Monitoring and Alerting for Proactive Failover
Automated failover is only effective if you are aware of failures and can react quickly. Comprehensive monitoring and alerting are non-negotiable.
Google Cloud Monitoring and Alerting
Google Cloud’s operations suite (formerly Stackdriver) provides robust tools for monitoring your infrastructure and applications.
- GCLB Health Check Status: Monitor the health status of your GCLB health checks. When a backend instance becomes unhealthy, GCLB logs this. You can create custom metrics and alerts based on these logs.
- Instance/Pod Metrics: Monitor CPU, memory, network I/O, and disk I/O for your MongoDB instances and backend API servers.
- Application Logs: Centralize logs from your MongoDB instances and backend applications. Use Cloud Logging to ingest and analyze these logs.
- Custom Metrics: For MongoDB, consider exporting metrics like replica set status, oplog lag, and query performance to Cloud Monitoring.
Example: Alerting on GCLB Unhealthy Backends
You can create a metric filter in Cloud Monitoring to detect when a backend becomes unhealthy.
# Metric: # compute.googleapis.com/loadbalancing.backend_service.backend_health # Filter example (adjust resource type and labels as needed): resource.type="backend_service" resource.labels.backend_service_name="mongodb-backend-service" metric.labels.health_state="UNHEALTHY"
Configure an alert policy in Cloud Monitoring based on this metric. Set the threshold to trigger an alert if any backend instance is unhealthy for more than, say, 2 minutes. Route these alerts to your on-call engineers via PagerDuty, Slack, or email.
MongoDB-Specific Monitoring
Beyond GCLB health checks, direct monitoring of the MongoDB replica set is vital.
- Replica Set Status: Regularly query
rs.status()and monitor the state of each member (PRIMARY, SECONDARY, ARBITER, STARTUP, etc.). - Oplog Lag: Monitor the lag between the primary’s oplog and secondaries. High oplog lag indicates that secondaries are not keeping up and might not be ready to take over quickly.
- Network Connectivity: Ensure members can communicate with each other.
- Disk Space: Running out of disk space is a common cause of database unavailability.
Tools like Percona Monitoring and Management (PMM), Prometheus with MongoDB exporters, or Datadog can be integrated to collect and visualize these metrics. Alerts should be configured for critical conditions like a missing primary, high oplog lag, or disk space warnings.
Conclusion: A Multi-Layered Approach
Architecting for automated failover for MongoDB and Shopify deployments on Google Cloud is a multi-layered endeavor. It involves leveraging Google Cloud’s robust networking and load balancing services for external traffic management, configuring databases for internal resilience, and implementing comprehensive monitoring and alerting. By combining GCLB health checks, intelligent application-level health checks, Cloud CDN for caching, and proactive monitoring, you can build highly available systems that minimize downtime and protect revenue.