$npx -y skills add Impertio-Studio/Frappe_Claude_Skill_Package --skill frappe-ops-performanceUse when tuning MariaDB, configuring Redis memory, sizing Gunicorn workers, setting up CDN, or profiling slow queries. Prevents performance bottlenecks from default configurations, memory exhaustion, and unoptimized database queries. Covers MariaDB tuning, Redis configuration, Gu
| 1 | # Performance Tuning |
| 2 | |
| 3 | Frappe/ERPNext performance depends on four layers: database (MariaDB), cache (Redis), application server (Gunicorn), and background workers (RQ). ALWAYS tune all four layers together — optimizing one while ignoring others creates new bottlenecks. |
| 4 | |
| 5 | ## Quick Reference |
| 6 | |
| 7 | ```bash |
| 8 | # Check system health |
| 9 | bench doctor |
| 10 | |
| 11 | # Show pending background jobs |
| 12 | bench --site mysite.com show-pending-jobs |
| 13 | |
| 14 | # Clear all caches |
| 15 | bench --site mysite.com clear-cache |
| 16 | bench --site mysite.com clear-website-cache |
| 17 | |
| 18 | # Purge stuck background jobs |
| 19 | bench purge-jobs |
| 20 | |
| 21 | # Enable MariaDB slow query log |
| 22 | # In /etc/mysql/mariadb.conf.d/50-server.cnf: |
| 23 | # slow_query_log = 1 |
| 24 | # slow_query_log_file = /var/log/mysql/slow.log |
| 25 | # long_query_time = 1 |
| 26 | |
| 27 | # Check Gunicorn worker count |
| 28 | # In Procfile or supervisor config: -w [workers] |
| 29 | # Formula: workers = (2 * CPU_CORES) + 1 |
| 30 | ``` |
| 31 | |
| 32 | --- |
| 33 | |
| 34 | ## Performance Decision Tree |
| 35 | |
| 36 | ``` |
| 37 | What is slow? |
| 38 | | |
| 39 | +-- Page loads are slow? |
| 40 | | +-- Check Gunicorn workers (are they saturated?) |
| 41 | | +-- Check MariaDB slow query log |
| 42 | | +-- Check Redis memory (is cache evicting?) |
| 43 | | +-- Enable CDN for static assets |
| 44 | | |
| 45 | +-- Background jobs are delayed? |
| 46 | | +-- bench doctor (check worker count and pending jobs) |
| 47 | | +-- Increase RQ worker count |
| 48 | | +-- Check for long-running jobs blocking queues |
| 49 | | |
| 50 | +-- Database queries are slow? |
| 51 | | +-- Enable slow query log |
| 52 | | +-- Run EXPLAIN on slow queries |
| 53 | | +-- Add indexes on frequently filtered columns |
| 54 | | +-- Use get_cached_value instead of get_value |
| 55 | | |
| 56 | +-- Server runs out of memory? |
| 57 | | +-- Reduce Gunicorn workers |
| 58 | | +-- Set Redis maxmemory |
| 59 | | +-- Check MariaDB innodb_buffer_pool_size |
| 60 | | +-- Look for memory leaks in custom code |
| 61 | | |
| 62 | +-- High CPU usage? |
| 63 | | +-- Profile Python code (cProfile) |
| 64 | | +-- Check for N+1 query patterns |
| 65 | | +-- Review custom scheduled jobs |
| 66 | ``` |
| 67 | |
| 68 | --- |
| 69 | |
| 70 | ## MariaDB Tuning |
| 71 | |
| 72 | ### Critical Settings |
| 73 | |
| 74 | ```ini |
| 75 | # /etc/mysql/mariadb.conf.d/50-server.cnf |
| 76 | [mysqld] |
| 77 | # InnoDB buffer pool — MOST important setting |
| 78 | # Set to 50-70% of available RAM on dedicated DB server |
| 79 | # Set to 25-40% of RAM on shared server |
| 80 | innodb_buffer_pool_size = 2G |
| 81 | |
| 82 | # Buffer pool instances (1 per GB of buffer pool) |
| 83 | innodb_buffer_pool_instances = 2 |
| 84 | |
| 85 | # Log file size (larger = better write performance, slower recovery) |
| 86 | innodb_log_file_size = 256M |
| 87 | |
| 88 | # Flush method — use O_DIRECT to avoid double buffering |
| 89 | innodb_flush_method = O_DIRECT |
| 90 | |
| 91 | # Character set (ALWAYS use utf8mb4 for Frappe) |
| 92 | character-set-server = utf8mb4 |
| 93 | collation-server = utf8mb4_unicode_ci |
| 94 | |
| 95 | # Key buffer for MyISAM (Frappe uses InnoDB, keep small) |
| 96 | key_buffer_size = 32M |
| 97 | |
| 98 | # Query cache (DISABLE for MariaDB 10.4+ / MySQL 8.0+) |
| 99 | query_cache_type = 0 |
| 100 | query_cache_size = 0 |
| 101 | |
| 102 | # Connection limits |
| 103 | max_connections = 200 |
| 104 | wait_timeout = 600 |
| 105 | interactive_timeout = 600 |
| 106 | |
| 107 | # Temp tables |
| 108 | tmp_table_size = 64M |
| 109 | max_heap_table_size = 64M |
| 110 | |
| 111 | # Slow query log |
| 112 | slow_query_log = 1 |
| 113 | slow_query_log_file = /var/log/mysql/slow.log |
| 114 | long_query_time = 1 |
| 115 | ``` |
| 116 | |
| 117 | ### Slow Query Analysis |
| 118 | |
| 119 | ```bash |
| 120 | # Enable slow query log (runtime, no restart needed) |
| 121 | SET GLOBAL slow_query_log = 1; |
| 122 | SET GLOBAL long_query_time = 1; |
| 123 | |
| 124 | # Analyze slow queries with mysqldumpslow |
| 125 | mysqldumpslow -t 10 -s c /var/log/mysql/slow.log |
| 126 | # -t 10: top 10 queries |
| 127 | # -s c: sort by count (use -s t for total time) |
| 128 | |
| 129 | # Use EXPLAIN to analyze specific queries |
| 130 | EXPLAIN SELECT * FROM `tabSales Invoice` WHERE customer = 'ABC'; |
| 131 | # Look for: type=ALL (full table scan), rows > 10000, Using filesort |
| 132 | ``` |
| 133 | |
| 134 | ### Index Optimization |
| 135 | |
| 136 | ```sql |
| 137 | -- Check for missing indexes on frequently filtered columns |
| 138 | SHOW INDEX FROM `tabSales Invoice`; |
| 139 | |
| 140 | -- Add index for common filter patterns |
| 141 | ALTER TABLE `tabSales Invoice` ADD INDEX idx_customer_date (customer, posting_date); |
| 142 | |
| 143 | -- Frappe way: add index via DocType definition |
| 144 | -- In doctype JSON: set "in_list_view" or "search_index" on fields |
| 145 | -- OR use hooks.py: |
| 146 | -- after_migrate = ["myapp.patches.add_custom_indexes"] |
| 147 | ``` |
| 148 | |
| 149 | --- |
| 150 | |
| 151 | ## Redis Configuration |
| 152 | |
| 153 | ### Memory Management |
| 154 | |
| 155 | ```conf |
| 156 | # /etc/redis/redis.conf (or bench config/redis_cache.conf) |
| 157 | |
| 158 | # Set maximum memory — NEVER let Redis use all available RAM |
| 159 | maxmemory 512mb |
| 160 | |
| 161 | # Eviction policy — allkeys-lru is best for cache use |
| 162 | maxmemory-policy allkeys-lru |
| 163 | |
| 164 | # Disable persistence for cache Redis (performance boost) |
| 165 | save "" |
| 166 | appendonly no |
| 167 | ``` |
| 168 | |
| 169 | ### Fr |