database optimization/ back button in the configurator/ query competition and more

This commit is contained in:
Vladless
2026-02-12 00:53:38 +03:00
parent 2f1d8ff9e5
commit 2041e66956
32 changed files with 351 additions and 111 deletions
+2
View File
@@ -113,6 +113,7 @@ async def create_coupon_usage(session: AsyncSession, coupon_id: int, user_id: in
except SQLAlchemyError as e:
logger.error(f"❌ Ошибка при сохранении использования купона: {e}")
await session.rollback()
raise
async def check_coupon_usage(session: AsyncSession, coupon_id: int, user_id: int) -> bool:
@@ -136,6 +137,7 @@ async def update_coupon_usage_count(session: AsyncSession, coupon_id: int):
except SQLAlchemyError as e:
logger.error(f"❌ Ошибка при обновлении купона {coupon_id}: {e}")
await session.rollback()
raise
def apply_percent_coupon(price_rub: int, coupon: Coupon) -> tuple[int, int]:
+29 -3
View File
@@ -1,15 +1,24 @@
import asyncio
from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.orm import declarative_base
from config import DATABASE_URL
from config import DATABASE_URL, DB_MAX_OVERFLOW, DB_POOL_SIZE
CONCURRENT_UPDATES_LIMIT = DB_POOL_SIZE + DB_MAX_OVERFLOW
MAX_UPDATE_AGE_SEC = 28
engine = create_async_engine(
DATABASE_URL,
echo=False,
future=True,
pool_size=100,
max_overflow=200,
pool_size=DB_POOL_SIZE,
max_overflow=DB_MAX_OVERFLOW,
pool_timeout=60,
pool_pre_ping=True,
pool_recycle=300,
)
async_session_maker = async_sessionmaker(
@@ -19,3 +28,20 @@ async_session_maker = async_sessionmaker(
)
Base = declarative_base()
WARM_POOL_COUNT = 10
async def warm_pool() -> None:
"""
Прогревает пул соединений при старте.
"""
async def _one() -> None:
async with async_session_maker() as session:
await session.execute(text("SELECT 1"))
count = min(WARM_POOL_COUNT, DB_POOL_SIZE)
if count <= 0:
return
await asyncio.gather(*[asyncio.create_task(_one()) for _ in range(count)])
+1 -1
View File
@@ -50,4 +50,4 @@ async def store_gift_link(
except SQLAlchemyError as e:
logger.error(f"❌ Ошибка при сохранении подарка {gift_id}: {e}")
await session.rollback()
return False
raise
+6 -4
View File
@@ -92,8 +92,9 @@ async def import_keys_from_3xui_db(db_path: str, session: AsyncSession) -> tuple
updated_at=datetime.utcnow(),
)
)
except SQLAlchemyError:
continue
except SQLAlchemyError as e:
await session.rollback()
raise RuntimeError(f"Ошибка при импорте пользователя tg_id={tg_id}") from e
key_exists = await session.execute(select(Key).where(Key.client_id == client_id))
if key_exists.scalar():
@@ -119,8 +120,9 @@ async def import_keys_from_3xui_db(db_path: str, session: AsyncSession) -> tuple
)
)
imported += 1
except SQLAlchemyError:
continue
except SQLAlchemyError as e:
await session.rollback()
raise RuntimeError(f"Ошибка при импорте ключа client_id={client_id}") from e
await session.commit()
return imported, skipped
-3
View File
@@ -5,7 +5,6 @@ from sqlalchemy import select
from config import ADMIN_ID
from database.db import async_session_maker, engine
from database.models import Admin, Base, User
from database.tariffs import initialize_all_tariff_weights
async def init_db():
@@ -35,5 +34,3 @@ async def init_db():
)
)
await session.commit()
await initialize_all_tariff_weights(session)
+4 -2
View File
@@ -85,6 +85,7 @@ async def store_key(
except SQLAlchemyError as e:
logger.error(f"❌ Ошибка при сохранении ключа: {e}")
await session.rollback()
raise
async def get_keys(session: AsyncSession, tg_id: int):
@@ -155,10 +156,11 @@ async def get_key_count(session: AsyncSession, tg_id: int) -> int:
return result.scalar() or 0
async def delete_key(session: AsyncSession, identifier: int | str):
async def delete_key(session: AsyncSession, identifier: int | str, commit: bool = True):
stmt = delete(Key).where(Key.tg_id == identifier if str(identifier).isdigit() else Key.client_id == identifier)
await session.execute(stmt)
await session.commit()
if commit:
await session.commit()
logger.info(f"Ключ с идентификатором {identifier} удалён")
+3
View File
@@ -31,6 +31,7 @@ async def add_notification(session: AsyncSession, tg_id: int, notification_type:
except SQLAlchemyError as e:
logger.error(f"❌ Ошибка при добавлении уведомления: {e}")
await session.rollback()
raise
async def delete_notification(session: AsyncSession, tg_id: int, notification_type: str):
@@ -101,6 +102,7 @@ async def check_hot_lead_discount(session: AsyncSession, tg_id: int) -> dict:
except Exception as e:
logger.error(f"❌ Ошибка при проверке скидки горячего лида для {tg_id}: {e}")
await session.rollback()
return {"available": False}
@@ -173,4 +175,5 @@ async def check_notifications_bulk(
except Exception as e:
logger.error(f"Ошибка при массовой проверке уведомлений типа {notification_type}: {e}")
await session.rollback()
return []
+2
View File
@@ -106,6 +106,7 @@ async def get_payment_by_id(session: AsyncSession, internal_id: int) -> dict | N
}
except SQLAlchemyError as e:
logger.error(f"Ошибка при поиске платежа id={internal_id}: {e}")
await session.rollback()
return None
@@ -161,6 +162,7 @@ async def get_payment_by_payment_id(session: AsyncSession, pid: str) -> dict | N
}
except SQLAlchemyError as e:
logger.error(f"Ошибка при поиске платежа payment_id={pid}: {e}")
await session.rollback()
return None
+1
View File
@@ -224,6 +224,7 @@ async def get_referral_stats(session: AsyncSession, referrer_tg_id: int):
except Exception as e:
logger.error(f"[ReferralStats] Ошибка при получении статистики для пользователя {referrer_tg_id}: {e}")
await session.rollback()
raise
+4
View File
@@ -100,6 +100,7 @@ async def get_servers(session: AsyncSession, include_enabled: bool = False) -> d
return grouped
except SQLAlchemyError as e:
logger.error(f"Ошибка при получении серверов: {e}")
await session.rollback()
return {}
@@ -124,6 +125,7 @@ async def check_server_name_by_cluster(session: AsyncSession, server_name: str)
return {"cluster_name": row[0]} if row else None
except SQLAlchemyError as e:
logger.error(f"Ошибка при поиске кластера для сервера {server_name}: {e}")
await session.rollback()
return None
@@ -161,6 +163,7 @@ async def get_server_by_name(session: AsyncSession, server_name: str) -> dict |
return None
except SQLAlchemyError as e:
logger.error(f"Ошибка при получении сервера {server_name}: {e}")
await session.rollback()
return None
@@ -209,6 +212,7 @@ async def get_available_clusters(session: AsyncSession) -> list[str]:
return [row[0] for row in result.all()]
except SQLAlchemyError as e:
logger.error(f"Ошибка при получении списка кластеров: {e}")
await session.rollback()
return []
+4
View File
@@ -73,6 +73,7 @@ async def get_tariffs(
return tariffs
except SQLAlchemyError as e:
logger.error(f"[TARIFF] Ошибка при получении тарифов: {e}")
await session.rollback()
return []
@@ -83,6 +84,7 @@ async def get_tariff_by_id(session: AsyncSession, tariff_id: int):
return dict(tariff.__dict__) if tariff else None
except SQLAlchemyError as e:
logger.error(f"[TARIFF] Ошибка при получении тарифа по ID {tariff_id}: {e}")
await session.rollback()
return None
@@ -179,6 +181,7 @@ async def check_tariff_exists(session: AsyncSession, tariff_id: int):
return False
except SQLAlchemyError as e:
logger.error(f"[TARIFF] Ошибка при проверке тарифа {tariff_id}: {e}")
await session.rollback()
return False
@@ -195,6 +198,7 @@ async def get_tariff_sort_order(session: AsyncSession, tariff_id: int) -> int:
return sort_order
except SQLAlchemyError as e:
logger.error(f"[TARIFF] Ошибка при получении sort_order для тарифа {tariff_id}: {e}")
await session.rollback()
return None
+1
View File
@@ -25,6 +25,7 @@ async def create_temporary_data(session: AsyncSession, tg_id: int, state: str, d
except SQLAlchemyError as e:
logger.error(f"❌ Ошибка при сохранении временных данных для {tg_id}: {e}")
await session.rollback()
raise
async def get_temporary_data(session: AsyncSession, tg_id: int) -> dict | None:
+1
View File
@@ -21,6 +21,7 @@ async def create_tracking_source(session: AsyncSession, name: str, code: str, ty
except SQLAlchemyError as e:
logger.error(f"❌ Ошибка при создании источника {code}: {e}")
await session.rollback()
raise
async def get_all_tracking_sources(session: AsyncSession) -> list[dict]:
+3 -1
View File
@@ -100,6 +100,7 @@ async def set_user_balance(session: AsyncSession, tg_id: int, balance: float) ->
except SQLAlchemyError as e:
logger.error(f"Ошибка при установке баланса для пользователя {tg_id}: {e}")
await session.rollback()
raise
async def update_trial(session: AsyncSession, tg_id: int, status: int):
@@ -110,6 +111,7 @@ async def update_trial(session: AsyncSession, tg_id: int, status: int):
except SQLAlchemyError as e:
logger.error(f"[DB] Ошибка при обновлении триала пользователя {tg_id}: {e}")
await session.rollback()
raise
async def get_trial(session: AsyncSession, tg_id: int) -> int:
@@ -205,7 +207,7 @@ async def delete_user_data(session: AsyncSession, tg_id: int):
delete(Referral).where(or_(Referral.referrer_tg_id == tg_id, Referral.referred_tg_id == tg_id))
)
await session.execute(delete(CouponUsage).where(CouponUsage.user_id == tg_id))
await delete_key(session, tg_id)
await delete_key(session, tg_id, commit=False)
await session.execute(delete(TemporaryData).where(TemporaryData.tg_id == tg_id))
await session.execute(delete(BlockedUser).where(BlockedUser.tg_id == tg_id))
await session.execute(delete(User).where(User.tg_id == tg_id))