Spaces:
Running
Running
File size: 15,434 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 | # Data loading and caching
This document describes the current implementation of data loading and caching in the
HiveTrace Guardrail Leaderboard frontend.
The implementation has three goals:
- keep the private Hugging Face token on the SvelteKit server;
- avoid downloading and adapting unchanged bucket payloads;
- make repeated client-side navigation fast while providing a predictable data freshness window.
## Architecture
```text
Browser
-> SvelteKit page or __data.json request
-> Hugging Face Space Node process
-> private Hugging Face bucket
-> latest/manifest.json
-> payload files referenced by the manifest
```
The browser never reads the private bucket directly. All bucket requests are made by the
SvelteKit server with the server-only `HF_TOKEN` environment variable.
## Data source
The bucket is configured with these runtime variables:
```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`, and `HF_BUCKET_REQUEST_RETRIES` are optional.
The values above are their defaults.
`HF_BUCKET_CACHE_TTL_MS` still controls the regular in-memory manifest cache and the direct
Tools snapshot helper. The main Ranking, Details, and Visualizations page-data flow explicitly
refreshes the manifest after the browser cache misses, so this variable does not add another
freshness delay to normal client-side page transitions.
## Manifest as the version pointer
`latest/manifest.json` is the single version pointer used by the frontend. Its important fields
are:
- `snapshot_id`: the identity of the published dataset;
- `files`: paths to all payload files;
- `hashes`: SHA-256 hashes of the payload files;
- `schema_version`: the bucket contract version;
- model, group, and dataset counts used for validation.
The frontend decides whether payload data changed by comparing `snapshot_id`. A new publish
must always use a new, unique `snapshot_id`.
Changing payload files or hashes without changing `snapshot_id` is not supported. In that case,
the frontend considers the snapshot unchanged and can continue using the old in-memory payload
until the Space process restarts.
## Routes and payloads
| Route | Server loader | Bucket data |
| -------------- | --------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `/` | `getHfBucketRankingState()` | catalog, leaderboard, and details matrix |
| `/details` | `getHfBucketDetailsState()` | catalog, leaderboard, and details matrix |
| `/tools` | `getHfBucketToolsState()` | catalog, leaderboard, drilldown index, radar, scatter, heatmap, grouped bars, Pareto, performance, and robustness |
| `/methodology` | no bucket page loader | no bucket payload |
Ranking also uses the details matrix because the `partial` model status is derived from
`metrics_evaluated_samples < sample_count`.
The root `+layout.server.ts` reads the manifest separately to expose public bucket status such
as snapshot date and model count. That status uses the regular manifest cache. It is metadata
for the layout and does not control page-data freshness.
## Client-side navigation
SvelteKit intercepts internal menu links and requests route data through endpoints such as:
```text
/__data.json
/details/__data.json
/tools/__data.json
```
Localized variants are recognized as well, for example `/ru/details/__data.json`.
The response optimization in `src/hooks.server.ts` applies only to these three page-data
routes. It does not apply to Methodology, static assets, errors, or arbitrary API responses.
### Browser cache
Successful page-data responses use:
```http
Cache-Control: private, max-age=300
```
This means:
- each browser can reuse a route response for five minutes;
- the response is not a public CDN or shared-proxy cache entry;
- no `stale-while-revalidate` window is used;
- after five minutes, the next navigation must contact the SvelteKit server before rendering
that route.
The cache is per response URL, including SvelteKit query parameters. Ranking, Details, and
Visualizations therefore have independent browser cache entries.
An already rendered page does not update itself. Fresh data is applied on a later navigation or
full page reload.
### Gzip compression
The same hook compresses a page-data response when all conditions are true:
- the response is successful and has a body;
- its declared size is at least 1,024 bytes;
- the client accepts `gzip`.
The compressed response contains:
```http
Content-Encoding: gzip
Vary: Accept-Encoding
```
`Content-Length` is removed because the compressed body is streamed. Clients without gzip
support receive the original body with the same five-minute private cache policy.
Approximate measured sizes for the current dataset are:
| Route data | Uncompressed | Gzip |
| -------------- | -----------: | -----: |
| Ranking | 171 KB | 58 KB |
| Details | 643 KB | 158 KB |
| Visualizations | 709 KB | 122 KB |
These sizes depend on the bucket contents and will change as models and datasets are added.
## Server request flow
When the browser does not have a fresh page-data response, the server follows this flow:
1. The page loader calls the corresponding Ranking, Details, or Tools state function.
2. The state function calls `fetchBucketRankingSnapshot({ refreshManifest: true })`.
3. `refreshManifest: true` bypasses the regular manifest TTL and reads the current small
`manifest.json` from Hugging Face.
4. If the returned `snapshot_id` matches the in-memory ranking snapshot, catalog and leaderboard
are reused without another payload download.
5. If `snapshot_id` changed, catalog and leaderboard are downloaded in parallel, their hashes
and schemas are validated, and the ranking snapshot is replaced.
6. Route-specific data is reused or rebuilt for the same new `snapshot_id`.
7. SvelteKit serializes the adapted route data, and the server hook applies the private cache
header and gzip compression.
The important distinction between the snapshot options is:
- `refreshManifest: true` always checks the manifest but reuses payloads when `snapshot_id` is
unchanged;
- `forceRefresh: true` also bypasses payload reuse and rebuilds the ranking snapshot.
Normal page transitions use `refreshManifest`, not `forceRefresh`.
## In-memory caches
All server caches are module-level memory in the running Node process. They are private to a
single Space replica and are cleared when the container restarts, sleeps, or is redeployed.
### Manifest cache
`src/lib/server/hf-bucket/cache.ts` stores:
- the parsed manifest;
- the time it was fetched;
- its expiration time;
- one shared in-flight manifest request.
Regular callers use `HF_BUCKET_CACHE_TTL_MS`, with a five-minute default. Page-data refreshes use
the forced manifest path described above. Concurrent forced checks join the same in-flight
request when they overlap.
### Ranking snapshot
The ranking snapshot contains the manifest, catalog, and leaderboard. It is cached by
`snapshot_id`.
Concurrent downloads of the same snapshot share one promise. In-flight requests are also keyed
by `snapshot_id`, so a request for a newly published snapshot does not accidentally reuse a
download for an older snapshot.
### Details snapshot
The details snapshot adds `details_matrix` to the ranking snapshot and is cached by
`snapshot_id`.
This cache is shared by Ranking and Details. Ranking needs the matrix for `partial` detection, so
opening Details after Ranking does not download and validate `details_matrix` a second time.
Concurrent requests for the same details snapshot also share one promise.
### Adapted Ranking and Details datasets
The UI-ready Ranking and Details objects are cached by `snapshot_id`. When the manifest is
unchanged, the server returns the already adapted object. A new snapshot is parsed, validated,
and adapted once per Node process.
### Visualizations snapshot and adapted Tools dataset
For a new snapshot, the visualization files are downloaded in parallel. The raw visualization
snapshot and the adapted Tools dataset are cached separately by `snapshot_id`.
The direct `getHfBucketToolsSnapshot()` helper retains its TTL-based fast path. The `/tools`
page uses the state path, which refreshes the manifest after a browser cache miss.
## Validation
Payload reuse is allowed only after the manifest version check. New payloads go through:
- JSON parsing;
- schema parsing;
- SHA-256 verification when a hash is present;
- manifest count checks;
- model, group, and dataset reference checks;
- matrix completeness and duplicate-pair checks;
- visualization reference checks.
A validation error is handled in the same way as another refresh failure.
## Data freshness guarantee
Assume a valid new snapshot is published at time `T` and the bucket remains reachable.
### Route response is already in the browser cache
The old route response can be used until its individual five-minute `max-age` expires. The first
navigation to that route after expiration reads the fresh manifest. If `snapshot_id` changed,
the same navigation waits for and receives the new payload.
Therefore, the expected guarantee is:
> No later than the first navigation after the route's five-minute browser cache expires, the
> user receives the new snapshot.
There is no additional stale response window.
### Route response is not in the browser cache
The navigation immediately contacts the server and checks the manifest. If the server already
has the new payload, it reuses it. Otherwise, the navigation waits while the new snapshot is
downloaded and adapted.
### Full page reload
The five-minute policy targets SvelteKit `__data.json` navigation responses. A normal full HTML
request is not covered by this page-data cache and runs the server page loader again.
### Exceptions
The five-minute expectation does not apply when:
- Hugging Face or the network is unavailable;
- the new payload fails parsing, hash checks, or validation;
- the publisher reused the old `snapshot_id`;
- the manifest was published before all referenced files became readable.
## Failure behavior
The cache is also a resilience mechanism:
- if a refresh fails and a usable route dataset exists, the server returns cached data;
- if a new manifest is readable but a new payload is invalid or unavailable, the route state is
returned as `stale` and the UI can show a warning;
- if no usable cache exists, the route returns an `unavailable` state with an empty fallback
dataset;
- simultaneous requests share in-flight work to avoid duplicate bucket downloads.
One implementation detail is worth noting: when a forced manifest read fails but an older
manifest is cached, the manifest layer can return that stale manifest. The existing route data
can then continue to be served. The current route state does not always propagate this specific
manifest fallback as a visible `stale` warning.
## Publishing a new snapshot
The producer must publish atomically from the frontend's point of view:
1. Generate all payload files.
2. Use immutable or snapshot-specific file paths where practical.
3. Calculate and write the SHA-256 hashes.
4. Upload every payload file and verify it is readable.
5. Create a manifest with a new unique `snapshot_id`, correct paths, hashes, counts, and schema
version.
6. Upload `latest/manifest.json` last.
Publishing the manifest last prevents the frontend from seeing a new version that references
files which are not available yet.
## Cold starts and replicas
After a Space restart or cold start, no in-memory snapshot exists. The first request must read
the manifest and all payloads required by that route. Later requests reuse the prepared data.
If the Space runs multiple replicas, each replica has its own memory cache and warms
independently. The browser cache remains local to each user.
## Operational checks
To inspect the page-data response headers, use an authenticated request to the Space:
```bash
curl -sS \
-H "Authorization: Bearer $HF_TOKEN" \
-H "Accept-Encoding: gzip" \
-D - \
-o /dev/null \
"https://<space-domain>/details/__data.json"
```
Expected headers include:
```http
Cache-Control: private, max-age=300
Content-Encoding: gzip
Vary: Accept-Encoding
```
`curl` does not reproduce the browser's navigation cache unless an explicit curl cache is used.
Repeated curl requests therefore reach the server and can trigger repeated manifest checks.
When verifying a publish, check all of the following:
- the manifest has a new `snapshot_id`;
- every referenced file exists;
- hashes match the published contents;
- the Space logs contain no bucket validation errors;
- the first route navigation after browser cache expiration shows the new snapshot.
## Implementation map
| Responsibility | File |
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| HTTP bucket client and server-only token | `src/lib/server/hf-bucket/client.ts` |
| Regular manifest cache and in-flight request | `src/lib/server/hf-bucket/cache.ts` |
| Ranking snapshot download and validation | `src/lib/server/hf-bucket/ranking-snapshot.ts` |
| Shared details snapshot | `src/lib/server/hf-bucket/details-snapshot.ts` |
| Visualization snapshot download | `src/lib/server/hf-bucket/tools-snapshot.ts` |
| Adapted Ranking cache | `src/lib/server/hf-bucket/ranking-cache.ts` |
| Adapted Details cache | `src/lib/server/hf-bucket/details-cache.ts` |
| Raw and adapted Tools caches | `src/lib/server/hf-bucket/tools-cache.ts` |
| Browser cache headers and gzip | `src/hooks.server.ts` |
| Root public bucket status | `src/routes/+layout.server.ts` |
| Route orchestration | `src/routes/+page.server.ts`, `src/routes/details/+page.server.ts`, `src/routes/tools/+page.server.ts` |
|