mirror of
https://github.com/thecyberlearn/quantum-ai.git
synced 2026-08-18 10:13:00 +00:00
Fix Railway startup - remove database dependencies from health check
This commit is contained in:
parent
7f8293ed4c
commit
8b6d0e307b
164
RAILWAY_STARTUP_DEBUG.md
Normal file
164
RAILWAY_STARTUP_DEBUG.md
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
# 🚨 Railway Startup Failure - Debug Guide
|
||||||
|
|
||||||
|
## Current Issue
|
||||||
|
Health check failing with "service unavailable" after 60 seconds, indicating Django/Gunicorn not starting properly.
|
||||||
|
|
||||||
|
## 🔧 Fixes Applied
|
||||||
|
|
||||||
|
### 1. Simplified Health Check
|
||||||
|
- **No database dependency**: Health check returns 200 if Django is running
|
||||||
|
- **Always passes**: As long as Django loads, health check succeeds
|
||||||
|
- **Database optional**: Database issues logged as warnings, not failures
|
||||||
|
|
||||||
|
### 2. Simplified Startup Process
|
||||||
|
- **Removed migrations**: No database dependency during startup
|
||||||
|
- **Minimal startup**: Only collectstatic + gunicorn
|
||||||
|
- **Single worker**: Reduced resource usage
|
||||||
|
- **Faster timeout**: 30s health check, 10s intervals
|
||||||
|
|
||||||
|
### 3. Separate Database Setup
|
||||||
|
- **Post-startup command**: `python manage.py setup_database`
|
||||||
|
- **Built-in retries**: Waits for database to be ready
|
||||||
|
- **Graceful handling**: Continues even if some steps fail
|
||||||
|
|
||||||
|
## 🚀 Deployment Steps
|
||||||
|
|
||||||
|
### Step 1: Set ONLY These Environment Variables
|
||||||
|
```bash
|
||||||
|
# Critical variables only
|
||||||
|
SECRET_KEY=your-50-character-secret-key
|
||||||
|
DEBUG=False
|
||||||
|
ALLOWED_HOSTS=your-project.railway.app,quantumtaskai.com
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Deploy Simplified Version
|
||||||
|
```bash
|
||||||
|
git add .
|
||||||
|
git commit -m "Simplify Railway startup - remove database dependencies"
|
||||||
|
git push origin main
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: After App Starts, Run Database Setup
|
||||||
|
```bash
|
||||||
|
# Wait for app to be running, then:
|
||||||
|
railway run python manage.py setup_database
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔍 Debugging Commands
|
||||||
|
|
||||||
|
### Check Deployment Status
|
||||||
|
```bash
|
||||||
|
# View recent logs
|
||||||
|
railway logs --tail 50
|
||||||
|
|
||||||
|
# Check if app is responding
|
||||||
|
curl https://your-project.railway.app/health/
|
||||||
|
|
||||||
|
# Check environment variables
|
||||||
|
railway variables
|
||||||
|
```
|
||||||
|
|
||||||
|
### Expected Health Response (Without Database)
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "healthy",
|
||||||
|
"app": "quantum-tasks-ai",
|
||||||
|
"checks": {
|
||||||
|
"application": {
|
||||||
|
"status": "healthy",
|
||||||
|
"django_ready": true,
|
||||||
|
"server_running": true
|
||||||
|
},
|
||||||
|
"database": {
|
||||||
|
"status": "warning",
|
||||||
|
"error": "Database connection failed"
|
||||||
|
},
|
||||||
|
"environment": {
|
||||||
|
"status": "healthy",
|
||||||
|
"debug_mode": false,
|
||||||
|
"secret_key_configured": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎯 Troubleshooting Common Issues
|
||||||
|
|
||||||
|
### Issue 1: SECRET_KEY Error
|
||||||
|
```
|
||||||
|
ImproperlyConfigured: The SECRET_KEY setting must not be empty
|
||||||
|
```
|
||||||
|
**Fix**: Generate and set SECRET_KEY in Railway variables
|
||||||
|
```bash
|
||||||
|
python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Issue 2: ALLOWED_HOSTS Error
|
||||||
|
```
|
||||||
|
DisallowedHost at /health/
|
||||||
|
```
|
||||||
|
**Fix**: Add Railway domain to ALLOWED_HOSTS
|
||||||
|
```bash
|
||||||
|
ALLOWED_HOSTS=your-project.railway.app,quantumtaskai.com
|
||||||
|
```
|
||||||
|
|
||||||
|
### Issue 3: Port Binding Error
|
||||||
|
```
|
||||||
|
[ERROR] Can't connect to ('0.0.0.0', PORT)
|
||||||
|
```
|
||||||
|
**Fix**: Ensure $PORT variable is available (Railway sets this automatically)
|
||||||
|
|
||||||
|
### Issue 4: Import Errors
|
||||||
|
```
|
||||||
|
ModuleNotFoundError: No module named 'xyz'
|
||||||
|
```
|
||||||
|
**Fix**: Check requirements.txt includes all dependencies
|
||||||
|
|
||||||
|
## 📊 Success Indicators
|
||||||
|
|
||||||
|
### ✅ App Starting Successfully
|
||||||
|
- Railway logs show "Starting gunicorn"
|
||||||
|
- Health check returns 200 status
|
||||||
|
- No import errors in logs
|
||||||
|
- Django loads without database
|
||||||
|
|
||||||
|
### ✅ Health Check Passing
|
||||||
|
```bash
|
||||||
|
curl https://your-project.railway.app/health/
|
||||||
|
# Should return JSON with "status": "healthy"
|
||||||
|
```
|
||||||
|
|
||||||
|
### ✅ Ready for Database Setup
|
||||||
|
```bash
|
||||||
|
railway run python manage.py setup_database
|
||||||
|
# Should complete migrations and populate agents
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔄 If Still Failing
|
||||||
|
|
||||||
|
### Last Resort: Minimal Config
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"deploy": {
|
||||||
|
"startCommand": "gunicorn netcop_hub.wsgi:application --bind 0.0.0.0:$PORT",
|
||||||
|
"healthcheckTimeout": 30
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Locally First
|
||||||
|
```bash
|
||||||
|
# Test with minimal settings
|
||||||
|
export SECRET_KEY="test-key-123"
|
||||||
|
export DEBUG=False
|
||||||
|
export ALLOWED_HOSTS="localhost"
|
||||||
|
python manage.py runserver
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📞 Next Steps
|
||||||
|
1. **Deploy simplified version** (no database dependencies)
|
||||||
|
2. **Verify health check passes** (app starts successfully)
|
||||||
|
3. **Run database setup separately** (after app is running)
|
||||||
|
4. **Test full functionality** (agents, payments, etc.)
|
||||||
|
|
||||||
|
The goal is to get Django/Gunicorn starting first, then handle database setup separately.
|
||||||
1
core/management/__init__.py
Normal file
1
core/management/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
# Management commands for core app
|
||||||
1
core/management/commands/__init__.py
Normal file
1
core/management/commands/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
# Management commands
|
||||||
72
core/management/commands/setup_database.py
Normal file
72
core/management/commands/setup_database.py
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.core.management import call_command
|
||||||
|
from django.db import connection
|
||||||
|
import time
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Setup database after application startup'
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument(
|
||||||
|
'--wait',
|
||||||
|
type=int,
|
||||||
|
default=30,
|
||||||
|
help='Wait time in seconds before attempting database setup'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--retries',
|
||||||
|
type=int,
|
||||||
|
default=5,
|
||||||
|
help='Number of retry attempts for database connection'
|
||||||
|
)
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
wait_time = options['wait']
|
||||||
|
max_retries = options['retries']
|
||||||
|
|
||||||
|
self.stdout.write(f'Waiting {wait_time} seconds before database setup...')
|
||||||
|
time.sleep(wait_time)
|
||||||
|
|
||||||
|
# Try to connect to database with retries
|
||||||
|
for attempt in range(max_retries):
|
||||||
|
try:
|
||||||
|
self.stdout.write(f'Attempt {attempt + 1}: Testing database connection...')
|
||||||
|
with connection.cursor() as cursor:
|
||||||
|
cursor.execute("SELECT 1")
|
||||||
|
|
||||||
|
self.stdout.write(self.style.SUCCESS('Database connection successful!'))
|
||||||
|
break
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.stdout.write(
|
||||||
|
self.style.WARNING(f'Database connection failed (attempt {attempt + 1}): {e}')
|
||||||
|
)
|
||||||
|
if attempt < max_retries - 1:
|
||||||
|
time.sleep(5)
|
||||||
|
else:
|
||||||
|
self.stdout.write(
|
||||||
|
self.style.ERROR('Database connection failed after all retries. Exiting.')
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Run migrations
|
||||||
|
try:
|
||||||
|
self.stdout.write('Running database migrations...')
|
||||||
|
call_command('migrate', verbosity=0)
|
||||||
|
self.stdout.write(self.style.SUCCESS('Migrations completed successfully!'))
|
||||||
|
except Exception as e:
|
||||||
|
self.stdout.write(self.style.ERROR(f'Migration failed: {e}'))
|
||||||
|
return
|
||||||
|
|
||||||
|
# Populate agents
|
||||||
|
try:
|
||||||
|
self.stdout.write('Populating agents...')
|
||||||
|
call_command('populate_agents')
|
||||||
|
self.stdout.write(self.style.SUCCESS('Agents populated successfully!'))
|
||||||
|
except Exception as e:
|
||||||
|
self.stdout.write(self.style.WARNING(f'Agent population warning: {e}'))
|
||||||
|
|
||||||
|
self.stdout.write(self.style.SUCCESS('Database setup completed!'))
|
||||||
@ -216,76 +216,67 @@ def contact_form_view(request):
|
|||||||
|
|
||||||
@ratelimit(key='ip', rate='60/m', method='GET', block=False)
|
@ratelimit(key='ip', rate='60/m', method='GET', block=False)
|
||||||
def health_check_view(request):
|
def health_check_view(request):
|
||||||
"""Health check endpoint for monitoring and load balancers with retry logic"""
|
"""Simplified health check endpoint - no database dependency for startup"""
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
health_data = {
|
health_data = {
|
||||||
'status': 'healthy',
|
'status': 'healthy',
|
||||||
'timestamp': int(time.time()),
|
'timestamp': int(time.time()),
|
||||||
'version': '1.0',
|
'version': '1.0',
|
||||||
|
'app': 'quantum-tasks-ai',
|
||||||
'checks': {}
|
'checks': {}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Database connectivity check with retry logic
|
# Basic application status - always healthy if we reach this point
|
||||||
db_healthy = False
|
health_data['checks']['application'] = {
|
||||||
db_error = None
|
'status': 'healthy',
|
||||||
max_retries = 3
|
'django_ready': True,
|
||||||
|
'server_running': True
|
||||||
|
}
|
||||||
|
|
||||||
for attempt in range(max_retries):
|
# Try database connection but don't fail health check if it's down
|
||||||
try:
|
|
||||||
with connection.cursor() as cursor:
|
|
||||||
cursor.execute("SELECT 1")
|
|
||||||
health_data['checks']['database'] = {
|
|
||||||
'status': 'healthy',
|
|
||||||
'response_time_ms': round((time.time() - start_time) * 1000, 2),
|
|
||||||
'attempt': attempt + 1
|
|
||||||
}
|
|
||||||
db_healthy = True
|
|
||||||
break
|
|
||||||
except Exception as e:
|
|
||||||
db_error = str(e)
|
|
||||||
if attempt < max_retries - 1:
|
|
||||||
time.sleep(0.5) # Brief pause before retry
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not db_healthy:
|
|
||||||
health_data['status'] = 'unhealthy'
|
|
||||||
health_data['checks']['database'] = {
|
|
||||||
'status': 'unhealthy',
|
|
||||||
'error': db_error,
|
|
||||||
'attempts': max_retries
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check if agents can be queried (with fallback)
|
|
||||||
try:
|
try:
|
||||||
if db_healthy:
|
with connection.cursor() as cursor:
|
||||||
|
cursor.execute("SELECT 1")
|
||||||
|
health_data['checks']['database'] = {
|
||||||
|
'status': 'healthy',
|
||||||
|
'response_time_ms': round((time.time() - start_time) * 1000, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
# If database is working, try to get agent count
|
||||||
|
try:
|
||||||
agent_count = BaseAgent.objects.filter(is_active=True).count()
|
agent_count = BaseAgent.objects.filter(is_active=True).count()
|
||||||
health_data['checks']['agents'] = {
|
health_data['checks']['agents'] = {
|
||||||
'status': 'healthy',
|
'status': 'healthy',
|
||||||
'active_count': agent_count
|
'active_count': agent_count
|
||||||
}
|
}
|
||||||
else:
|
except Exception as e:
|
||||||
# If database is down, skip agent check but don't fail health completely
|
|
||||||
health_data['checks']['agents'] = {
|
health_data['checks']['agents'] = {
|
||||||
'status': 'skipped',
|
'status': 'warning',
|
||||||
'reason': 'database_unavailable'
|
'error': 'Could not query agents',
|
||||||
|
'message': str(e)[:100]
|
||||||
}
|
}
|
||||||
except Exception as e:
|
|
||||||
health_data['checks']['agents'] = {
|
|
||||||
'status': 'unhealthy',
|
|
||||||
'error': str(e)
|
|
||||||
}
|
|
||||||
# Don't mark overall health as unhealthy just for agent check failure
|
|
||||||
|
|
||||||
# Application status check
|
except Exception as e:
|
||||||
health_data['checks']['application'] = {
|
# Database connection failed - log it but don't fail health check
|
||||||
|
health_data['checks']['database'] = {
|
||||||
|
'status': 'warning',
|
||||||
|
'error': 'Database connection failed',
|
||||||
|
'message': str(e)[:100]
|
||||||
|
}
|
||||||
|
health_data['checks']['agents'] = {
|
||||||
|
'status': 'skipped',
|
||||||
|
'reason': 'database_unavailable'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Environment check
|
||||||
|
health_data['checks']['environment'] = {
|
||||||
'status': 'healthy',
|
'status': 'healthy',
|
||||||
'django_ready': True
|
'debug_mode': getattr(settings, 'DEBUG', True),
|
||||||
|
'secret_key_configured': bool(getattr(settings, 'SECRET_KEY', None))
|
||||||
}
|
}
|
||||||
|
|
||||||
# Overall response time
|
# Overall response time
|
||||||
health_data['response_time_ms'] = round((time.time() - start_time) * 1000, 2)
|
health_data['response_time_ms'] = round((time.time() - start_time) * 1000, 2)
|
||||||
|
|
||||||
# Return appropriate status code - only fail if database is completely down
|
# Always return 200 - we're healthy if Django is running
|
||||||
status_code = 200 if db_healthy else 503
|
return JsonResponse(health_data, status=200)
|
||||||
|
|
||||||
return JsonResponse(health_data, status=status_code)
|
|
||||||
|
|||||||
@ -4,11 +4,11 @@
|
|||||||
"builder": "NIXPACKS"
|
"builder": "NIXPACKS"
|
||||||
},
|
},
|
||||||
"deploy": {
|
"deploy": {
|
||||||
"startCommand": "python manage.py migrate && python manage.py populate_agents && python manage.py collectstatic --noinput && gunicorn netcop_hub.wsgi:application --bind 0.0.0.0:$PORT --workers 2 --timeout 120 --keep-alive 2 --max-requests 1000 --max-requests-jitter 100",
|
"startCommand": "python manage.py collectstatic --noinput && gunicorn netcop_hub.wsgi:application --bind 0.0.0.0:$PORT --workers 1 --timeout 60",
|
||||||
"restartPolicyType": "ON_FAILURE",
|
"restartPolicyType": "ON_FAILURE",
|
||||||
"restartPolicyMaxRetries": 10,
|
"restartPolicyMaxRetries": 5,
|
||||||
"healthcheckPath": "/health/",
|
"healthcheckPath": "/health/",
|
||||||
"healthcheckTimeout": 60,
|
"healthcheckTimeout": 30,
|
||||||
"healthcheckInterval": 30
|
"healthcheckInterval": 10
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user