topics+authors-reimplemented-cache
All checks were successful
Deploy on push / deploy (push) Successful in 5s
All checks were successful
Deploy on push / deploy (push) Successful in 5s
This commit is contained in:
@@ -1,18 +1,15 @@
|
||||
import time
|
||||
|
||||
from sqlalchemy import func, select, text
|
||||
from sqlalchemy import desc, select, text
|
||||
|
||||
from cache.cache import (
|
||||
cache_topic,
|
||||
cached_query,
|
||||
get_cached_topic_authors,
|
||||
get_cached_topic_by_slug,
|
||||
get_cached_topic_followers,
|
||||
redis_operation,
|
||||
invalidate_cache_by_prefix,
|
||||
)
|
||||
from cache.memorycache import cache_region
|
||||
from orm.author import Author
|
||||
from orm.shout import Shout, ShoutTopic
|
||||
from orm.topic import Topic, TopicFollower
|
||||
from orm.topic import Topic
|
||||
from resolvers.stat import get_with_stat
|
||||
from services.auth import login_required
|
||||
from services.db import local_session
|
||||
@@ -30,42 +27,26 @@ async def get_all_topics():
|
||||
Returns:
|
||||
list: Список всех тем без статистики
|
||||
"""
|
||||
# Пытаемся получить данные из кеша
|
||||
cached_topics = await redis_operation("GET", "topics:all:basic")
|
||||
cache_key = "topics:all:basic"
|
||||
|
||||
if cached_topics:
|
||||
logger.debug("Используем кешированные базовые данные о темах из Redis")
|
||||
try:
|
||||
import json
|
||||
# Функция для получения всех тем из БД
|
||||
async def fetch_all_topics():
|
||||
logger.debug("Получаем список всех тем из БД и кешируем результат")
|
||||
|
||||
return json.loads(cached_topics)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при десериализации тем из Redis: {e}")
|
||||
with local_session() as session:
|
||||
# Запрос на получение базовой информации о темах
|
||||
topics_query = select(Topic)
|
||||
topics = session.execute(topics_query).scalars().all()
|
||||
|
||||
# Если в кеше нет данных, выполняем запрос в БД
|
||||
logger.debug("Получаем список всех тем из БД и кешируем результат")
|
||||
# Преобразуем темы в словари
|
||||
return [topic.dict() for topic in topics]
|
||||
|
||||
with local_session() as session:
|
||||
# Запрос на получение базовой информации о темах
|
||||
topics_query = select(Topic)
|
||||
topics = session.execute(topics_query).scalars().all()
|
||||
|
||||
# Преобразуем темы в словари
|
||||
result = [topic.dict() for topic in topics]
|
||||
|
||||
# Кешируем результат в Redis без TTL (будет обновляться только при изменениях)
|
||||
try:
|
||||
import json
|
||||
|
||||
await redis_operation("SET", "topics:all:basic", json.dumps(result))
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при кешировании тем в Redis: {e}")
|
||||
|
||||
return result
|
||||
# Используем универсальную функцию для кеширования запросов
|
||||
return await cached_query(cache_key, fetch_all_topics)
|
||||
|
||||
|
||||
# Вспомогательная функция для получения тем со статистикой с пагинацией
|
||||
async def get_topics_with_stats(limit=100, offset=0, community_id=None):
|
||||
async def get_topics_with_stats(limit=100, offset=0, community_id=None, by=None):
|
||||
"""
|
||||
Получает темы со статистикой с пагинацией.
|
||||
|
||||
@@ -73,105 +54,140 @@ async def get_topics_with_stats(limit=100, offset=0, community_id=None):
|
||||
limit: Максимальное количество возвращаемых тем
|
||||
offset: Смещение для пагинации
|
||||
community_id: Опциональный ID сообщества для фильтрации
|
||||
by: Опциональный параметр сортировки
|
||||
|
||||
Returns:
|
||||
list: Список тем с их статистикой
|
||||
"""
|
||||
# Формируем ключ кеша с учетом параметров
|
||||
cache_key = f"topics:stats:limit={limit}:offset={offset}"
|
||||
if community_id:
|
||||
cache_key += f":community={community_id}"
|
||||
# Формируем ключ кеша с помощью универсальной функции
|
||||
cache_key = f"topics:stats:limit={limit}:offset={offset}:community_id={community_id}"
|
||||
|
||||
# Пытаемся получить данные из кеша
|
||||
cached_topics = await redis_operation("GET", cache_key)
|
||||
# Функция для получения тем из БД
|
||||
async def fetch_topics_with_stats():
|
||||
logger.debug(f"Выполняем запрос на получение тем со статистикой: limit={limit}, offset={offset}")
|
||||
|
||||
if cached_topics:
|
||||
logger.debug(f"Используем кешированные данные о темах из Redis: {cache_key}")
|
||||
try:
|
||||
import json
|
||||
with local_session() as session:
|
||||
# Базовый запрос для получения тем
|
||||
base_query = select(Topic)
|
||||
|
||||
return json.loads(cached_topics)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при десериализации тем из Redis: {e}")
|
||||
# Добавляем фильтр по сообществу, если указан
|
||||
if community_id:
|
||||
base_query = base_query.where(Topic.community == community_id)
|
||||
|
||||
# Если в кеше нет данных, выполняем оптимизированный запрос
|
||||
logger.debug(f"Выполняем запрос на получение тем со статистикой: limit={limit}, offset={offset}")
|
||||
# Применяем сортировку на основе параметра by
|
||||
if by:
|
||||
if isinstance(by, dict):
|
||||
# Обработка словаря параметров сортировки
|
||||
for field, direction in by.items():
|
||||
column = getattr(Topic, field, None)
|
||||
if column:
|
||||
if direction.lower() == "desc":
|
||||
base_query = base_query.order_by(desc(column))
|
||||
else:
|
||||
base_query = base_query.order_by(column)
|
||||
elif by == "popular":
|
||||
# Сортировка по популярности (количеству публикаций)
|
||||
# Примечание: это требует дополнительного запроса или подзапроса
|
||||
base_query = base_query.order_by(
|
||||
desc(Topic.id)
|
||||
) # Временно, нужно заменить на proper implementation
|
||||
else:
|
||||
# По умолчанию сортируем по ID в обратном порядке
|
||||
base_query = base_query.order_by(desc(Topic.id))
|
||||
else:
|
||||
# По умолчанию сортируем по ID в обратном порядке
|
||||
base_query = base_query.order_by(desc(Topic.id))
|
||||
|
||||
with local_session() as session:
|
||||
# Базовый запрос для получения тем
|
||||
base_query = select(Topic)
|
||||
# Применяем лимит и смещение
|
||||
base_query = base_query.limit(limit).offset(offset)
|
||||
|
||||
# Добавляем фильтр по сообществу, если указан
|
||||
if community_id:
|
||||
base_query = base_query.where(Topic.community == community_id)
|
||||
# Получаем темы
|
||||
topics = session.execute(base_query).scalars().all()
|
||||
topic_ids = [topic.id for topic in topics]
|
||||
|
||||
# Применяем лимит и смещение
|
||||
base_query = base_query.limit(limit).offset(offset)
|
||||
if not topic_ids:
|
||||
return []
|
||||
|
||||
# Получаем темы
|
||||
topics = session.execute(base_query).scalars().all()
|
||||
topic_ids = [topic.id for topic in topics]
|
||||
# Запрос на получение статистики по публикациям для выбранных тем
|
||||
shouts_stats_query = f"""
|
||||
SELECT st.topic, COUNT(DISTINCT s.id) as shouts_count
|
||||
FROM shout_topic st
|
||||
JOIN shout s ON st.shout = s.id AND s.deleted_at IS NULL
|
||||
WHERE st.topic IN ({",".join(map(str, topic_ids))})
|
||||
GROUP BY st.topic
|
||||
"""
|
||||
shouts_stats = {row[0]: row[1] for row in session.execute(text(shouts_stats_query))}
|
||||
|
||||
if not topic_ids:
|
||||
return []
|
||||
# Запрос на получение статистики по подписчикам для выбранных тем
|
||||
followers_stats_query = f"""
|
||||
SELECT topic, COUNT(DISTINCT follower) as followers_count
|
||||
FROM topic_followers
|
||||
WHERE topic IN ({",".join(map(str, topic_ids))})
|
||||
GROUP BY topic
|
||||
"""
|
||||
followers_stats = {row[0]: row[1] for row in session.execute(text(followers_stats_query))}
|
||||
|
||||
# Запрос на получение статистики по публикациям для выбранных тем
|
||||
shouts_stats_query = f"""
|
||||
SELECT st.topic, COUNT(DISTINCT s.id) as shouts_count
|
||||
FROM shout_topic st
|
||||
JOIN shout s ON st.shout = s.id AND s.deleted_at IS NULL
|
||||
WHERE st.topic IN ({",".join(map(str, topic_ids))})
|
||||
GROUP BY st.topic
|
||||
"""
|
||||
shouts_stats = {row[0]: row[1] for row in session.execute(text(shouts_stats_query))}
|
||||
# Формируем результат с добавлением статистики
|
||||
result = []
|
||||
for topic in topics:
|
||||
topic_dict = topic.dict()
|
||||
topic_dict["stat"] = {
|
||||
"shouts": shouts_stats.get(topic.id, 0),
|
||||
"followers": followers_stats.get(topic.id, 0),
|
||||
}
|
||||
result.append(topic_dict)
|
||||
|
||||
# Запрос на получение статистики по подписчикам для выбранных тем
|
||||
followers_stats_query = f"""
|
||||
SELECT topic, COUNT(DISTINCT follower) as followers_count
|
||||
FROM topic_followers
|
||||
WHERE topic IN ({",".join(map(str, topic_ids))})
|
||||
GROUP BY topic
|
||||
"""
|
||||
followers_stats = {row[0]: row[1] for row in session.execute(text(followers_stats_query))}
|
||||
# Кешируем каждую тему отдельно для использования в других функциях
|
||||
await cache_topic(topic_dict)
|
||||
|
||||
# Формируем результат с добавлением статистики
|
||||
result = []
|
||||
for topic in topics:
|
||||
topic_dict = topic.dict()
|
||||
topic_dict["stat"] = {
|
||||
"shouts": shouts_stats.get(topic.id, 0),
|
||||
"followers": followers_stats.get(topic.id, 0),
|
||||
}
|
||||
result.append(topic_dict)
|
||||
return result
|
||||
|
||||
# Кешируем каждую тему отдельно для использования в других функциях
|
||||
await cache_topic(topic_dict)
|
||||
|
||||
# Кешируем полный результат в Redis без TTL (будет обновляться только при изменениях)
|
||||
try:
|
||||
import json
|
||||
|
||||
await redis_operation("SET", cache_key, json.dumps(result))
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при кешировании тем в Redis: {e}")
|
||||
|
||||
return result
|
||||
# Используем универсальную функцию для кеширования запросов
|
||||
return await cached_query(cache_key, fetch_topics_with_stats)
|
||||
|
||||
|
||||
# Функция для инвалидации кеша тем
|
||||
async def invalidate_topics_cache():
|
||||
async def invalidate_topics_cache(topic_id=None):
|
||||
"""
|
||||
Инвалидирует все кеши тем при изменении данных.
|
||||
Инвалидирует кеши тем при изменении данных.
|
||||
|
||||
Args:
|
||||
topic_id: Опциональный ID темы для точечной инвалидации.
|
||||
Если не указан, инвалидируются все кеши тем.
|
||||
"""
|
||||
logger.debug("Инвалидация кеша тем")
|
||||
if topic_id:
|
||||
# Точечная инвалидация конкретной темы
|
||||
logger.debug(f"Инвалидация кеша для темы #{topic_id}")
|
||||
specific_keys = [
|
||||
f"topic:id:{topic_id}",
|
||||
f"topic:authors:{topic_id}",
|
||||
f"topic:followers:{topic_id}",
|
||||
f"topic_shouts_{topic_id}",
|
||||
]
|
||||
|
||||
# Получаем все ключи, начинающиеся с "topics:"
|
||||
topic_keys = await redis.execute("KEYS", "topics:*")
|
||||
# Получаем slug темы, если есть
|
||||
with local_session() as session:
|
||||
topic = session.query(Topic).filter(Topic.id == topic_id).first()
|
||||
if topic and topic.slug:
|
||||
specific_keys.append(f"topic:slug:{topic.slug}")
|
||||
|
||||
if topic_keys:
|
||||
# Удаляем все найденные ключи
|
||||
await redis.execute("DEL", *topic_keys)
|
||||
logger.debug(f"Удалено {len(topic_keys)} ключей кеша тем")
|
||||
# Удаляем конкретные ключи
|
||||
for key in specific_keys:
|
||||
try:
|
||||
await redis.execute("DEL", key)
|
||||
logger.debug(f"Удален ключ кеша {key}")
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении ключа {key}: {e}")
|
||||
|
||||
# Также ищем и удаляем ключи коллекций, содержащих данные об этой теме
|
||||
collection_keys = await redis.execute("KEYS", "topics:stats:*")
|
||||
if collection_keys:
|
||||
await redis.execute("DEL", *collection_keys)
|
||||
logger.debug(f"Удалено {len(collection_keys)} коллекционных ключей тем")
|
||||
else:
|
||||
# Общая инвалидация всех кешей тем
|
||||
logger.debug("Полная инвалидация кеша тем")
|
||||
await invalidate_cache_by_prefix("topics")
|
||||
|
||||
|
||||
# Запрос на получение всех тем
|
||||
@@ -188,23 +204,24 @@ async def get_topics_all(_, _info):
|
||||
|
||||
# Запрос на получение тем с пагинацией и статистикой
|
||||
@query.field("get_topics_paginated")
|
||||
async def get_topics_paginated(_, _info, limit=100, offset=0):
|
||||
async def get_topics_paginated(_, _info, limit=100, offset=0, by=None):
|
||||
"""
|
||||
Получает список тем с пагинацией и статистикой.
|
||||
|
||||
Args:
|
||||
limit: Максимальное количество возвращаемых тем
|
||||
offset: Смещение для пагинации
|
||||
by: Опциональные параметры сортировки
|
||||
|
||||
Returns:
|
||||
list: Список тем с их статистикой
|
||||
"""
|
||||
return await get_topics_with_stats(limit, offset)
|
||||
return await get_topics_with_stats(limit, offset, None, by)
|
||||
|
||||
|
||||
# Запрос на получение тем по сообществу
|
||||
@query.field("get_topics_by_community")
|
||||
async def get_topics_by_community(_, _info, community_id: int, limit=100, offset=0):
|
||||
async def get_topics_by_community(_, _info, community_id: int, limit=100, offset=0, by=None):
|
||||
"""
|
||||
Получает список тем, принадлежащих указанному сообществу с пагинацией и статистикой.
|
||||
|
||||
@@ -212,11 +229,12 @@ async def get_topics_by_community(_, _info, community_id: int, limit=100, offset
|
||||
community_id: ID сообщества
|
||||
limit: Максимальное количество возвращаемых тем
|
||||
offset: Смещение для пагинации
|
||||
by: Опциональные параметры сортировки
|
||||
|
||||
Returns:
|
||||
list: Список тем с их статистикой
|
||||
"""
|
||||
return await get_topics_with_stats(limit, offset, community_id)
|
||||
return await get_topics_with_stats(limit, offset, community_id, by)
|
||||
|
||||
|
||||
# Запрос на получение тем по автору
|
||||
@@ -268,14 +286,18 @@ async def update_topic(_, _info, topic_input):
|
||||
if not topic:
|
||||
return {"error": "topic not found"}
|
||||
else:
|
||||
old_slug = topic.slug
|
||||
Topic.update(topic, topic_input)
|
||||
session.add(topic)
|
||||
session.commit()
|
||||
|
||||
# Инвалидируем кеш всех тем и конкретной темы
|
||||
await invalidate_topics_cache()
|
||||
await redis.execute("DEL", f"topic:slug:{slug}")
|
||||
await redis.execute("DEL", f"topic:id:{topic.id}")
|
||||
# Инвалидируем кеш только для этой конкретной темы
|
||||
await invalidate_topics_cache(topic.id)
|
||||
|
||||
# Если slug изменился, удаляем старый ключ
|
||||
if old_slug != topic.slug:
|
||||
await redis.execute("DEL", f"topic:slug:{old_slug}")
|
||||
logger.debug(f"Удален ключ кеша для старого slug: {old_slug}")
|
||||
|
||||
return {"topic": topic}
|
||||
|
||||
|
Reference in New Issue
Block a user