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` |