follow-cache-invalidation-fix
All checks were successful
Deploy on push / deploy (push) Successful in 3m18s
All checks were successful
Deploy on push / deploy (push) Successful in 3m18s
This commit is contained in:
@@ -52,6 +52,14 @@ async def follow(
|
||||
follower_id = follower_dict.get("id")
|
||||
logger.debug(f"follower_id: {follower_id}")
|
||||
|
||||
# ✅ КРИТИЧНО: Инвалидируем кеш В САМОМ НАЧАЛЕ, ДО любых операций
|
||||
# чтобы предотвратить чтение старых данных при последующей перезагрузке
|
||||
entity_type = what.lower()
|
||||
cache_key_pattern = f"author:follows-{entity_type}s:{follower_id}"
|
||||
await redis.execute("DEL", cache_key_pattern)
|
||||
await redis.execute("DEL", f"author:id:{follower_id}")
|
||||
logger.debug(f"Инвалидирован кеш подписок В НАЧАЛЕ операции: {cache_key_pattern}")
|
||||
|
||||
entity_classes = {
|
||||
"AUTHOR": (Author, AuthorFollower, get_cached_follower_authors, cache_author),
|
||||
"TOPIC": (Topic, TopicFollower, get_cached_follower_topics, cache_topic),
|
||||
@@ -68,6 +76,10 @@ async def follow(
|
||||
follows: list[dict[str, Any]] = []
|
||||
error: str | None = None
|
||||
|
||||
# ✅ Сохраняем entity_id и error вне сессии для использования после её закрытия
|
||||
entity_id_result: int | None = None
|
||||
error_result: str | None = None
|
||||
|
||||
try:
|
||||
logger.debug("Попытка получить сущность из базы данных")
|
||||
with local_session() as session:
|
||||
@@ -110,15 +122,10 @@ async def follow(
|
||||
.first()
|
||||
)
|
||||
|
||||
# 🔧 ИСПРАВЛЕНИЕ: Инвалидируем кэш ДО проверки existing_sub,
|
||||
# чтобы всегда возвращать актуальный список подписок (даже при ошибке "already following")
|
||||
cache_key_pattern = f"author:follows-{entity_type}s:{follower_id}"
|
||||
await redis.execute("DEL", cache_key_pattern)
|
||||
logger.debug(f"Инвалидирован кэш подписок: {cache_key_pattern}")
|
||||
|
||||
if existing_sub:
|
||||
logger.info(f"Пользователь {follower_id} уже подписан на {what.lower()} с ID {entity_id}")
|
||||
error = "already following"
|
||||
error_result = "already following"
|
||||
# ✅ КРИТИЧНО: Не делаем return - продолжаем для получения списка подписок
|
||||
else:
|
||||
logger.debug("Добавление новой записи в базу данных")
|
||||
sub = follower_class(follower=follower_id, **{entity_field: entity_id})
|
||||
@@ -127,37 +134,36 @@ async def follow(
|
||||
session.commit()
|
||||
logger.info(f"Пользователь {follower_id} подписался на {what.lower()} с ID {entity_id}")
|
||||
|
||||
if cache_method:
|
||||
logger.debug("Обновление кэша сущности")
|
||||
await cache_method(entity_dict)
|
||||
if cache_method:
|
||||
logger.debug("Обновление кэша сущности")
|
||||
await cache_method(entity_dict)
|
||||
|
||||
if what == "AUTHOR" and not existing_sub:
|
||||
logger.debug("Отправка уведомления автору о подписке")
|
||||
if isinstance(follower_dict, dict) and isinstance(entity_id, int):
|
||||
# Получаем ID созданной записи подписки
|
||||
subscription_id = getattr(sub, "id", None) if "sub" in locals() else None
|
||||
await notify_follower(
|
||||
follower=follower_dict,
|
||||
author_id=entity_id,
|
||||
action="follow",
|
||||
subscription_id=subscription_id,
|
||||
)
|
||||
if what == "AUTHOR":
|
||||
logger.debug("Отправка уведомления автору о подписке")
|
||||
if isinstance(follower_dict, dict) and isinstance(entity_id, int):
|
||||
# Получаем ID созданной записи подписки
|
||||
subscription_id = getattr(sub, "id", None) if "sub" in locals() else None
|
||||
await notify_follower(
|
||||
follower=follower_dict,
|
||||
author_id=entity_id,
|
||||
action="follow",
|
||||
subscription_id=subscription_id,
|
||||
)
|
||||
|
||||
# Инвалидируем кеш статистики авторов для обновления счетчиков подписчиков
|
||||
logger.debug("Инвалидируем кеш статистики авторов")
|
||||
await invalidate_authors_cache(entity_id)
|
||||
# Инвалидируем кеш статистики авторов для обновления счетчиков подписчиков
|
||||
logger.debug("Инвалидируем кеш статистики авторов")
|
||||
await invalidate_authors_cache(entity_id)
|
||||
|
||||
# ✅ КРИТИЧНО: Также инвалидируем кеш полных данных для корректной загрузки при рефреше
|
||||
# Это гарантирует, что после рефреша клиент получит актуальные данные из БД
|
||||
await redis.execute("DEL", f"author:id:{follower_id}")
|
||||
logger.debug(f"Инвалидирован кеш полных данных пользователя: author:id:{follower_id}")
|
||||
entity_id_result = entity_id
|
||||
|
||||
# Всегда получаем актуальный список подписок для возврата клиенту
|
||||
# ✅ Получаем актуальный список подписок для возврата клиенту
|
||||
# Кеш уже инвалидирован в начале функции, поэтому get_cached_follows_method
|
||||
# вернет свежие данные из БД
|
||||
if get_cached_follows_method and isinstance(follower_id, int):
|
||||
logger.debug("Получение актуального списка подписок из кэша")
|
||||
logger.debug("Получение актуального списка подписок после закрытия сессии")
|
||||
existing_follows = await get_cached_follows_method(follower_id)
|
||||
logger.debug(
|
||||
f"Получено подписок: {len(existing_follows)}, содержит target={entity_id in [f.get('id') for f in existing_follows] if existing_follows else False}"
|
||||
f"Получено подписок: {len(existing_follows)}, содержит target={entity_id_result in [f.get('id') for f in existing_follows] if existing_follows else False}"
|
||||
)
|
||||
|
||||
# Если это авторы, получаем безопасную версию
|
||||
@@ -181,7 +187,7 @@ async def follow(
|
||||
|
||||
logger.debug(f"Актуальный список подписок получен: {len(follows)} элементов")
|
||||
|
||||
return {f"{entity_type}s": follows, "error": error}
|
||||
return {f"{entity_type}s": follows, "error": error_result}
|
||||
|
||||
except Exception as exc:
|
||||
logger.exception("Произошла ошибка в функции 'follow'")
|
||||
@@ -207,6 +213,13 @@ async def unfollow(
|
||||
follower_id = follower_dict.get("id")
|
||||
logger.debug(f"follower_id: {follower_id}")
|
||||
|
||||
# ✅ КРИТИЧНО: Инвалидируем кеш В САМОМ НАЧАЛЕ, ДО любых операций
|
||||
entity_type = what.lower()
|
||||
cache_key_pattern = f"author:follows-{entity_type}s:{follower_id}"
|
||||
await redis.execute("DEL", cache_key_pattern)
|
||||
await redis.execute("DEL", f"author:id:{follower_id}")
|
||||
logger.debug(f"Инвалидирован кеш подписок В НАЧАЛЕ операции unfollow: {cache_key_pattern}")
|
||||
|
||||
entity_classes = {
|
||||
"AUTHOR": (Author, AuthorFollower, get_cached_follower_authors, cache_author),
|
||||
"TOPIC": (Topic, TopicFollower, get_cached_follower_topics, cache_topic),
|
||||
|
||||
Reference in New Issue
Block a user