Spaces:
Running
Running
File size: 22,348 Bytes
a290b8b 644ba85 a290b8b 644ba85 a290b8b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 | # Загрузка данных и кеширование
Этот документ описывает текущую реализацию загрузки данных и кеширования во 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` |
|