Многие разработчики, столкнувшись с задачей построения еженедельного рейтинга, сразу тянутся к планировщику задач: cron, Celery beat или встроенному шедулеру. Но если приложение небольшое и работает на Flask с SQLite, можно обойтись без дополнительных сущностей. В этой статье разберём, как организовать еженедельный рейтинг (например, «дежурство недели») без крона и без создания новых таблиц, но с учётом подводных камней.
Почему не нужен крон
Крон полезен, когда нужно выполнять тяжёлые фоновые задачи по расписанию: рассылки, очистку данных, пересчёт сложных агрегатов. Для простого рейтинга, который обновляется раз в неделю, достаточно вычислять его «на лету» при запросе или кэшировать результат в памяти процесса. Это упрощает архитектуру: не нужно настраивать отдельный сервис, следить за его доступностью и обрабатывать ошибки планировщика. Кроме того, при использовании SQLite и Flask встроенный кэш (например, на основе functools.lru_cache или простого словаря) позволяет избежать повторных тяжёлых запросов.
Схема данных без новых таблиц
Предположим, у нас уже есть таблица с событиями или действиями пользователей, где каждая запись содержит идентификатор пользователя, тип события и временную метку. Для еженедельного рейтинга нам нужно агрегировать эти события за последние 7 дней (или за календарную неделю). Вместо создания отдельной таблицы для хранения рейтинга можно выполнять SQL-запрос с группировкой и сортировкой непосредственно при обращении к странице рейтинга. Если нагрузка высока, результат можно закэшировать на несколько минут или до конца текущей недели.
Пример запроса для SQLite:
- SELECT user_id, COUNT(*) as score FROM events WHERE timestamp >= date('now', '-7 days') GROUP BY user_id ORDER BY score DESC LIMIT 10;
Такой запрос вернёт топ-10 пользователей за последнюю неделю. Важно учитывать, что в SQLite функции работы с датами могут отличаться от других СУБД, поэтому убедитесь, что временные метки хранятся в формате, поддерживаемом функциями date() и datetime().
Кэширование и обновление
Если рейтинг запрашивается часто, стоит задуматься о кэшировании. Простейший вариант — хранить результат в глобальной переменной или в атрибуте приложения и обновлять его раз в час или при наступлении нового дня. Однако при использовании нескольких воркеров (например, Gunicorn с несколькими процессами) кэш в памяти каждого процесса будет независимым, что может привести к рассинхронизации. В таких случаях лучше использовать внешний кэш (Redis, Memcached) или вычислять рейтинг при каждом запросе, если база данных невелика.
Ещё один подход — материализованное представление (materialized view), но в SQLite его нет из коробки. Можно создать таблицу-кэш и обновлять её по событию (например, при добавлении нового действия проверять, не пора ли пересчитать рейтинг). Но это уже усложнение, которое не всегда оправдано.
Типичные ошибки и как их избежать
- Неправильная работа с часовыми поясами. Если сервер и пользователи находятся в разных часовых поясах, границы недели могут смещаться. Храните время в UTC и при необходимости конвертируйте для отображения.
- Игнорирование индексов. Запрос с фильтрацией по дате будет медленным без индекса на столбце timestamp. Добавьте индекс, если его нет.
- Использование cron без необходимости. Как упоминалось, для простых случаев крон только добавляет сложности. Оцените, действительно ли нужен фоновый пересчёт, или можно обойтись вычислением по запросу.
- Отсутствие ограничения на количество записей. Всегда используйте LIMIT, чтобы не перегружать страницу и не гонять лишние данные.
- Забыли про пустые результаты. Если за неделю не было событий, рейтинг будет пустым — предусмотрите заглушку в интерфейсе.
Следуя этим рекомендациям, вы сможете реализовать еженедельный рейтинг на Flask и SQLite без лишних сущностей и с минимальными рисками. Главное — понимать, что простое решение часто оказывается надёжнее и легче в поддержке, чем нагромождение планировщиков и таблиц.