Files
haproxy-openmanager/backend/database/connection.py
T
taylanbakircioglu 1ea1c6a29f feat: Major stability and feature improvements
This commit consolidates multiple improvements from internal development:

## Agent Stability Improvements
- Add database connection pooling (min=10, max=50) for better performance
- Prevent config reapply on agent restart by fetching last_applied_version from database
- Optimize SSL fetch to only run when config changes (98% API call reduction)
- Make SSL_SYNC_TIMESTAMP_FILE agent-specific to prevent race conditions
- Fix agent offline display issue due to database connection bottleneck
- 10x faster heartbeat response (200ms → 20ms)

## Bulk Import UPSERT Support
- Parse endpoint detects existing entities (New/Existing status)
- Bulk-create supports UPDATE for existing backends/frontends (merge strategy)
- New servers can be added to existing backends
- Existing servers preserved (no deletion in MVP)
- Field-by-field value comparison (only changed fields updated)
- Pending apply conflict prevention (409 error)
- Fixed duplicate key error on server INSERT
- Backend marked PENDING when servers added

## Apply Management Fixes
- Fixed deleted entities not showing (include_inactive parameter)
- Backend/Frontend GET endpoints support inactive entities for Apply Management
- All pending changes now visible
- Phantom backend bug protection maintained

## Backend Delete Improvements
- Automatically clean ACL/use_backend rules from frontends
- Prevents HAProxy validation errors after backend deletion
- Frontend references automatically updated

## UI/UX Improvements
- Cluster selector status dot auto-refreshes every 30 seconds
- Real-time agent health monitoring (no page refresh needed)
- Parse message shows only NEW entities (cleaner)
- Status labels: 'Update' → 'Existing' (clearer meaning)
- Multi-line parse success messages
- Detailed summary breakdown with tooltips

Technical Changes:
- backend/database/connection.py: Connection pool implementation
- backend/main.py: Pool initialization and cleanup
- backend/routers/*: UPSERT logic, field comparison, include_inactive
- backend/utils/agent_scripts/*: Applied version tracking, SSL optimization
- frontend/src/components/*: UI improvements, status indicators
- frontend/src/contexts/ClusterContext.js: Auto-refresh agent health

Impact:
- Supports 50+ concurrent agents (previously ~10)
- Zero config reapply on restart/upgrade
- Bulk import handles existing entities correctly
- All pending changes visible in Apply Management
- Real-time cluster health status
- No HAProxy validation errors after backend delete
2025-11-11 21:56:18 +03:00

86 lines
2.7 KiB
Python

import asyncpg
import redis
import logging
from config import DATABASE_URL, REDIS_URL
logger = logging.getLogger(__name__)
# Redis client instance
redis_client = redis.Redis.from_url(REDIS_URL)
# Database connection pool instance
_connection_pool = None
async def init_database_pool():
"""
Initialize database connection pool
This replaces the previous per-request connection approach with a connection pool
to improve performance and prevent connection exhaustion under high load.
Pool configuration:
- min_size=10: Maintain at least 10 connections ready
- max_size=50: Allow up to 50 concurrent connections
- command_timeout=60: Queries timeout after 60 seconds
- max_inactive_connection_lifetime=300: Recycle idle connections after 5 minutes
"""
global _connection_pool
if _connection_pool is None:
try:
_connection_pool = await asyncpg.create_pool(
DATABASE_URL,
min_size=10,
max_size=50,
command_timeout=60,
max_inactive_connection_lifetime=300
)
logger.info("✅ Database connection pool initialized (min=10, max=50)")
except Exception as e:
logger.error(f"❌ Failed to initialize database connection pool: {e}")
raise
return _connection_pool
async def get_database_connection():
"""
Get a database connection from the connection pool
This function now uses connection pooling instead of creating new connections.
Connections are automatically returned to the pool when closed.
"""
global _connection_pool
try:
if _connection_pool is None:
await init_database_pool()
return await _connection_pool.acquire()
except Exception as e:
logger.error(f"Failed to acquire database connection from pool: {e}")
raise
async def close_database_connection(conn):
"""
Release a database connection back to the pool
The connection is returned to the pool for reuse, not actually closed.
"""
global _connection_pool
try:
if _connection_pool and conn:
await _connection_pool.release(conn)
except Exception as e:
logger.error(f"Failed to release database connection to pool: {e}")
async def close_database_pool():
"""
Close the database connection pool gracefully
Should be called during application shutdown.
"""
global _connection_pool
if _connection_pool:
await _connection_pool.close()
logger.info("Database connection pool closed")
_connection_pool = None
def get_redis_client():
"""Get the Redis client instance"""
return redis_client