Кэш: по интервалу таблица перечитывается всегда
Поле modified у публичной ссылки Яндекса может «застыть» (кэш на стороне Диска), и проверка «modified не изменился → не скачиваем» держала старые данные до неопределённого момента: стенд показывал прежнее расписание, хотя в таблице правки уже были. Теперь на каждое обновление (и по интервалу, и по кнопке) таблица скачивается заново; modified только показывается в админке. Плюс к этому: - при старте пишем в лог предупреждение, если ссылка в настройках отличается от SCHEDULE_LINK из окружения (именно так терялось время в прошлый раз: на стенде в настройках была сохранена другая ссылка); - на странице админки у поля ссылки пояснение, что значение хранится в настройках стенда и имеет приоритет над .env.
This commit is contained in:
@@ -99,8 +99,8 @@ nginx (https://schedule.our-place.ru) → 127.0.0.1:8090
|
||||
ошибка, число групп);
|
||||
- **вставить новую ссылку**: она проверяется скачиванием и разбором таблицы
|
||||
до сохранения — нерабочая не применится, данные на сайте не пострадают;
|
||||
- **принудительно обновить расписание** — вне интервала и без проверки
|
||||
«таблица не менялась» (то же, что делает фоновое обновление, но сейчас);
|
||||
- **принудительно обновить расписание** — вне интервала, не дожидаясь
|
||||
фонового обновления (интервал и так перечитывает таблицу целиком);
|
||||
- **задать интервал обновления** в минутах (`1…1440`).
|
||||
|
||||
Как это устроено:
|
||||
@@ -128,7 +128,7 @@ src/
|
||||
source.py загрузка файла по публичной ссылке Я.Диска
|
||||
lessons.py разбор ячеек занятий (общий конвейер ролей)
|
||||
parsing.py листы → расписание, недели, индексы преподавателей/аудиторий
|
||||
cache.py ScheduleCache: блокировка, интервал, modified, stale-while-error
|
||||
cache.py ScheduleCache: блокировка, интервал, stale-while-error
|
||||
settings.py хранилище настроек админки (ссылка, интервал) в JSON-файле
|
||||
errors.py ScheduleError
|
||||
frontend/
|
||||
@@ -152,7 +152,7 @@ tests/
|
||||
test_lessons.py все форматы ячеек
|
||||
test_xlsx.py чтение книги
|
||||
test_parsing.py разбор листов, недели, индексы
|
||||
test_api.py API и поведение кэша (TTL/modified/stale, 404/400/502)
|
||||
test_api.py API и поведение кэша (интервал/stale, 404/400/502)
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
@@ -67,6 +67,13 @@ def default_settings() -> SettingsStore:
|
||||
|
||||
def default_cache(settings: SettingsStore | None = None) -> ScheduleCache:
|
||||
settings = settings or default_settings()
|
||||
if SCHEDULE_LINK and settings.link and settings.link != SCHEDULE_LINK:
|
||||
log.warning(
|
||||
"Ссылка в настройках (%s) отличается от SCHEDULE_LINK из окружения (%s); "
|
||||
"работаем по ссылке из настроек — её видно и можно поменять в админке",
|
||||
settings.link,
|
||||
SCHEDULE_LINK,
|
||||
)
|
||||
return ScheduleCache(settings=settings, retry_after=RETRY_AFTER)
|
||||
|
||||
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
- однопоточная перезагрузка (lock): параллельные запросы во время загрузки
|
||||
ждут один download вместо N скачиваний;
|
||||
- TTL (интервал обновления), а также проверка modified таблицы: если таблица
|
||||
не менялась, скачивание не повторяется;
|
||||
- интервал обновления: по его истечении таблица перечитывается целиком
|
||||
(поле modified у публичной ссылки может «застыть», поэтому на него не
|
||||
опираемся — оно только показывается в админке);
|
||||
- stale-while-error: если таблица перестала скачиваться, отдаём последнее
|
||||
удачное состояние и повторяем попытку не чаще, чем раз в retry_after секунд
|
||||
(вместо скачивания на каждый запрос);
|
||||
@@ -208,16 +209,10 @@ class ScheduleCache:
|
||||
if not self._link:
|
||||
self._fail(ScheduleError(NO_LINK_ERROR), now)
|
||||
return
|
||||
# Обновление интервала всегда перечитывает таблицу: поле modified у
|
||||
# публичной ссылки может «застыть» (кэш на стороне Диска), и тогда
|
||||
# проверка «таблица не менялась» держала бы старые данные бесконечно.
|
||||
modified = fetch_table_modified(self._link)
|
||||
if (
|
||||
self._data is not None
|
||||
and modified is not None
|
||||
and modified == self._modified
|
||||
):
|
||||
# таблица не менялась — источник доступен, данные актуальны
|
||||
self._loaded_at = now
|
||||
self._error = None
|
||||
return
|
||||
try:
|
||||
self._load(now, modified)
|
||||
except ScheduleError as exc:
|
||||
|
||||
@@ -80,7 +80,8 @@
|
||||
</div>
|
||||
<button class="admin-btn" id="save-link" type="button">Сохранить ссылку</button>
|
||||
</div>
|
||||
<div class="admin-hint">Ссылка проверяется скачиванием: если не открывается — не сохранится, данные на сайте останутся прежними.</div>
|
||||
<div class="admin-hint">Ссылка проверяется скачиванием: если не открывается — не сохранится, данные на сайте останутся прежними.
|
||||
Это значение хранится в настройках стенда и <b>имеет приоритет над .env</b> — именно по нему сайт берёт расписание.</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-card">
|
||||
|
||||
@@ -155,11 +155,16 @@ def test_cache_no_repeated_download(client, cache):
|
||||
assert cache.calls["download"] == 1
|
||||
|
||||
|
||||
def test_cache_modified_unchanged_no_download(cache):
|
||||
cache._ttl = 0 # даже с истёкшим TTL
|
||||
def test_interval_refresh_always_downloads(cache):
|
||||
"""По интервалу таблица перечитывается всегда.
|
||||
|
||||
Поле modified у публичной ссылки может «застыть», поэтому на него не
|
||||
опираемся: иначе стенд показывал бы старые данные бесконечно.
|
||||
"""
|
||||
cache._ttl = 0
|
||||
cache.get()
|
||||
cache.get()
|
||||
assert cache.calls["download"] == 1
|
||||
assert cache.calls["download"] == 2 # оба обновления по интервалу скачали таблицу
|
||||
assert cache.calls["modified"] >= 2
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user