|
| 1 | +# Gunicorn Timeout Configuration for CSV Imports |
| 2 | + |
| 3 | +## Overview |
| 4 | +The Attack-a-Crack CRM system has been configured with extended timeout settings to handle large CSV import operations without encountering timeout errors. |
| 5 | + |
| 6 | +## Configuration |
| 7 | + |
| 8 | +### Default Settings |
| 9 | +- **Timeout**: 300 seconds (5 minutes) |
| 10 | +- **Workers**: 4 processes |
| 11 | + |
| 12 | +These defaults are suitable for: |
| 13 | +- CSV files with up to 15,000 rows |
| 14 | +- PropertyRadar imports |
| 15 | +- Contact list uploads |
| 16 | +- Campaign contact imports |
| 17 | + |
| 18 | +### Environment Variables |
| 19 | +You can customize the timeout and worker settings using environment variables: |
| 20 | + |
| 21 | +```bash |
| 22 | +# Set custom timeout (in seconds) |
| 23 | +export GUNICORN_TIMEOUT=600 # 10 minutes |
| 24 | + |
| 25 | +# Set custom number of workers |
| 26 | +export GUNICORN_WORKERS=8 # 8 worker processes |
| 27 | +``` |
| 28 | + |
| 29 | +### Files Modified |
| 30 | + |
| 31 | +1. **entrypoint.sh** |
| 32 | + - Added configurable timeout and workers |
| 33 | + - Uses environment variables with sensible defaults |
| 34 | + ```bash |
| 35 | + GUNICORN_TIMEOUT=${GUNICORN_TIMEOUT:-300} |
| 36 | + GUNICORN_WORKERS=${GUNICORN_WORKERS:-4} |
| 37 | + exec gunicorn --timeout=${GUNICORN_TIMEOUT} --workers=${GUNICORN_WORKERS} ... |
| 38 | + ``` |
| 39 | + |
| 40 | +2. **.env.example** |
| 41 | + - Documents the new environment variables |
| 42 | + ``` |
| 43 | + GUNICORN_TIMEOUT=300 # Timeout in seconds (default: 300s for large CSV imports) |
| 44 | + GUNICORN_WORKERS=4 # Number of worker processes (default: 4) |
| 45 | + ``` |
| 46 | + |
| 47 | +3. **.do/app.yaml** |
| 48 | + - Production configuration includes timeout settings |
| 49 | + ```yaml |
| 50 | + - key: GUNICORN_TIMEOUT |
| 51 | + value: "300" |
| 52 | + - key: GUNICORN_WORKERS |
| 53 | + value: "4" |
| 54 | + ``` |
| 55 | +
|
| 56 | +4. **config.py** |
| 57 | + - Added configuration constants for programmatic access |
| 58 | + ```python |
| 59 | + GUNICORN_TIMEOUT = int(os.environ.get('GUNICORN_TIMEOUT', '300')) |
| 60 | + GUNICORN_WORKERS = int(os.environ.get('GUNICORN_WORKERS', '4')) |
| 61 | + ``` |
| 62 | + |
| 63 | +## Timeout Sizing Guide |
| 64 | + |
| 65 | +### CSV Import Performance |
| 66 | +Based on testing and production experience: |
| 67 | + |
| 68 | +| CSV Size | Processing Time | Recommended Timeout | |
| 69 | +|----------|----------------|--------------------| |
| 70 | +| 1,000 rows | ~10-20 seconds | 60 seconds | |
| 71 | +| 5,000 rows | ~50-100 seconds | 180 seconds | |
| 72 | +| 10,000 rows | ~100-200 seconds | 300 seconds (default) | |
| 73 | +| 25,000 rows | ~250-500 seconds | 600 seconds | |
| 74 | +| 50,000 rows | ~500-1000 seconds | 1200 seconds | |
| 75 | + |
| 76 | +### Factors Affecting Processing Time |
| 77 | +1. **API Rate Limits**: OpenPhone API calls may be rate-limited |
| 78 | +2. **Database Operations**: Bulk inserts and updates |
| 79 | +3. **Data Validation**: Phone number validation and normalization |
| 80 | +4. **Network Latency**: API response times |
| 81 | +5. **Concurrent Operations**: Other system load |
| 82 | + |
| 83 | +## Troubleshooting |
| 84 | + |
| 85 | +### Timeout Errors During Import |
| 86 | +If you see "Worker timeout" errors: |
| 87 | + |
| 88 | +1. **Increase the timeout**: |
| 89 | + ```bash |
| 90 | + export GUNICORN_TIMEOUT=600 # 10 minutes |
| 91 | + docker-compose restart web |
| 92 | + ``` |
| 93 | + |
| 94 | +2. **For production (DigitalOcean)**: |
| 95 | + - Update the app.yaml configuration |
| 96 | + - Redeploy the application |
| 97 | + |
| 98 | +3. **Monitor the import**: |
| 99 | + ```bash |
| 100 | + docker-compose logs -f web |
| 101 | + ``` |
| 102 | + |
| 103 | +### Memory Issues |
| 104 | +For very large imports (>50,000 rows): |
| 105 | + |
| 106 | +1. **Consider batch processing**: |
| 107 | + - Split the CSV into smaller files |
| 108 | + - Process in chunks of 10,000 rows |
| 109 | + |
| 110 | +2. **Increase worker memory**: |
| 111 | + - Upgrade instance size in DigitalOcean |
| 112 | + - Or reduce number of workers to give each more memory |
| 113 | + |
| 114 | +### Performance Optimization |
| 115 | + |
| 116 | +1. **Use background jobs** for large imports: |
| 117 | + ```python |
| 118 | + # Instead of synchronous processing |
| 119 | + from tasks import import_csv_task |
| 120 | + import_csv_task.delay(file_path) |
| 121 | + ``` |
| 122 | + |
| 123 | +2. **Enable progress tracking**: |
| 124 | + - Use WebSocket or SSE for real-time updates |
| 125 | + - Store progress in Redis |
| 126 | + |
| 127 | +3. **Optimize database operations**: |
| 128 | + - Use bulk_insert_mappings() for large datasets |
| 129 | + - Disable autoflush during import |
| 130 | + |
| 131 | +## Testing |
| 132 | + |
| 133 | +### Unit Tests |
| 134 | +```bash |
| 135 | +docker-compose exec web pytest tests/unit/test_gunicorn_config.py -v |
| 136 | +``` |
| 137 | + |
| 138 | +### Integration Tests |
| 139 | +```bash |
| 140 | +docker-compose exec web pytest tests/integration/test_csv_import_timeout.py -v |
| 141 | +``` |
| 142 | + |
| 143 | +### Manual Testing |
| 144 | +1. Set a custom timeout: |
| 145 | + ```bash |
| 146 | + export GUNICORN_TIMEOUT=30 # Short timeout for testing |
| 147 | + ``` |
| 148 | + |
| 149 | +2. Try importing a large CSV |
| 150 | + |
| 151 | +3. Verify timeout is applied: |
| 152 | + ```bash |
| 153 | + docker-compose exec web ps aux | grep gunicorn |
| 154 | + # Should show: gunicorn ... --timeout=30 ... |
| 155 | + ``` |
| 156 | + |
| 157 | +## Best Practices |
| 158 | + |
| 159 | +1. **Set appropriate timeouts**: |
| 160 | + - Don't set unnecessarily high timeouts (security risk) |
| 161 | + - Consider the largest expected import size |
| 162 | + - Add 50% buffer to expected processing time |
| 163 | + |
| 164 | +2. **Monitor timeout usage**: |
| 165 | + - Log when requests take >50% of timeout |
| 166 | + - Alert on frequent timeout errors |
| 167 | + |
| 168 | +3. **Use async processing**: |
| 169 | + - For imports >10,000 rows, use Celery tasks |
| 170 | + - Provide progress feedback to users |
| 171 | + |
| 172 | +4. **Implement request chunking**: |
| 173 | + - Break large operations into smaller requests |
| 174 | + - Use pagination for large result sets |
| 175 | + |
| 176 | +## Related Configuration |
| 177 | + |
| 178 | +### Nginx (if used as reverse proxy) |
| 179 | +If using Nginx in front of Gunicorn: |
| 180 | +```nginx |
| 181 | +proxy_read_timeout 300s; |
| 182 | +proxy_connect_timeout 75s; |
| 183 | +``` |
| 184 | + |
| 185 | +### Docker Health Checks |
| 186 | +The Docker health check timeout is separate: |
| 187 | +```yaml |
| 188 | +healthcheck: |
| 189 | + timeout: 10s # This is just for health checks, not requests |
| 190 | +``` |
| 191 | +
|
| 192 | +### Database Connection Pool |
| 193 | +Ensure database can handle long transactions: |
| 194 | +```python |
| 195 | +SQLALCHEMY_ENGINE_OPTIONS = { |
| 196 | + 'pool_pre_ping': True, |
| 197 | + 'pool_recycle': 3600, |
| 198 | + 'connect_args': { |
| 199 | + 'connect_timeout': 10, |
| 200 | + 'options': '-c statement_timeout=300000' # 5 minutes in ms |
| 201 | + } |
| 202 | +} |
| 203 | +``` |
| 204 | + |
| 205 | +## Security Considerations |
| 206 | + |
| 207 | +1. **DoS Prevention**: |
| 208 | + - Long timeouts can enable DoS attacks |
| 209 | + - Implement rate limiting for import endpoints |
| 210 | + - Require authentication for import operations |
| 211 | + |
| 212 | +2. **Resource Limits**: |
| 213 | + - Set maximum file size limits |
| 214 | + - Limit concurrent import operations |
| 215 | + - Monitor CPU and memory usage |
| 216 | + |
| 217 | +3. **Audit Logging**: |
| 218 | + - Log all import operations |
| 219 | + - Track processing times |
| 220 | + - Alert on abnormal patterns |
| 221 | + |
| 222 | +--- |
| 223 | + |
| 224 | +*Last Updated: August 26, 2025* |
| 225 | +*Version: 1.0* |
0 commit comments