Skip to content

Commit 67ea0b3

Browse files
fix(loader): label loader_cache with the block key, not the resolve chain
`loader_cache` and `resolver_latency` were labelled with `ctx.resolverId`, which carries the full resolve chain: Categories@sections.variants.1.value.5.sections.0.section.page SearchResults Global@sections.0.section.page Categories@sections.variants.1.value.2.jsonLD Every section position, of every variant, of every page becomes its own time series. That is an identifier, not a dimension. Measured on the production ClickHouse: distinct `loader` values, 3h window fila-store 3,684 todolivrooficial 3,478 montecarlo 2,209 farmrio 1,469 happybooksoficial 1,028 fleet total 21,849 Against the budget documented in our own o11y guide — >1,000 per site is forbidden as an alert dimension and >100 fleet-wide as a cross-fleet aggregation — five sites are over and the fleet figure is 218x over. The cost is real: `loader_cache` is 11,485,954 of 14,998,196 rows in otel_metrics_sum over 3h, i.e. 76.6% of the whole table, and that table holds 48.4 GB. For contrast, `cache_hit` covers 194 tenants in 9 series, and the @decocms/start runtime does the equivalent job with 13 distinct `deco.cache.profile` values fleet-wide. The fix uses the block key that `adapt` already receives — the loader's module path, e.g. `vtex/loaders/legacy/productListingPage.ts` — which is bounded by the number of loader modules in the app. It is threaded into `wrapLoader` as a new parameter; `wrapLoader` is module-private with a single call site. What is lost: the metric can no longer distinguish two instances of the same loader sitting in different sections. That trade is deliberate. The chain is still carried on error logs, where high cardinality is acceptable because they are read by point lookup rather than aggregated — and the o11y guide draws exactly that line: high-cardinality attributes are fine for filtering and point lookups, forbidden as aggregation keys. `ctx.resolverId` is kept as a fallback so behaviour is unchanged if a caller ever passes an empty key. Verified: deno check blocks/loader.ts clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 40762b5 commit 67ea0b3

1 file changed

Lines changed: 23 additions & 2 deletions

File tree

blocks/loader.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,13 @@ const wrapLoader = (
188188
}: LoaderModule,
189189
resolveChain: FieldResolver[],
190190
release: DecofileProvider,
191+
/**
192+
* The block key — the loader's module path, e.g.
193+
* `vtex/loaders/legacy/productListingPage.ts`. Used as the metric label
194+
* instead of `ctx.resolverId`, which carries the full resolve chain and is
195+
* unbounded. See the `loader` constant below.
196+
*/
197+
blockKey: string,
191198
) => {
192199
const [cacheMaxAge, mode] = typeof cache === "string"
193200
? [MAX_AGE_S, cache]
@@ -208,7 +215,21 @@ const wrapLoader = (
208215
req: Request,
209216
ctx: FnContext<State, any>,
210217
): Promise<ReturnType<typeof handler>> => {
211-
const loader = ctx.resolverId || "unknown";
218+
// Metric label. Deliberately the block key and NOT `ctx.resolverId`:
219+
// resolverId carries the full resolve chain, e.g.
220+
// `Categories@sections.variants.1.value.5.sections.0.section.page`, so
221+
// every section position of every variant of every page becomes its own
222+
// time series. Measured in production that reached 3,684 distinct values
223+
// on a single site and 21,849 across the fleet, against a documented
224+
// budget of 1,000 per site and 100 fleet-wide — and `loader_cache` alone
225+
// became 76.6% of all rows in otel_metrics_sum.
226+
//
227+
// The block key is bounded by the number of loader modules in the app,
228+
// which is what the @decocms/start runtime already uses for the
229+
// equivalent metric (13 distinct values fleet-wide). The chain is not
230+
// lost: it still travels on error logs, where high cardinality is fine
231+
// because they are read by point lookup rather than aggregated.
232+
const loader = blockKey || ctx.resolverId || "unknown";
212233
const start = performance.now();
213234
let status: "bypass" | "miss" | "stale" | "hit" | undefined;
214235

@@ -365,7 +386,7 @@ const loaderBlock: Block<LoaderModule> = {
365386
wrapCaughtErrors,
366387
(props: TProps, ctx: HttpContext<{ global: any } & RequestState>) =>
367388
applyProps(
368-
wrapLoader(mod, ctx.resolveChain, ctx.context.state.release),
389+
wrapLoader(mod, ctx.resolveChain, ctx.context.state.release, key),
369390
)(
370391
props,
371392
ctx,

0 commit comments

Comments
 (0)