Skip to content

Commit 899052a

Browse files
Elon Muskclaude
andauthored
docs(observability): record what a zero on cache_* means — "no configured consumer", not "no cache activity" (#10023)
* docs(observability): record what a zero on `cache_*` means — "no configured consumer", not "no cache activity" (#9954) `SEMCONV` declares the `cache_*` families as a stable namespace so hosts can wire alerts/dashboards against it. In a default install those dashboards read a flat zero, and neither the declaration nor the operator docs said why — leaving "0% hit rate" and "the adapter is broken" as the available readings. Both wrong. Nothing about emission changes: #9832 wired the adapter to the host's registry and #9951 pins a real lookup observing cache_lookups_total{result=miss}. The zero is TRUE; what it failed to communicate is its cause. Cause re-measured on origin/main rather than taken from the card: no consumer of the `cache` service is unconditional. Every production consumer is a rate-limit or budget counter store gated on an explicit declaration — better-auth's per-IP counters (rate_limit_max / rate_limit_window_seconds), the dispatcher's inbound limiter and its declarative per-endpoint buckets (both register nothing at all with no budget declared), and the per-number OTP send budget. The annotation states that invariant rather than a roster, so it stays true when another conditional consumer lands and goes false exactly when an unconditional one appears — which is when it should be revisited. Worded deliberately against the HTTP note beside it: there zero means "not instrumented"; here zero is a true count of a service nothing consulted. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019bmVFqoQPq63zhKrxdYG1r * fix(observability): attach the `cache_*` zero-meaning note as per-family JSDoc, so it reaches the shipped `.d.ts` Measured, not assumed: `tsup`'s dts generation drops `//` line comments, so the first draft's group-header note was absent from dist/index.d.ts — invisible to the operator IDE hover that is the whole point of annotating a declaration an operator panels against. The HTTP families #10004 annotated use per-family JSDoc precisely because that form survives; this now matches that shape. Verified in the built artifact: 2 occurrences of the phrase in packages/observability/dist/index.d.ts after `pnpm --filter @objectstack/observability build`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019bmVFqoQPq63zhKrxdYG1r --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent f8afa0b commit 899052a

4 files changed

Lines changed: 125 additions & 3 deletions

File tree

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
---
2+
"@objectstack/observability": patch
3+
---
4+
5+
docs(observability): record what a zero on `cache_*` MEANS — "no configured consumer", not "no cache activity" (#9954)
6+
7+
`SEMCONV` declares the `cache_*` families as a stable namespace explicitly so
8+
hosts can wire alerts and dashboards against it. An operator who does that gets
9+
a flat zero on `cache_lookups_total` in a default install — and nothing in the
10+
declaration or the operator docs said why, so the only available readings were
11+
"0% hit rate" or "the adapter is broken". Both are wrong.
12+
13+
**Nothing about emission changes.** The path is proven working: #9832 wired the
14+
cache adapter to the host's registry and #9951 pins a real lookup observing
15+
`cache_lookups_total{adapter=memory,result=miss}`. The zero is *true*; what it
16+
failed to communicate is its cause.
17+
18+
The cause, re-measured on `origin/main` rather than taken from the card: no
19+
consumer of the `cache` service is unconditional. Every production consumer is
20+
a rate-limit or budget counter store, and each is gated on a declaration
21+
somebody has to write:
22+
23+
- `packages/plugins/plugin-auth/src/auth-plugin.ts` — better-auth's per-IP
24+
counters, reached only when `rate_limit_max` or `rate_limit_window_seconds`
25+
is explicitly supplied in auth settings.
26+
- `packages/runtime/src/dispatcher-plugin.ts` — the inbound rate limiter and
27+
the declarative per-endpoint buckets. Both register *nothing at all* when no
28+
budget is declared (`createInboundRateLimitMiddleware` returns `null`;
29+
`limiterFor` returns `null` on an endpoint with no armed `rateLimit`), so an
30+
unmetered deployment never reaches the cache.
31+
- the per-number OTP send budget, reached only on an SMS send path.
32+
33+
Declare none of them — the default slate — and the family sits at 0 while the
34+
server handles traffic normally.
35+
36+
So the annotation states the invariant rather than a roster: *every* consumer
37+
is an explicitly-declared rate-limit/budget counter store. That sentence stays
38+
true when another conditional consumer is added, and goes false exactly when an
39+
unconditional one appears — which is when it should be revisited.
40+
41+
This is the mirror image of the HTTP note that landed alongside it: there a
42+
zero means "not instrumented"; here a zero is a true count of a service nothing
43+
asked anything of. The two are deliberately worded so they cannot be read as
44+
the same statement.
45+
46+
The note is attached as per-family JSDoc rather than a `//` group header, and
47+
that placement is load-bearing: `tsup`'s dts generation drops line comments, so
48+
only the JSDoc form reaches `dist/index.d.ts` — i.e. the operator's IDE hover,
49+
which is where somebody wiring a `cache_lookups_total` panel actually meets the
50+
declaration. Verified in the built artifact, not assumed.

content/docs/deployment/production-readiness.mdx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,18 @@ on `http_requests_total` there means "not instrumented", never "no traffic".
7878
The shipped Hono adapter implements it; verify a custom adapter with
7979
`typeof server.afterResponse === 'function'` before trusting its numbers.
8080

81+
**A flat `cache_*` means "no configured consumer"**, and that reading is the
82+
opposite of the one above: the `@objectstack/service-cache` adapters *are*
83+
instrumented and do hold the host's registry, so a zero on
84+
`cache_lookups_total` is a true count — of a service nothing asked anything
85+
of. Nothing consults the `cache` service unconditionally; every production
86+
consumer is a rate-limit or budget counter store gated on an explicit
87+
declaration (better-auth's `rate_limit_max` / `rate_limit_window_seconds`, the
88+
dispatcher's inbound and per-endpoint `rateLimit` budgets, the per-number OTP
89+
send budget). Declare none and the family sits at 0 under normal traffic, so
90+
treat a cache hit-rate panel as reporting on your configuration before you
91+
treat it as reporting on your cache.
92+
8193
Rate limiting is the one piece you wire at the adapter layer (Fastify
8294
preHandler, Hono middleware, etc.) because that's where you have
8395
reliable access to the caller's IP and authenticated identity. See

docs/OBSERVABILITY.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,32 @@ RUNTIME_METRICS.httpRequestDurationMs // 'http_request_duration_ms'
7878
RUNTIME_METRICS.httpRequestErrorsTotal // 'http_request_errors_total'
7979
```
8080

81+
### `cache_*` — a flat zero means "no configured consumer"
82+
83+
The `@objectstack/service-cache` adapters emit `cache_lookups_total`,
84+
`cache_writes_total` and `cache_errors_total` (declared in `SEMCONV`, from
85+
`@objectstack/observability`) on every call the `cache` service receives. The
86+
adapters pick the host's metrics registry up automatically, so the emission
87+
path is live as soon as `ObservabilityServicePlugin` is registered.
88+
89+
> **⚠️ A flat `cache_*` in a default install is CORRECT, not a blind spot —
90+
> but it does not mean what a cache panel implies.** Nothing consults the
91+
> `cache` service unconditionally: every production consumer is a rate-limit
92+
> or budget counter store, each gated on a declaration somebody has to write
93+
> — better-auth's per-IP counters (`rate_limit_max` /
94+
> `rate_limit_window_seconds` in auth settings), the dispatcher's inbound
95+
> rate limiter and its declarative per-endpoint buckets (an armed `rateLimit`
96+
> budget; with none declared the dispatcher registers no limiter at all), and
97+
> the per-number OTP send budget (reached only on an SMS send). Declare none
98+
> of them and `cache_lookups_total` stays at 0 while the server handles
99+
> traffic normally.
100+
>
101+
> So read a zero here as a question about **configuration**, never as a 0%
102+
> hit rate or a broken adapter. This is the mirror image of the HTTP note
103+
> above: there, zero means "not instrumented"; here, zero is a true count of
104+
> a service nothing asked anything of. Before trusting a cache hit-rate
105+
> panel, confirm at least one consumer above is actually armed.
106+
81107
### Prometheus adapter (prom-client)
82108

83109
```ts
@@ -376,6 +402,10 @@ countServerTiming('db', queryMs, 'queries'); // → db;dur=<sum>;desc="<n> queri
376402
- [ ] Verified `http_requests_total{status="5xx"}` increments when an
377403
endpoint deliberately throws.
378404
- [ ] Verified `http_request_duration_ms` histogram has non-empty buckets.
405+
- [ ] If a `cache_*` panel or alert is wired: confirmed at least one cache
406+
consumer is armed (an auth `rate_limit_*` setting, a dispatcher or
407+
endpoint `rateLimit` budget, or the SMS OTP path). Otherwise a flat
408+
`cache_lookups_total` is the expected reading, not a fault to chase.
379409
- [ ] `errorReporter` adapter configured and at least one synthetic 5xx
380410
reaches your APM dashboard.
381411
- [ ] Verified 4xx does **not** flood the APM.

packages/observability/src/semconv.ts

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,41 @@ export const SEMCONV = {
5353
storageErrorsTotal: 'storage_errors_total',
5454

5555
// ── Cache — emitted by `@objectstack/service-cache` adapters ──
56-
/** Counter, labels: `adapter` (`memory`|`redis`), `result` (`hit`|`miss`). */
56+
// Uniform emitter; what varies is who CONSULTS the service — see below.
57+
/**
58+
* Counter, labels: `adapter` (`memory`|`redis`), `result` (`hit`|`miss`).
59+
*
60+
* ⚠️ A flat zero means "NO CONFIGURED CONSUMER", not "no cache activity",
61+
* and — unlike the HTTP families above — it is NOT an instrumentation gap.
62+
* The adapters hold the host's registry and count every call they receive
63+
* (#9832 wired that; #9951 pins it), so a zero here is TRUE. What it fails
64+
* to communicate is WHY.
65+
*
66+
* The why: nothing consults the `cache` service unconditionally. Every
67+
* production consumer is a rate-limit / budget counter store, and each is
68+
* gated on a declaration somebody has to write — better-auth's per-IP
69+
* counters (`rate_limit_max` / `rate_limit_window_seconds` in auth
70+
* settings), the dispatcher's inbound limiter and its declarative
71+
* per-endpoint buckets (an armed `rateLimit` budget; with none declared
72+
* the dispatcher registers no limiter at all), and the per-number OTP send
73+
* budget (an SMS send path). A default install declares none of them, so
74+
* this counter stays at 0 while the server handles traffic normally.
75+
*
76+
* ⇒ Read a flat `cache_*` as a question about CONFIGURATION, never as a 0%
77+
* hit rate or a broken adapter. Before trusting a cache hit-rate panel,
78+
* confirm at least one consumer above is actually armed.
79+
*/
5780
cacheLookupsTotal: 'cache_lookups_total',
58-
/** Counter, labels: `adapter`, `op` (`set`|`delete`|`clear`). */
81+
/**
82+
* Counter, labels: `adapter`, `op` (`set`|`delete`|`clear`). Same
83+
* "zero = no configured consumer" reading as `cacheLookupsTotal` above.
84+
*/
5985
cacheWritesTotal: 'cache_writes_total',
60-
/** Counter, labels: `adapter`, `op`, `errorClass`. */
86+
/**
87+
* Counter, labels: `adapter`, `op`, `errorClass`. Same "zero = no
88+
* configured consumer" reading as `cacheLookupsTotal` above — a zero is
89+
* "nothing was asked of the cache", not "every call succeeded".
90+
*/
6191
cacheErrorsTotal: 'cache_errors_total',
6292

6393
// ── Background jobs — emitted by `@objectstack/runtime`'s AppPlugin ──

0 commit comments

Comments
 (0)