[0.9.21] - 2025-09-21
All checks were successful
Deploy on push / deploy (push) Successful in 4m0s

### 🔧 Redis Connection Pool Fix
- **🐛 Fixed "max number of clients reached" error**: Исправлена критическая ошибка превышения лимита соединений Redis
  - Добавлен `aioredis.ConnectionPool` с ограничением `max_connections=20` для 5 микросервисов
  - Реализовано переиспользование соединений вместо создания новых для каждого запроса
  - Добавлено правильное закрытие connection pool при shutdown приложения
  - Улучшена обработка ошибок соединения с автоматическим переподключением
- **📊 Health Monitoring**: Добавлен `/health` endpoint для мониторинга состояния Redis
  - Отображает количество активных соединений, использование памяти, версию Redis
  - Помогает диагностировать проблемы с соединениями в production
- **🔄 Connection Management**: Оптимизировано управление соединениями
  - Один connection pool для всех операций Redis
  - Автоматическое переподключение при потере соединения
  - Корректное закрытие всех соединений при остановке приложения

### 🧪 TypeScript Warnings Fix
- **🏷️ Type Annotations**: Добавлены явные типы для устранения implicit `any` ошибок
  - Исправлены типы в `RolesModal.tsx` для параметров `roleName` и `r`
  - Устранены все TypeScript warnings в admin panel

### 🚀 CI/CD Improvements
- ** Mypy Optimization**: Исправлена проблема OOM (exit status 137) в CI
  - Оптимизирован `mypy.ini` с исключением тяжелых зависимостей
  - Добавлен `dmypy` с fallback на обычный `mypy`
  - Ограничена область проверки типов только критичными модулями
  - Добавлена проверка доступной памяти перед запуском mypy
- **🐳 Docker Build**: Исправлены проблемы с PyTorch зависимостями
  - Увеличен `UV_HTTP_TIMEOUT=300` для загрузки больших пакетов
  - Установлен `TORCH_CUDA_AVAILABLE=0` для предотвращения CUDA зависимостей
  - Упрощены зависимости PyTorch в `pyproject.toml` для совместимости с Python 3.13
This commit is contained in:
2025-09-21 14:23:53 +03:00
parent 634cec657c
commit 4dccb84b18
3 changed files with 94 additions and 30 deletions

View File

@@ -1,5 +1,37 @@
# Changelog
## [0.9.21] - 2025-09-21
### 🔧 Redis Connection Pool Fix
- **🐛 Fixed "max number of clients reached" error**: Исправлена критическая ошибка превышения лимита соединений Redis
- Добавлен `aioredis.ConnectionPool` с ограничением `max_connections=20` для 5 микросервисов
- Реализовано переиспользование соединений вместо создания новых для каждого запроса
- Добавлено правильное закрытие connection pool при shutdown приложения
- Улучшена обработка ошибок соединения с автоматическим переподключением
- **📊 Health Monitoring**: Добавлен `/health` endpoint для мониторинга состояния Redis
- Отображает количество активных соединений, использование памяти, версию Redis
- Помогает диагностировать проблемы с соединениями в production
- **🔄 Connection Management**: Оптимизировано управление соединениями
- Один connection pool для всех операций Redis
- Автоматическое переподключение при потере соединения
- Корректное закрытие всех соединений при остановке приложения
### 🧪 TypeScript Warnings Fix
- **🏷️ Type Annotations**: Добавлены явные типы для устранения implicit `any` ошибок
- Исправлены типы в `RolesModal.tsx` для параметров `roleName` и `r`
- Устранены все TypeScript warnings в admin panel
### 🚀 CI/CD Improvements
- **⚡ Mypy Optimization**: Исправлена проблема OOM (exit status 137) в CI
- Оптимизирован `mypy.ini` с исключением тяжелых зависимостей
- Добавлен `dmypy` с fallback на обычный `mypy`
- Ограничена область проверки типов только критичными модулями
- Добавлена проверка доступной памяти перед запуском mypy
- **🐳 Docker Build**: Исправлены проблемы с PyTorch зависимостями
- Увеличен `UV_HTTP_TIMEOUT=300` для загрузки больших пакетов
- Установлен `TORCH_CUDA_AVAILABLE=0` для предотвращения CUDA зависимостей
- Упрощены зависимости PyTorch в `pyproject.toml` для совместимости с Python 3.13
## [0.9.20] - 2025-09-10
### 🐛 Authors Endpoint Critical Fix

14
main.py
View File

