The Ultimate DevOps Playbook: Tuning Nginx, Gunicorn/FPM, and MySQL on OVH for C++
Nginx Configuration for High-Traffic C++ Applications
Optimizing Nginx is crucial for serving C++ applications, especially when they are fronted by WSGI/FastCGI servers like Gunicorn or PHP-FPM. The primary goals are efficient request handling, robust connection management, and effective caching.
Worker Processes and Connections
The worker_processes directive determines how many worker processes Nginx will spawn. A common best practice is to set this to the number of CPU cores available on your server. For OVH instances, this can be determined dynamically or set statically. The worker_connections directive sets the maximum number of simultaneous connections that each worker process can handle. The total number of connections is limited by the system’s file descriptor limit.
On an OVH server, you can check CPU cores with:
grep -c processor /proc/cpuinfo
A typical Nginx configuration snippet for performance:
user www-data;
worker_processes auto; # Or set to the number of CPU cores
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 1024; # Adjust based on system limits and expected load
multi_accept on;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
server_tokens off; # Important for security
# ... other http configurations ...
}
Gzip Compression and Caching
Enabling Gzip compression significantly reduces bandwidth usage and improves load times. Browser caching can be leveraged by setting appropriate Cache-Control headers.
http {
# ... other http configurations ...
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
# Caching for static assets
location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public";
}
# ... server blocks ...
}
Proxying to Gunicorn/PHP-FPM
When proxying to Gunicorn (for Python/C++ extensions) or PHP-FPM, configure timeouts and buffer sizes appropriately. For C++ applications served via Gunicorn, ensure the WSGI server is tuned as well.
server {
listen 80;
server_name your_domain.com;
location / {
proxy_pass http://unix:/path/to/your/app.sock; # Or http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
}
# For PHP-FPM
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock; # Adjust PHP version
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_read_timeout 300; # Increase timeout for potentially long-running PHP scripts
}
}
Gunicorn Tuning for C++ Extensions
Gunicorn is a popular WSGI HTTP Server for Python. When your C++ code is integrated via Python extensions (e.g., using Cython or pybind11), Gunicorn’s performance directly impacts your application’s responsiveness. Tuning involves adjusting worker types, worker counts, and timeouts.
Worker Types and Count
Gunicorn offers several worker types. For I/O-bound applications or those with significant C++ extensions that might block, the gevent or event (async) workers are often preferred over the default sync workers. The number of workers is typically set to (2 * number_of_cores) + 1, but this can be adjusted based on memory usage and the nature of your C++ extensions.
Starting Gunicorn with optimized settings:
gunicorn --workers 3 \
--worker-class gevent \
--bind unix:/path/to/your/app.sock \
--timeout 120 \
--graceful-timeout 120 \
your_module:your_app
Explanation of parameters:
--workers: Number of worker processes.--worker-class: The type of worker.geventis good for I/O concurrency.--bind: The address to bind to. A Unix socket is generally faster than TCP/IP for local communication.--timeout: Maximum time in seconds that a worker can spend on a request. Crucial for long-running C++ computations.--graceful-timeout: Timeout for graceful worker restarts.
Asynchronous Workers (Gevent/Event)
If your C++ extensions perform blocking I/O or long computations, using gevent or event workers can prevent a single slow request from blocking other requests. This requires your C++ code (or the Python wrappers) to be compatible with asynchronous I/O patterns if possible, though even for CPU-bound tasks, better concurrency management can be achieved.
Ensure you have gevent installed:
pip install gevent
PHP-FPM Tuning for C++ Integration
If your C++ code is exposed via PHP extensions (e.g., using SWIG or custom PHP extensions), tuning PHP-FPM is essential. The focus is on process management, memory limits, and execution timeouts.
Process Management
PHP-FPM uses a pool of worker processes. The pm (process manager) setting determines how these processes are managed. dynamic is often a good balance, but ondemand can save resources if traffic is sporadic. For consistently high traffic, static might offer the best performance by keeping processes ready.
Locate your PHP-FPM pool configuration file (e.g., /etc/php/7.4/fpm/pool.d/www.conf) and adjust:
; Choose one of: static, dynamic, ondemand pm = dynamic ; If pm = dynamic: pm.max_children = 35 pm.start_servers = 2 pm.min_spare_servers = 1 pm.max_spare_servers = 3 ; If pm = static: ; pm.max_children = 50 ; If pm = ondemand: ; pm.max_children = 5 ; pm.process_idle_timeout = 10s ; Maximum amount of memory a script may consume (e.g. 256MB) ; Crucial if your C++ extensions are memory-intensive. memory_limit = 512M ; Maximum execution time of each script, in seconds ; Set this high enough for your C++ computations. max_execution_time = 300 ; Maximum input variables (used for POST data, etc.) max_input_vars = 2000
Tuning `max_execution_time` and `memory_limit`
The max_execution_time directive in php.ini (and mirrored in FPM pool config) is critical. If your C++ computations take longer than the default 30 seconds, you’ll hit timeouts. Similarly, memory_limit must be sufficient for your C++ code’s memory footprint.
You can also set these per-request using ini_set() in your PHP code, but it’s better to configure them globally for consistency.
<?php
// Example of setting ini values in PHP (less preferred for global settings)
ini_set('max_execution_time', 600); // 10 minutes
ini_set('memory_limit', '1024M');
// Your C++ extension calls here...
?>
MySQL Tuning for High Load
Database performance is often a bottleneck. Tuning MySQL involves optimizing the buffer pool, query cache (though often disabled in modern versions), connection handling, and query execution.
InnoDB Buffer Pool
The innodb_buffer_pool_size is arguably the most important setting for InnoDB. It caches data and indexes. A common recommendation is to set it to 50-70% of your available RAM on a dedicated database server. On a shared OVH instance, you’ll need to be more conservative.
Edit your MySQL configuration file (e.g., /etc/mysql/mysql.conf.d/mysqld.cnf or my.cnf):
[mysqld] # General settings user = mysql pid-file = /var/run/mysqld/mysqld.pid socket = /var/run/mysqld/mysqld.sock datadir = /var/lib/mysql log-error = /var/log/mysql/error.log # General log disabled for performance, enable for debugging # general_log_file = /var/log/mysql/mysql.log # general_log = 1 # InnoDB settings innodb_buffer_pool_size = 2G # Adjust based on available RAM (e.g., 2GB for a 4GB RAM server) innodb_log_file_size = 512M # Larger log files can improve write performance innodb_flush_log_at_trx_commit = 1 # Default is 1, 0 or 2 can be faster but riskier innodb_flush_method = O_DIRECT # Often beneficial on Linux # Connection settings max_connections = 200 # Adjust based on application needs and server capacity thread_cache_size = 16 # Cache threads for reuse # Query Cache (often disabled in MySQL 8+) # query_cache_type = 0 # query_cache_size = 0 # Other performance tweaks tmp_table_size = 64M max_heap_table_size = 64M sort_buffer_size = 4M join_buffer_size = 4M read_rnd_buffer_size = 2M read_buffer_size = 2M
Connection Handling and Threading
max_connections should be set to accommodate your application’s peak load. thread_cache_size helps reduce the overhead of creating new threads for each connection.
Query Optimization and Slow Query Log
Regularly analyze your slow query log to identify and optimize inefficient queries. Ensure your C++ application (or its database access layer) uses proper indexing.
[mysqld] # ... other settings ... slow_query_log = 1 slow_query_log_file = /var/log/mysql/mysql-slow.log long_query_time = 2 # Log queries taking longer than 2 seconds log_queries_not_using_indexes = 1 # Log queries that don't use indexes
After making changes to my.cnf or related files, restart MySQL:
sudo systemctl restart mysql
Monitoring and Diagnostics
Effective monitoring is key to identifying performance issues before they impact users. Use tools like htop, iotop, netstat, Nginx’s status module, Gunicorn’s metrics, and MySQL’s performance schema.
System-Level Monitoring
htop provides a real-time view of CPU and memory usage per process. iotop shows disk I/O activity.
# Install if not present sudo apt update && sudo apt install htop iotop -y # Run them htop sudo iotop
Nginx Status Module
Enable Nginx’s stub_status module to get basic connection and request statistics.
# In your nginx.conf or a site-specific conf
http {
# ...
server {
listen 80;
server_name status.your_domain.com;
location /nginx_status {
stub_status;
allow 127.0.0.1; # Restrict access
deny all;
}
}
# ...
}
Then reload Nginx:
sudo systemctl reload nginx
Accessing http://status.your_domain.com/nginx_status will show:
Active connections: 123 server accepts handled requests 1604686 1604686 1356789 Reading: 6 Writing: 117 Timed out: 0 (0 requests/sec)
Gunicorn and PHP-FPM Monitoring
Gunicorn can be configured to log worker PIDs and request times. PHP-FPM provides status pages or can be monitored via pm.status_path in its pool configuration.
MySQL Performance Schema
The Performance Schema provides detailed instrumentation for MySQL. It can be used to diagnose bottlenecks at a very granular level.
-- Enable Performance Schema (if not already enabled in my.cnf) SET GLOBAL performance_schema = ON; -- Example: View top wait events SELECT * FROM performance_schema.events_waits_summary_global_by_event_name ORDER BY SUM_TIMER_WAIT DESC LIMIT 10; -- Example: View top queries by execution time SELECT * FROM performance_schema.events_statements_summary_by_digest ORDER BY SUM_TIMER_WAIT DESC LIMIT 10;
By systematically tuning Nginx, your application server (Gunicorn/PHP-FPM), and the database (MySQL), you can build a highly performant and scalable infrastructure on OVH for your C++-powered applications.