mirror of
https://github.com/readur/readur.git
synced 2026-01-12 17:49:50 -06:00
512 lines
11 KiB
Markdown
512 lines
11 KiB
Markdown
# Performance Tuning Guide
|
|
|
|
## Overview
|
|
|
|
This guide provides comprehensive performance optimization strategies for Readur deployments, from small personal instances to large enterprise installations.
|
|
|
|
## Performance Baseline
|
|
|
|
### System Metrics to Monitor
|
|
|
|
| Metric | Target | Warning | Critical |
|
|
|--------|--------|---------|----------|
|
|
| CPU Usage | <60% | 60-80% | >80% |
|
|
| Memory Usage | <70% | 70-85% | >85% |
|
|
| Disk I/O Wait | <10% | 10-25% | >25% |
|
|
| Database Connections | <60% | 60-80% | >80% |
|
|
| Response Time (p95) | <500ms | 500-1000ms | >1000ms |
|
|
| OCR Queue Length | <100 | 100-500 | >500 |
|
|
|
|
## Database Optimization
|
|
|
|
### PostgreSQL Tuning
|
|
|
|
#### Connection Pool Settings
|
|
|
|
```bash
|
|
# postgresql.conf
|
|
max_connections = 200
|
|
shared_buffers = 256MB # 25% of available RAM
|
|
effective_cache_size = 1GB # 50-75% of available RAM
|
|
work_mem = 4MB
|
|
maintenance_work_mem = 64MB
|
|
|
|
# Write performance
|
|
checkpoint_segments = 32
|
|
checkpoint_completion_target = 0.9
|
|
wal_buffers = 16MB
|
|
|
|
# Query optimization
|
|
random_page_cost = 1.1 # For SSD storage
|
|
effective_io_concurrency = 200 # For SSD
|
|
default_statistics_target = 100
|
|
```
|
|
|
|
#### Application Connection Pooling
|
|
|
|
```bash
|
|
# Readur configuration
|
|
DATABASE_POOL_SIZE=20
|
|
DATABASE_MAX_OVERFLOW=10
|
|
DATABASE_POOL_TIMEOUT=30
|
|
DATABASE_POOL_RECYCLE=3600
|
|
DATABASE_STATEMENT_TIMEOUT=30000 # 30 seconds
|
|
```
|
|
|
|
### Query Optimization
|
|
|
|
#### Index Creation
|
|
|
|
```sql
|
|
-- Essential indexes for performance
|
|
CREATE INDEX CONCURRENTLY idx_documents_user_created
|
|
ON documents(user_id, created_at DESC);
|
|
|
|
CREATE INDEX CONCURRENTLY idx_documents_ocr_status
|
|
ON documents(ocr_status)
|
|
WHERE ocr_status IN ('pending', 'processing');
|
|
|
|
CREATE INDEX CONCURRENTLY idx_documents_search
|
|
ON documents USING gin(to_tsvector('english', content));
|
|
|
|
-- Partial indexes for common queries
|
|
CREATE INDEX CONCURRENTLY idx_recent_documents
|
|
ON documents(created_at DESC)
|
|
WHERE created_at > CURRENT_DATE - INTERVAL '30 days';
|
|
```
|
|
|
|
#### Query Analysis
|
|
|
|
```sql
|
|
-- Enable query logging for slow queries
|
|
ALTER SYSTEM SET log_min_duration_statement = 1000; -- Log queries over 1 second
|
|
ALTER SYSTEM SET log_statement = 'all';
|
|
SELECT pg_reload_conf();
|
|
|
|
-- Analyze query performance
|
|
EXPLAIN (ANALYZE, BUFFERS)
|
|
SELECT * FROM documents
|
|
WHERE user_id = '123'
|
|
AND created_at > '2024-01-01'
|
|
ORDER BY created_at DESC
|
|
LIMIT 100;
|
|
```
|
|
|
|
### Database Maintenance
|
|
|
|
```bash
|
|
#!/bin/bash
|
|
# maintenance.sh - Run weekly
|
|
|
|
# Vacuum and analyze
|
|
docker-compose exec postgres vacuumdb -U readur -d readur -z -v
|
|
|
|
# Reindex for better performance
|
|
docker-compose exec postgres reindexdb -U readur -d readur
|
|
|
|
# Update statistics
|
|
docker-compose exec postgres psql -U readur -d readur -c "ANALYZE;"
|
|
|
|
# Clean up old data via database queries
|
|
docker-compose exec readur psql -U readur -d readur -c \
|
|
"DELETE FROM sessions WHERE last_activity < NOW() - INTERVAL '30 days';"
|
|
|
|
# Check for orphaned files
|
|
docker-compose exec readur psql -U readur -d readur -c \
|
|
"SELECT COUNT(*) FROM documents WHERE file_path NOT IN (SELECT path FROM files);"
|
|
```
|
|
|
|
## OCR Performance
|
|
|
|
### OCR Worker Configuration
|
|
|
|
```bash
|
|
# Optimize based on CPU cores and RAM
|
|
OCR_WORKERS=4 # Number of parallel workers
|
|
OCR_MAX_PARALLEL=8 # Max concurrent OCR operations
|
|
OCR_QUEUE_SIZE=1000 # Queue buffer size
|
|
OCR_BATCH_SIZE=10 # Documents per batch
|
|
OCR_TIMEOUT=300 # Seconds per document
|
|
|
|
# Memory management
|
|
OCR_MAX_MEMORY_MB=1024 # Per worker memory limit
|
|
OCR_TEMP_DIR=/tmp/ocr # Use fast storage for temp files
|
|
|
|
# Tesseract optimization
|
|
TESSERACT_THREAD_LIMIT=2 # Threads per OCR job
|
|
TESSERACT_PSM=3 # Page segmentation mode
|
|
TESSERACT_OEM=1 # OCR engine mode (LSTM)
|
|
```
|
|
|
|
### OCR Processing Strategies
|
|
|
|
#### Priority Queue Implementation
|
|
|
|
```python
|
|
# priority_queue.py
|
|
from celery import Celery
|
|
from kombu import Queue, Exchange
|
|
|
|
app = Celery('readur')
|
|
|
|
# Define priority queues
|
|
app.conf.task_routes = {
|
|
'ocr.process_document': {'queue': 'ocr', 'routing_key': 'ocr.normal'},
|
|
'ocr.process_urgent': {'queue': 'ocr_priority', 'routing_key': 'ocr.high'},
|
|
}
|
|
|
|
app.conf.task_queues = (
|
|
Queue('ocr', Exchange('ocr'), routing_key='ocr.normal', priority=5),
|
|
Queue('ocr_priority', Exchange('ocr'), routing_key='ocr.high', priority=10),
|
|
)
|
|
|
|
# Worker configuration
|
|
app.conf.worker_prefetch_multiplier = 1
|
|
app.conf.task_acks_late = True
|
|
```
|
|
|
|
#### Batch Processing
|
|
|
|
```bash
|
|
# Re-queue pending OCR documents during off-hours
|
|
0 2 * * * docker-compose exec readur /app/enqueue_pending_ocr
|
|
```
|
|
|
|
## Storage Optimization
|
|
|
|
### File System Performance
|
|
|
|
#### Local Storage
|
|
|
|
```bash
|
|
# Mount options for better performance
|
|
/dev/sdb1 /data ext4 defaults,noatime,nodiratime,nobarrier 0 2
|
|
|
|
# For XFS
|
|
/dev/sdb1 /data xfs defaults,noatime,nodiratime,allocsize=64m 0 2
|
|
|
|
# Enable compression (Btrfs)
|
|
mount -o compress=lzo /dev/sdb1 /data
|
|
```
|
|
|
|
#### Storage Layout
|
|
|
|
```
|
|
/data/
|
|
├── readur/
|
|
│ ├── documents/ # Main storage (SSD recommended)
|
|
│ ├── temp/ # Temporary files (tmpfs or fast SSD)
|
|
│ ├── cache/ # Cache directory (SSD)
|
|
│ └── thumbnails/ # Generated thumbnails (can be slower storage)
|
|
```
|
|
|
|
### S3 Optimization
|
|
|
|
```bash
|
|
# S3 transfer optimization
|
|
S3_MAX_CONNECTIONS=100
|
|
S3_MAX_BANDWIDTH=100MB # Limit bandwidth if needed
|
|
S3_MULTIPART_THRESHOLD=64MB
|
|
S3_MULTIPART_CHUNKSIZE=16MB
|
|
S3_MAX_CONCURRENCY=10
|
|
S3_USE_ACCELERATE_ENDPOINT=true # AWS only
|
|
|
|
# Connection pooling
|
|
S3_CONNECTION_POOL_SIZE=50
|
|
S3_CONNECTION_TIMEOUT=30
|
|
S3_READ_TIMEOUT=60
|
|
```
|
|
|
|
## Caching Strategy
|
|
|
|
### Redis Configuration
|
|
|
|
```bash
|
|
# redis.conf
|
|
maxmemory 4gb
|
|
maxmemory-policy allkeys-lru
|
|
save "" # Disable persistence for cache-only use
|
|
tcp-keepalive 60
|
|
timeout 300
|
|
|
|
# Performance tuning
|
|
tcp-backlog 511
|
|
databases 2
|
|
hz 10
|
|
```
|
|
|
|
### Application Caching
|
|
|
|
```bash
|
|
# Cache configuration
|
|
CACHE_TYPE=redis
|
|
CACHE_REDIS_URL=redis://localhost:6379/0
|
|
CACHE_DEFAULT_TIMEOUT=3600 # 1 hour
|
|
CACHE_THRESHOLD=1000 # Max cached items
|
|
|
|
# Specific cache TTLs
|
|
CACHE_SEARCH_RESULTS_TTL=600 # 10 minutes
|
|
CACHE_USER_SESSIONS_TTL=3600 # 1 hour
|
|
CACHE_DOCUMENT_METADATA_TTL=86400 # 24 hours
|
|
CACHE_THUMBNAILS_TTL=604800 # 7 days
|
|
```
|
|
|
|
### CDN Integration
|
|
|
|
```nginx
|
|
# Serve static files through CDN
|
|
location /static/ {
|
|
expires 30d;
|
|
add_header Cache-Control "public, immutable";
|
|
add_header Vary "Accept-Encoding";
|
|
}
|
|
|
|
location /media/thumbnails/ {
|
|
expires 7d;
|
|
add_header Cache-Control "public";
|
|
}
|
|
```
|
|
|
|
## Application Optimization
|
|
|
|
### Gunicorn/Uvicorn Configuration
|
|
|
|
```bash
|
|
# Gunicorn settings
|
|
GUNICORN_WORKERS=4 # 2-4 x CPU cores
|
|
GUNICORN_WORKER_CLASS=uvicorn.workers.UvicornWorker
|
|
GUNICORN_WORKER_CONNECTIONS=1000
|
|
GUNICORN_MAX_REQUESTS=1000
|
|
GUNICORN_MAX_REQUESTS_JITTER=50
|
|
GUNICORN_TIMEOUT=30
|
|
GUNICORN_KEEPALIVE=5
|
|
|
|
# Thread pool
|
|
GUNICORN_THREADS=4
|
|
GUNICORN_THREAD_WORKERS=2
|
|
```
|
|
|
|
### Async Processing
|
|
|
|
```python
|
|
# async_config.py
|
|
import asyncio
|
|
from concurrent.futures import ThreadPoolExecutor
|
|
|
|
# Configure async settings
|
|
ASYNC_MAX_WORKERS = 10
|
|
ASYNC_QUEUE_SIZE = 100
|
|
executor = ThreadPoolExecutor(max_workers=ASYNC_MAX_WORKERS)
|
|
|
|
# Background task processing
|
|
CELERY_WORKER_CONCURRENCY = 4
|
|
CELERY_WORKER_PREFETCH_MULTIPLIER = 1
|
|
CELERY_TASK_TIME_LIMIT = 300
|
|
CELERY_TASK_SOFT_TIME_LIMIT = 240
|
|
```
|
|
|
|
## Network Optimization
|
|
|
|
### HTTP/2 Configuration
|
|
|
|
```nginx
|
|
server {
|
|
listen 443 ssl http2;
|
|
|
|
# HTTP/2 settings
|
|
http2_max_field_size 16k;
|
|
http2_max_header_size 32k;
|
|
http2_max_requests 1000;
|
|
|
|
# Keep-alive
|
|
keepalive_timeout 65;
|
|
keepalive_requests 100;
|
|
}
|
|
```
|
|
|
|
### Load Balancing
|
|
|
|
```nginx
|
|
upstream readur_backend {
|
|
least_conn; # Or ip_hash for session affinity
|
|
|
|
server backend1:8000 weight=5 max_fails=3 fail_timeout=30s;
|
|
server backend2:8000 weight=5 max_fails=3 fail_timeout=30s;
|
|
server backend3:8000 weight=3 backup;
|
|
|
|
keepalive 32;
|
|
}
|
|
```
|
|
|
|
## Monitoring and Profiling
|
|
|
|
### Performance Monitoring Stack
|
|
|
|
```yaml
|
|
# docker-compose.monitoring.yml
|
|
services:
|
|
prometheus:
|
|
image: prom/prometheus
|
|
volumes:
|
|
- ./prometheus.yml:/etc/prometheus/prometheus.yml
|
|
ports:
|
|
- "9090:9090"
|
|
|
|
grafana:
|
|
image: grafana/grafana
|
|
ports:
|
|
- "3000:3000"
|
|
environment:
|
|
- GF_SECURITY_ADMIN_PASSWORD=admin
|
|
|
|
node_exporter:
|
|
image: prom/node-exporter
|
|
ports:
|
|
- "9100:9100"
|
|
```
|
|
|
|
### Application Profiling
|
|
|
|
```python
|
|
# profile_middleware.py
|
|
import cProfile
|
|
import pstats
|
|
import io
|
|
|
|
class ProfilingMiddleware:
|
|
def __init__(self, app):
|
|
self.app = app
|
|
|
|
def __call__(self, environ, start_response):
|
|
if 'profile' in environ.get('QUERY_STRING', ''):
|
|
profiler = cProfile.Profile()
|
|
profiler.enable()
|
|
|
|
response = self.app(environ, start_response)
|
|
|
|
profiler.disable()
|
|
stream = io.StringIO()
|
|
stats = pstats.Stats(profiler, stream=stream)
|
|
stats.sort_stats('cumulative')
|
|
stats.print_stats(20)
|
|
|
|
print(stream.getvalue())
|
|
|
|
return response
|
|
return self.app(environ, start_response)
|
|
```
|
|
|
|
## Scaling Strategies
|
|
|
|
### Horizontal Scaling
|
|
|
|
```yaml
|
|
# docker-compose.scale.yml
|
|
services:
|
|
readur:
|
|
deploy:
|
|
replicas: 3
|
|
resources:
|
|
limits:
|
|
cpus: '2'
|
|
memory: 4G
|
|
reservations:
|
|
cpus: '1'
|
|
memory: 2G
|
|
|
|
ocr-worker:
|
|
deploy:
|
|
replicas: 5
|
|
resources:
|
|
limits:
|
|
cpus: '1'
|
|
memory: 2G
|
|
```
|
|
|
|
### Vertical Scaling Guidelines
|
|
|
|
| Users | CPU | RAM | Storage | Database |
|
|
|-------|-----|-----|---------|----------|
|
|
| 1-10 | 2 cores | 4GB | 100GB | Shared |
|
|
| 10-50 | 4 cores | 8GB | 500GB | Dedicated 2 cores, 4GB |
|
|
| 50-100 | 8 cores | 16GB | 1TB | Dedicated 4 cores, 8GB |
|
|
| 100-500 | 16 cores | 32GB | 5TB | Cluster |
|
|
| 500+ | Multiple servers | 64GB+ | Object storage | Cluster with replicas |
|
|
|
|
## Optimization Checklist
|
|
|
|
### Quick Wins
|
|
|
|
- [ ] Enable gzip compression
|
|
- [ ] Set appropriate cache headers
|
|
- [ ] Configure connection pooling
|
|
- [ ] Enable query result caching
|
|
- [ ] Optimize database indexes
|
|
- [ ] Tune OCR worker count
|
|
- [ ] Configure Redis caching
|
|
- [ ] Enable HTTP/2
|
|
|
|
### Advanced Optimizations
|
|
|
|
- [ ] Implement read replicas
|
|
- [ ] Set up CDN for static files
|
|
- [ ] Enable database partitioning
|
|
- [ ] Implement queue priorities
|
|
- [ ] Configure auto-scaling
|
|
- [ ] Set up performance monitoring
|
|
- [ ] Implement rate limiting
|
|
- [ ] Enable connection multiplexing
|
|
|
|
## Troubleshooting Performance Issues
|
|
|
|
### High CPU Usage
|
|
|
|
```bash
|
|
# Identify CPU-intensive processes
|
|
top -H -p $(pgrep -d',' readur)
|
|
|
|
# Check OCR worker load
|
|
docker-compose exec readur celery inspect active
|
|
|
|
# Profile Python code
|
|
python -m cProfile -o profile.stats app.py
|
|
```
|
|
|
|
### Memory Issues
|
|
|
|
```bash
|
|
# Check memory usage
|
|
free -h
|
|
docker stats
|
|
|
|
# Find memory leaks
|
|
docker-compose exec readur python -m tracemalloc
|
|
|
|
# Adjust memory limits
|
|
docker update --memory 4g readur_container
|
|
```
|
|
|
|
### Slow Queries
|
|
|
|
```sql
|
|
-- Find slow queries
|
|
SELECT query, calls, mean_exec_time, total_exec_time
|
|
FROM pg_stat_statements
|
|
ORDER BY mean_exec_time DESC
|
|
LIMIT 10;
|
|
|
|
-- Check missing indexes
|
|
SELECT schemaname, tablename, attname, n_distinct, correlation
|
|
FROM pg_stats
|
|
WHERE schemaname = 'public'
|
|
AND n_distinct > 100
|
|
AND correlation < 0.1
|
|
ORDER BY n_distinct DESC;
|
|
```
|
|
|
|
## Related Documentation
|
|
|
|
- [Architecture Overview](../architecture.md)
|
|
- [Monitoring Guide](./monitoring.md)
|
|
- [Database Guardrails](../dev/DATABASE_GUARDRAILS.md)
|
|
- [OCR Optimization](../dev/OCR_OPTIMIZATION_GUIDE.md) |