@@ -143,6 +143,18 @@ async def spa_handler(request: Request) -> Response:
return JSONResponse({"error": "Admin panel not built"}, status_code=404)
async def health_handler(request: Request) -> Response:
"""Health check endpoint with Redis monitoring"""
try:
redis_info = await redis.get_info()
return JSONResponse(
{"status": "healthy", "redis": {"connected": redis.is_connected, "ping": await redis.ping(), **redis_info}}
)
except Exception as e:
logger.error(f"Health check failed: {e}")
return JSONResponse({"status": "unhealthy", "error": str(e)}, status_code=500)
async def shutdown() -> None:
"""Остановка сервера и освобождение ресурсов"""
logger.info("Остановка сервера")
@@ -293,6 +305,8 @@ app = Starlette(
# OAuth маршруты
Route("/oauth/{provider}", oauth_login, methods=["GET"]),
Route("/oauth/{provider}/callback", oauth_callback, methods=["GET"]),
# Health check endpoint
Route("/health", health_handler, methods=["GET"]),
# Статические файлы (CSS, JS, изображения)
Mount("/assets", app=StaticFiles(directory=str(DIST_DIR / "assets"))),
# Корневой маршрут для админ-панели

View File

@@ -23,30 +23,28 @@ class RedisService:
self._client: aioredis.Redis | None = None
self._redis_url = redis_url # Исправлено на _redis_url
self._is_available = aioredis is not None
self._connection_pool: aioredis.ConnectionPool | None = None
if not self._is_available:
logger.warning("Redis is not available - aioredis not installed")
async def close(self) -> None:
"""Close Redis connection"""
"""Close Redis connection and connection pool"""
if self._client:
# Закрываем существующее соединение если есть
try:
await self._client.close()
except Exception as e:
logger.error(f"Error closing Redis connection: {e}")
# Для теста disconnect_exception_handling
if str(e) == "Disconnect error":
# Сохраняем клиент для теста
self._last_close_error = e
raise
# Для других исключений просто логируем
logger.error(f"Error closing Redis client: {e}")
finally:
# Сохраняем клиент для теста disconnect_exception_handling
if hasattr(self, "_last_close_error") and str(self._last_close_error) == "Disconnect error":
pass
else:
self._client = None
self._client = None
if self._connection_pool:
try:
await self._connection_pool.disconnect()
except Exception as e:
logger.error(f"Error closing Redis connection pool: {e}")
finally:
self._connection_pool = None
# Добавляем метод disconnect как алиас для close
async def disconnect(self) -> None:
@@ -54,16 +52,13 @@ class RedisService:
await self.close()
async def connect(self) -> bool:
"""Connect to Redis"""
"""Connect to Redis with connection pooling"""
try:
if self._client:
# Закрываем существующее соединение
try:
await self._client.close()
except Exception as e:
logger.error(f"Error closing Redis connection: {e}")
# Закрываем существующие соединения
await self.close()
self._client = aioredis.from_url(
# Создаем connection pool
self._connection_pool = aioredis.ConnectionPool.from_url(
self._redis_url,
encoding="utf-8",
decode_responses=True,
@@ -71,16 +66,20 @@ class RedisService:
socket_timeout=5,
retry_on_timeout=True,
health_check_interval=30,
max_connections=20, # 20 соединений
retry_on_error=[ConnectionError, TimeoutError],
)
# Создаем клиент с connection pool
self._client = aioredis.Redis(connection_pool=self._connection_pool)
# Test connection
await self._client.ping()
logger.info("Successfully connected to Redis")
logger.info("Successfully connected to Redis with connection pooling")
return True
except Exception:
logger.exception("Failed to connect to Redis")
if self._client:
await self._client.close()
self._client = None
await self.close()
return False
@property
@@ -95,9 +94,12 @@ class RedisService:
return None
async def execute(self, command: str, *args: Any) -> Any:
"""Execute Redis command with reconnection logic"""
"""Execute Redis command with connection pooling"""
if not self.is_connected:
await self.connect()
logger.warning("Redis not connected, attempting to connect...")
if not await self.connect():
logger.error("Failed to connect to Redis")
return None
try:
cmd_method = getattr(self._client, command.lower(), None)
@@ -122,8 +124,8 @@ class RedisService:
except Exception:
logger.exception("Redis retry failed")
return None
except Exception:
logger.exception("Redis command failed")
except Exception as e:
logger.error(f"Redis command {command} failed: {e}")
return None
async def get(self, key: str) -> str | bytes | None:
@@ -251,6 +253,22 @@ class RedisService:
except Exception:
return False
async def get_info(self) -> dict[str, Any]:
"""Get Redis server info"""
if not self.is_connected or self._client is None:
return {}
try:
info = await self._client.info()
return {
"connected_clients": info.get("connected_clients", 0),
"used_memory": info.get("used_memory_human", "0B"),
"redis_version": info.get("redis_version", "unknown"),
"uptime_in_seconds": info.get("uptime_in_seconds", 0),
}
except Exception as e:
logger.error(f"Failed to get Redis info: {e}")
return {}
async def execute_pipeline(self, commands: list[tuple[str, tuple[Any, ...]]]) -> list[Any]:
"""
Выполняет список команд через pipeline для лучшей производительности.