|
| 1 | +# 🚀 SolidQueue Adaptive Polling |
| 2 | + |
| 3 | +**Adaptive Polling** is a feature that automatically optimizes SolidQueue's memory and CPU consumption by dynamically adjusting worker polling intervals based on current workload. |
| 4 | + |
| 5 | +> **💡 Important**: This is a SolidQueue gem feature. Configuration should be done in the **Rails application that consumes the gem**, not in the gem itself. |
| 6 | +
|
| 7 | +## 📊 Benefits |
| 8 | + |
| 9 | +- **20-40% less CPU** when system is idle |
| 10 | +- **20-50% less memory** by reducing unnecessary queries |
| 11 | +- **Faster response** when there's work to process |
| 12 | +- **Better utilization** of database resources |
| 13 | +- **Intelligent behavior** that adapts automatically |
| 14 | + |
| 15 | +## 🔧 How It Works |
| 16 | + |
| 17 | +The system continuously monitors: |
| 18 | +- How many jobs are found in each poll |
| 19 | +- Query execution time |
| 20 | +- Load patterns over time |
| 21 | + |
| 22 | +Based on these metrics, it: |
| 23 | +- **Accelerates** polling when it detects work (down to configured minimum) |
| 24 | +- **Decelerates** polling when there's no work (up to configured maximum) |
| 25 | +- **Converges** gradually to base interval when load is stable |
| 26 | + |
| 27 | +## ⚙️ Configuration |
| 28 | + |
| 29 | +### Basic Setup |
| 30 | + |
| 31 | +**In your Rails application** that uses the SolidQueue gem, add to `config/application.rb` or `config/environments/production.rb`: |
| 32 | + |
| 33 | +```ruby |
| 34 | +Rails.application.configure do |
| 35 | + # Enable adaptive polling |
| 36 | + config.solid_queue.adaptive_polling_enabled = true |
| 37 | +end |
| 38 | +``` |
| 39 | + |
| 40 | +### Advanced Configuration |
| 41 | + |
| 42 | +**In your Rails application**, create a file `config/initializers/solid_queue_adaptive_polling.rb`: |
| 43 | + |
| 44 | +```ruby |
| 45 | +# config/initializers/solid_queue_adaptive_polling.rb |
| 46 | +Rails.application.configure do |
| 47 | + config.solid_queue.adaptive_polling_enabled = true |
| 48 | + |
| 49 | + # Minimum interval when system is busy (default: 0.05s) |
| 50 | + config.solid_queue.adaptive_polling_min_interval = 0.03 |
| 51 | + |
| 52 | + # Maximum interval when system is idle (default: 5.0s) |
| 53 | + config.solid_queue.adaptive_polling_max_interval = 8.0 |
| 54 | + |
| 55 | + # Growth factor when idle (default: 1.5) |
| 56 | + config.solid_queue.adaptive_polling_backoff_factor = 1.6 |
| 57 | + |
| 58 | + # Acceleration factor when busy (default: 0.7) |
| 59 | + config.solid_queue.adaptive_polling_speedup_factor = 0.6 |
| 60 | + |
| 61 | + # Analysis window size (default: 10) |
| 62 | + config.solid_queue.adaptive_polling_window_size = 15 |
| 63 | +end |
| 64 | +``` |
| 65 | + |
| 66 | +## 🌟 Recommended Configurations |
| 67 | + |
| 68 | +### Production (Aggressive) |
| 69 | +```ruby |
| 70 | +# Maximum efficiency for high-load environments |
| 71 | +config.solid_queue.adaptive_polling_min_interval = 0.03 # 30ms minimum |
| 72 | +config.solid_queue.adaptive_polling_max_interval = 10.0 # 10s maximum |
| 73 | +config.solid_queue.adaptive_polling_backoff_factor = 1.8 # Fast backoff |
| 74 | +config.solid_queue.adaptive_polling_speedup_factor = 0.5 # Fast acceleration |
| 75 | +config.solid_queue.adaptive_polling_window_size = 20 # Precise analysis |
| 76 | +``` |
| 77 | + |
| 78 | +### Staging (Balanced) |
| 79 | +```ruby |
| 80 | +# Balanced configuration - use defaults |
| 81 | +config.solid_queue.adaptive_polling_enabled = true |
| 82 | +# Other settings use default values |
| 83 | +``` |
| 84 | + |
| 85 | +### Development (Conservative) |
| 86 | +```ruby |
| 87 | +# More predictable behavior for development |
| 88 | +config.solid_queue.adaptive_polling_min_interval = 0.1 # 100ms minimum |
| 89 | +config.solid_queue.adaptive_polling_max_interval = 2.0 # 2s maximum |
| 90 | +config.solid_queue.adaptive_polling_backoff_factor = 1.2 # Gentle |
| 91 | +config.solid_queue.adaptive_polling_speedup_factor = 0.8 # Gentle |
| 92 | +``` |
| 93 | + |
| 94 | +## 📈 Monitoring |
| 95 | + |
| 96 | +The system automatically logs information about its operation: |
| 97 | + |
| 98 | +### Startup Logs |
| 99 | +``` |
| 100 | +SolidQueue Adaptive Polling ENABLED with configuration: |
| 101 | + - Min interval: 0.05s |
| 102 | + - Max interval: 5.0s |
| 103 | + - Backoff factor: 1.5 |
| 104 | + - Speedup factor: 0.7 |
| 105 | + - Window size: 10 |
| 106 | +``` |
| 107 | + |
| 108 | +### Operation Logs (Debug) |
| 109 | +``` |
| 110 | +Worker 12345 adaptive polling stats: polls=1000 avg_jobs_per_poll=2.3 empty_poll_rate=45.2% current_interval=0.125s |
| 111 | +Adaptive polling: interval adjusted to 0.087s (empty: 0, busy: 15) |
| 112 | +``` |
| 113 | + |
| 114 | +### Worker Statistics |
| 115 | +``` |
| 116 | +Worker 12345 Adaptive Polling stats: uptime=3600s polls=5420 jobs=8765 efficiency=1.617 jobs/poll avg_interval=0.324s |
| 117 | +``` |
| 118 | + |
| 119 | +## 🔬 How to Test |
| 120 | + |
| 121 | +### 1. Test Environment |
| 122 | +```ruby |
| 123 | +# In your Rails application, in config/environments/development.rb |
| 124 | +Rails.application.configure do |
| 125 | + config.logger.level = :info |
| 126 | + config.solid_queue.adaptive_polling_enabled = true |
| 127 | +end |
| 128 | +``` |
| 129 | + |
| 130 | +### 2. Simulate Load |
| 131 | +```ruby |
| 132 | +# In your Rails application console (rails console) |
| 133 | +100.times { MyJob.perform_later } |
| 134 | + |
| 135 | +# Wait for processing and observe solid_queue logs |
| 136 | +# Interval should decrease when there's work |
| 137 | +``` |
| 138 | + |
| 139 | +### 3. Simulate Idle |
| 140 | +```ruby |
| 141 | +# Stop creating jobs |
| 142 | +# Observe interval gradually increasing in logs |
| 143 | +``` |
| 144 | + |
| 145 | +## 🐛 Troubleshooting |
| 146 | + |
| 147 | +### Issue: Polling too slow |
| 148 | +```ruby |
| 149 | +# Reduce maximum interval |
| 150 | +config.solid_queue.adaptive_polling_max_interval = 2.0 |
| 151 | + |
| 152 | +# Reduce backoff factor |
| 153 | +config.solid_queue.adaptive_polling_backoff_factor = 1.2 |
| 154 | +``` |
| 155 | + |
| 156 | +### Issue: Polling too fast |
| 157 | +```ruby |
| 158 | +# Increase minimum interval |
| 159 | +config.solid_queue.adaptive_polling_min_interval = 0.1 |
| 160 | + |
| 161 | +# Increase speedup factor (closer to 1.0) |
| 162 | +config.solid_queue.adaptive_polling_speedup_factor = 0.8 |
| 163 | +``` |
| 164 | + |
| 165 | +### Issue: Slow adaptation |
| 166 | +```ruby |
| 167 | +# Reduce analysis window for faster reaction |
| 168 | +config.solid_queue.adaptive_polling_window_size = 5 |
| 169 | + |
| 170 | +# Adjust factors for more aggressive changes |
| 171 | +config.solid_queue.adaptive_polling_backoff_factor = 1.8 |
| 172 | +config.solid_queue.adaptive_polling_speedup_factor = 0.5 |
| 173 | +``` |
| 174 | + |
| 175 | +## 🔧 Advanced Per-Worker Configuration |
| 176 | + |
| 177 | +For different configurations per worker, use YAML configuration: |
| 178 | + |
| 179 | +```yaml |
| 180 | +# config/queue.yml |
| 181 | +production: |
| 182 | + workers: |
| 183 | + - queues: "critical" |
| 184 | + threads: 5 |
| 185 | + adaptive_polling: |
| 186 | + min_interval: 0.01 |
| 187 | + max_interval: 1.0 |
| 188 | + - queues: "background" |
| 189 | + threads: 3 |
| 190 | + adaptive_polling: |
| 191 | + min_interval: 0.1 |
| 192 | + max_interval: 10.0 |
| 193 | +``` |
| 194 | +
|
| 195 | +## 📚 Detailed Algorithm |
| 196 | +
|
| 197 | +### System States |
| 198 | +- **Busy**: > 60% of polls found work OR average > 2 jobs/poll |
| 199 | +- **Idle**: >= 5 consecutive polls without work |
| 200 | +- **Stable**: Between busy and idle |
| 201 | +
|
| 202 | +### Adaptation Logic |
| 203 | +```ruby |
| 204 | +if busy? |
| 205 | + new_interval = current_interval * speedup_factor |
| 206 | + # Accelerate more if very busy (10+ consecutive polls) |
| 207 | + new_interval *= 0.8 if consecutive_busy_polls >= 10 |
| 208 | + |
| 209 | +elsif idle? |
| 210 | + backoff_multiplier = [1 + (consecutive_empty_polls * 0.1), 3.0].min |
| 211 | + new_interval = current_interval * backoff_factor * backoff_multiplier |
| 212 | + |
| 213 | +else |
| 214 | + # Gradually converge to base interval |
| 215 | + new_interval = current_interval.lerp(base_interval, 0.05) |
| 216 | +end |
| 217 | + |
| 218 | +# Always respect min/max limits |
| 219 | +new_interval.clamp(min_interval, max_interval) |
| 220 | +``` |
| 221 | + |
| 222 | +## 🚨 Considerations |
| 223 | + |
| 224 | +- **Tests**: Always disabled in test environment for predictability |
| 225 | +- **Database**: Reduces database load, but may cause latency on sudden spikes |
| 226 | +- **Memory**: Significant improvement, especially in systems with idle periods |
| 227 | +- **CPU**: Reduction proportional to system idle time |
| 228 | + |
| 229 | +## 📦 Installation and Setup |
| 230 | + |
| 231 | +1. **Ensure your application is using SolidQueue with the version that includes Adaptive Polling** |
| 232 | + |
| 233 | +2. **Create an initializer in your application**: |
| 234 | + ```bash |
| 235 | + # In your Rails application |
| 236 | + touch config/initializers/solid_queue_adaptive_polling.rb |
| 237 | + ``` |
| 238 | + |
| 239 | +3. **Configure based on the example file**: |
| 240 | + - Check `examples_adaptive_polling_config.rb` in the gem to see all options |
| 241 | + - Copy relevant configurations to your initializer |
| 242 | + |
| 243 | +4. **Restart your application** to apply the configurations |
| 244 | + |
| 245 | +5. **Monitor the logs** to verify it's working: |
| 246 | + ``` |
| 247 | + SolidQueue Adaptive Polling ENABLED with configuration: |
| 248 | + - Min interval: 0.05s |
| 249 | + - Max interval: 5.0s |
| 250 | + ``` |
| 251 | + |
| 252 | +--- |
| 253 | + |
| 254 | +*For complete example configurations, see the `examples_adaptive_polling_config.rb` file included in the gem.* |
0 commit comments