Spaces:
Running
Running
Anton Malykhin
fix: stabilize benchmark leaders, model benchmark sorting, and HF bucket reads
644ba85 | # Загрузка данных и кеширование | |
| Этот документ описывает текущую реализацию загрузки данных и кеширования во frontend | |
| HiveTrace Guardrail Leaderboard. | |
| У реализации три основные цели: | |
| - хранить приватный Hugging Face token только на сервере SvelteKit; | |
| - не скачивать и не адаптировать повторно неизменившиеся payload-файлы bucket; | |
| - ускорить повторные переходы между страницами и сохранить предсказуемое окно актуальности | |
| данных. | |
| ## Архитектура | |
| ```text | |
| Browser | |
| -> SvelteKit page или __data.json request | |
| -> Node process в Hugging Face Space | |
| -> private Hugging Face bucket | |
| -> latest/manifest.json | |
| -> payload-файлы, указанные в manifest | |
| ``` | |
| Браузер не обращается к приватному bucket напрямую. Все запросы в bucket выполняет сервер | |
| SvelteKit с помощью server-only переменной `HF_TOKEN`. | |
| ## Источник данных | |
| Bucket настраивается runtime-переменными: | |
| ```text | |
| HF_TOKEN=<private read token> | |
| HF_BUCKET_ID=hivetrace/leaderboard_frontend_v2 | |
| HF_BUCKET_PREFIX=latest | |
| HF_BUCKET_REQUEST_TIMEOUT_MS=60000 | |
| HF_BUCKET_REQUEST_RETRIES=2 | |
| HF_BUCKET_ENDPOINT=https://huggingface.co | |
| ``` | |
| `HF_BUCKET_ENDPOINT`, `HF_BUCKET_REQUEST_TIMEOUT_MS` и `HF_BUCKET_REQUEST_RETRIES` опциональны. | |
| Выше указаны их значения по умолчанию. | |
| `HF_BUCKET_CACHE_TTL_MS` по-прежнему управляет обычным in-memory кешем manifest и прямым helper | |
| Tools snapshot. Основной поток page data для Ranking, Details и Visualizations после промаха | |
| браузерного кеша явно обновляет manifest, поэтому эта переменная не добавляет еще одну задержку | |
| актуальности при обычных переходах по страницам. | |
| ## Manifest как указатель версии | |
| `latest/manifest.json` является единственным указателем версии для frontend. Основные поля: | |
| - `snapshot_id`: идентификатор опубликованного набора данных; | |
| - `files`: пути ко всем payload-файлам; | |
| - `hashes`: SHA-256 хеши payload-файлов; | |
| - `schema_version`: версия контракта bucket; | |
| - количества моделей, групп и датасетов для валидации. | |
| Frontend определяет изменение payload по `snapshot_id`. Каждая новая публикация обязана иметь | |
| новый уникальный `snapshot_id`. | |
| Изменение payload-файлов или хешей без изменения `snapshot_id` не поддерживается. В таком случае | |
| frontend считает snapshot прежним и может использовать старый in-memory payload до перезапуска | |
| процесса Space. | |
| ## Маршруты и payload-файлы | |
| | Маршрут | Server loader | Данные bucket | | |
| | -------------- | --------------------------- | -------------------------------------------------------------------------------------------------------------- | | |
| | `/` | `getHfBucketRankingState()` | catalog, leaderboard и details matrix | | |
| | `/details` | `getHfBucketDetailsState()` | catalog, leaderboard и details matrix | | |
| | `/tools` | `getHfBucketToolsState()` | catalog, leaderboard, drilldown index, radar, scatter, heatmap, grouped bars, Pareto, performance и robustness | | |
| | `/methodology` | нет bucket page loader | payload из bucket не используется | | |
| Ranking также использует details matrix, потому что статус модели `partial` вычисляется по | |
| условию `metrics_evaluated_samples < sample_count`. | |
| Корневой `+layout.server.ts` отдельно читает manifest и возвращает публичный статус bucket: | |
| дату snapshot, количество моделей и другие общие поля. Этот статус использует обычный кеш | |
| manifest. Он является метаданными layout и не управляет актуальностью page data. | |
| ## Клиентская навигация | |
| SvelteKit перехватывает внутренние ссылки главного меню и запрашивает данные маршрутов через | |
| endpoint вида: | |
| ```text | |
| /__data.json | |
| /details/__data.json | |
| /tools/__data.json | |
| ``` | |
| Локализованные варианты также распознаются, например `/ru/details/__data.json`. | |
| Оптимизация response в `src/hooks.server.ts` применяется только к этим трем page-data | |
| маршрутам. Она не применяется к Methodology, статическим файлам, ошибкам и произвольным API | |
| response. | |
| ### Браузерный кеш | |
| Успешный page-data response получает заголовок: | |
| ```http | |
| Cache-Control: private, max-age=300 | |
| ``` | |
| Это означает: | |
| - браузер может повторно использовать response маршрута в течение пяти минут; | |
| - response не попадает в общий CDN или shared proxy cache; | |
| - окно `stale-while-revalidate` отсутствует; | |
| - после пяти минут следующий переход обязан обратиться к серверу SvelteKit до отрисовки | |
| маршрута. | |
| Кеш привязан к URL response, включая внутренние query-параметры SvelteKit. Поэтому Ranking, | |
| Details и Visualizations имеют отдельные записи браузерного кеша. | |
| Уже открытая страница сама не обновляется. Новые данные применяются при следующем переходе или | |
| полной перезагрузке страницы. | |
| ### Gzip-сжатие | |
| Тот же hook сжимает page-data response, когда выполнены все условия: | |
| - response успешный и содержит body; | |
| - заявленный размер не меньше 1 024 bytes; | |
| - клиент поддерживает `gzip`. | |
| Сжатый response содержит: | |
| ```http | |
| Content-Encoding: gzip | |
| Vary: Accept-Encoding | |
| ``` | |
| `Content-Length` удаляется, потому что сжатый body передается потоком. Клиенты без поддержки | |
| gzip получают исходный body с тем же приватным кешем на пять минут. | |
| Примерные измеренные размеры для текущего набора данных: | |
| | Данные маршрута | Без сжатия | Gzip | | |
| | --------------- | ---------: | -----: | | |
| | Ranking | 171 KB | 58 KB | | |
| | Details | 643 KB | 158 KB | | |
| | Visualizations | 709 KB | 122 KB | | |
| Размеры зависят от содержимого bucket и будут меняться при добавлении моделей и датасетов. | |
| ## Поток server request | |
| Если в браузере нет свежего page-data response, сервер выполняет следующие шаги: | |
| 1. Page loader вызывает соответствующую state-функцию Ranking, Details или Tools. | |
| 2. State-функция вызывает `fetchBucketRankingSnapshot({ refreshManifest: true })`. | |
| 3. `refreshManifest: true` обходит обычный TTL manifest и читает актуальный небольшой | |
| `manifest.json` из Hugging Face. | |
| 4. Если полученный `snapshot_id` совпадает с in-memory ranking snapshot, catalog и leaderboard | |
| используются повторно без скачивания payload. | |
| 5. Если `snapshot_id` изменился, catalog и leaderboard скачиваются параллельно, проверяются их | |
| хеши и schema, после чего ranking snapshot заменяется. | |
| 6. Данные конкретного маршрута переиспользуются или пересобираются для нового `snapshot_id`. | |
| 7. SvelteKit сериализует адаптированные данные маршрута, а server hook добавляет приватный кеш и | |
| gzip-сжатие. | |
| Разница между параметрами snapshot: | |
| - `refreshManifest: true` всегда проверяет manifest, но переиспользует payload при неизменном | |
| `snapshot_id`; | |
| - `forceRefresh: true` также отключает переиспользование payload и пересобирает ranking snapshot. | |
| Обычные переходы по страницам используют `refreshManifest`, а не `forceRefresh`. | |
| ## In-memory кеши сервера | |
| Все серверные кеши хранятся в памяти работающего Node process. Они принадлежат одному replica | |
| Space и очищаются при перезапуске, засыпании контейнера или новом deploy. | |
| ### Кеш manifest | |
| `src/lib/server/hf-bucket/cache.ts` хранит: | |
| - распарсенный manifest; | |
| - время его получения; | |
| - время истечения кеша; | |
| - один общий in-flight request manifest. | |
| Обычные callers используют `HF_BUCKET_CACHE_TTL_MS`, по умолчанию пять минут. Page-data refresh | |
| использует принудительное чтение manifest, описанное выше. Одновременные принудительные проверки | |
| подключаются к одному in-flight request, если пересекаются по времени. | |
| ### Ranking snapshot | |
| Ranking snapshot содержит manifest, catalog и leaderboard. Он кешируется по `snapshot_id`. | |
| Одновременные загрузки одного snapshot используют общий promise. In-flight request также | |
| привязан к `snapshot_id`, поэтому запрос нового опубликованного snapshot не использует по ошибке | |
| загрузку предыдущего snapshot. | |
| ### Details snapshot | |
| Details snapshot добавляет `details_matrix` к ranking snapshot и кешируется по `snapshot_id`. | |
| Этот кеш общий для Ranking и Details. Ranking использует matrix для определения `partial`, поэтому | |
| после открытия Ranking страница Details не скачивает и не валидирует `details_matrix` повторно. | |
| Одновременные запросы одного details snapshot также используют общий promise. | |
| ### Адаптированные Ranking и Details | |
| Готовые для UI объекты Ranking и Details кешируются по `snapshot_id`. Если manifest не изменился, | |
| сервер возвращает уже адаптированный объект. Новый snapshot парсится, валидируется и адаптируется | |
| один раз на Node process. | |
| ### Visualizations snapshot и адаптированный Tools dataset | |
| Для нового snapshot файлы визуализаций скачиваются параллельно. Raw visualization snapshot и | |
| адаптированный Tools dataset кешируются отдельно по `snapshot_id`. | |
| Прямой helper `getHfBucketToolsSnapshot()` сохраняет свой быстрый путь на основе TTL. Страница | |
| `/tools` использует state-путь, который обновляет manifest после промаха браузерного кеша. | |
| ## Валидация | |
| Payload используется повторно только после проверки версии manifest. Новый payload проходит: | |
| - JSON parsing; | |
| - schema parsing; | |
| - проверку SHA-256 при наличии хеша; | |
| - сверку количеств из manifest; | |
| - проверку ссылок на модели, группы и датасеты; | |
| - проверку полноты matrix и дублирующихся пар; | |
| - проверку ссылок в данных визуализаций. | |
| Ошибка валидации обрабатывается так же, как другая ошибка обновления. | |
| ## Гарантия актуальности данных | |
| Предположим, что корректный новый snapshot опубликован в момент `T`, а bucket доступен. | |
| ### Response маршрута уже находится в браузерном кеше | |
| Старый response может использоваться до истечения его индивидуального `max-age` в пять минут. | |
| Первый переход на этот маршрут после истечения кеша читает свежий manifest. Если `snapshot_id` | |
| изменился, тот же переход ожидает загрузку нового payload и получает новые данные. | |
| Ожидаемая гарантия: | |
| > Не позднее первого перехода после истечения пятиминутного браузерного кеша маршрута | |
| > пользователь получает новый snapshot. | |
| Дополнительного окна выдачи stale response нет. | |
| ### Response маршрута отсутствует в браузерном кеше | |
| Переход сразу обращается к серверу и проверяет manifest. Если новый payload уже находится в | |
| памяти сервера, он используется повторно. Иначе переход ожидает скачивание и адаптацию нового | |
| snapshot. | |
| ### Полная перезагрузка страницы | |
| Пятиминутная политика применяется к SvelteKit navigation response `__data.json`. Обычный полный | |
| HTML request не покрывается этим page-data кешем и снова запускает server page loader. | |
| ### Исключения | |
| Пятиминутное ожидание неприменимо, если: | |
| - Hugging Face или сеть недоступны; | |
| - новый payload не проходит parsing, проверку хеша или валидацию; | |
| - publisher повторно использовал старый `snapshot_id`; | |
| - manifest опубликован раньше, чем стали доступны все указанные в нем файлы. | |
| ## Поведение при ошибках | |
| Кеш также обеспечивает устойчивость: | |
| - если refresh завершился ошибкой и готовые данные маршрута существуют, сервер возвращает кеш; | |
| - если новый manifest доступен, но новый payload невалиден или недоступен, route state становится | |
| `stale`, и UI может показать предупреждение; | |
| - если пригодного кеша нет, маршрут возвращает состояние `unavailable` с пустым fallback dataset; | |
| - одновременные запросы используют общую in-flight работу и не дублируют загрузку bucket. | |
| Важная деталь текущей реализации: если принудительное чтение manifest завершилось ошибкой, но в | |
| памяти есть предыдущий manifest, слой manifest может вернуть его как stale. После этого сервер | |
| продолжит выдавать предыдущие route data. Текущий route state не во всех случаях пробрасывает | |
| именно этот fallback manifest как видимое предупреждение `stale`. | |
| ## Публикация нового snapshot | |
| Producer должен выполнять публикацию атомарно с точки зрения frontend: | |
| 1. Сгенерировать все payload-файлы. | |
| 2. По возможности использовать immutable или snapshot-specific пути. | |
| 3. Рассчитать и записать SHA-256 хеши. | |
| 4. Загрузить каждый payload-файл и проверить, что он доступен для чтения. | |
| 5. Создать manifest с новым уникальным `snapshot_id`, корректными путями, хешами, количествами и | |
| версией schema. | |
| 6. Последним загрузить `latest/manifest.json`. | |
| Публикация manifest последним не позволяет frontend увидеть новую версию, которая ссылается на | |
| еще недоступные файлы. | |
| ## Cold start и replicas | |
| После перезапуска или cold start Space in-memory snapshot отсутствует. Первый request должен | |
| прочитать manifest и все payload-файлы, необходимые выбранному маршруту. Следующие запросы | |
| используют подготовленные данные повторно. | |
| Если Space работает с несколькими replicas, каждая имеет собственный кеш в памяти и прогревается | |
| независимо. Браузерный кеш остается локальным для каждого пользователя. | |
| ## Операционная проверка | |
| Для проверки заголовков page-data response нужен авторизованный запрос к Space: | |
| ```bash | |
| curl -sS \ | |
| -H "Authorization: Bearer $HF_TOKEN" \ | |
| -H "Accept-Encoding: gzip" \ | |
| -D - \ | |
| -o /dev/null \ | |
| "https://<space-domain>/details/__data.json" | |
| ``` | |
| Ожидаемые заголовки: | |
| ```http | |
| Cache-Control: private, max-age=300 | |
| Content-Encoding: gzip | |
| Vary: Accept-Encoding | |
| ``` | |
| `curl` не воспроизводит navigation cache браузера без отдельной настройки собственного кеша. | |
| Поэтому повторные curl-запросы доходят до сервера и могут повторно запускать проверку manifest. | |
| При проверке новой публикации нужно убедиться, что: | |
| - manifest содержит новый `snapshot_id`; | |
| - каждый указанный файл существует; | |
| - хеши соответствуют опубликованному содержимому; | |
| - в логах Space нет ошибок валидации bucket; | |
| - первый переход после истечения браузерного кеша показывает новый snapshot. | |
| ## Карта реализации | |
| | Ответственность | Файл | | |
| | ---------------------------------------- | ------------------------------------------------------------------------------------------------------ | | |
| | HTTP client bucket и server-only token | `src/lib/server/hf-bucket/client.ts` | | |
| | Обычный кеш manifest и in-flight request | `src/lib/server/hf-bucket/cache.ts` | | |
| | Загрузка и валидация Ranking snapshot | `src/lib/server/hf-bucket/ranking-snapshot.ts` | | |
| | Общий Details snapshot | `src/lib/server/hf-bucket/details-snapshot.ts` | | |
| | Загрузка Visualization snapshot | `src/lib/server/hf-bucket/tools-snapshot.ts` | | |
| | Кеш адаптированного Ranking | `src/lib/server/hf-bucket/ranking-cache.ts` | | |
| | Кеш адаптированного Details | `src/lib/server/hf-bucket/details-cache.ts` | | |
| | Raw и адаптированный кеш Tools | `src/lib/server/hf-bucket/tools-cache.ts` | | |
| | Заголовки браузерного кеша и gzip | `src/hooks.server.ts` | | |
| | Публичный статус bucket в root layout | `src/routes/+layout.server.ts` | | |
| | Оркестрация маршрутов | `src/routes/+page.server.ts`, `src/routes/details/+page.server.ts`, `src/routes/tools/+page.server.ts` | | |