Skip to content

Commit 50a541c

Browse files
committed
feat: support cache statistics
1 parent 2d870b6 commit 50a541c

File tree

5 files changed

+312
-5
lines changed

5 files changed

+312
-5
lines changed

.changeset/yellow-dolls-fail.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@modern-js/runtime-utils': patch
3+
---
4+
5+
feat: support cache statistics
6+
feat: 支持缓存命中率统计

packages/document/main-doc/docs/en/guides/basic-features/data/data-cache.mdx

+63-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ sidebar_position: 4
44
---
55
# Data Caching
66

7-
The `cache` function allows you to cache the results of data fetching or computation.
7+
The `cache` function allows you to cache the results of data fetching or computation, Compared to full-page [rendering cache](/guides/basic-features/render/ssr-cache), it provides more fine-grained control over data granularity and is applicable to various scenarios such as Client-Side Rendering (CSR), Server-Side Rendering (SSR), and API services (BFF).
88

99
:::info
1010
X.65.5 and above versions are required
@@ -217,6 +217,68 @@ const getUserD = cache(
217217
);
218218
```
219219

220+
#### `onCache` Parameter
221+
222+
The `onCache` parameter allows you to track cache statistics such as hit rate. It's a callback function that receives information about each cache operation, including the status, key, parameters, and result.
223+
224+
```ts
225+
import { cache, CacheTime } from '@modern-js/runtime/cache';
226+
227+
// Track cache statistics
228+
const stats = {
229+
total: 0,
230+
hits: 0,
231+
misses: 0,
232+
stales: 0,
233+
hitRate: () => stats.hits / stats.total
234+
};
235+
236+
const getUser = cache(
237+
fetchUserData,
238+
{
239+
maxAge: CacheTime.MINUTE * 5,
240+
onCache({ status, key, params, result }) {
241+
// status can be 'hit', 'miss', or 'stale'
242+
stats.total++;
243+
244+
if (status === 'hit') {
245+
stats.hits++;
246+
} else if (status === 'miss') {
247+
stats.misses++;
248+
} else if (status === 'stale') {
249+
stats.stales++;
250+
}
251+
252+
console.log(`Cache ${status} for key: ${String(key)}`);
253+
console.log(`Current hit rate: ${stats.hitRate() * 100}%`);
254+
}
255+
}
256+
);
257+
258+
// Usage example
259+
await getUser(1); // Cache miss
260+
await getUser(1); // Cache hit
261+
await getUser(2); // Cache miss
262+
```
263+
264+
The `onCache` callback receives an object with the following properties:
265+
266+
- `status`: The cache operation status, which can be:
267+
- `hit`: Cache hit, returning cached content
268+
- `miss`: Cache miss, executing the function and caching the result
269+
- `stale`: Cache hit but data is stale, returning cached content while revalidating in the background
270+
- `key`: The cache key, which is either the result of `customKey` or the default generated key
271+
- `params`: The parameters passed to the cached function
272+
- `result`: The result data (either from cache or newly computed)
273+
274+
This callback is only invoked when the `options` parameter is provided. When using the cache function without options (for SSR request-scoped caching), the `onCache` callback is not called.
275+
276+
The `onCache` callback is useful for:
277+
- Monitoring cache performance
278+
- Calculating hit rates
279+
- Logging cache operations
280+
- Implementing custom metrics
281+
220282
### Storage
221283

222284
Currently, both client and server caches are stored in memory.

packages/document/main-doc/docs/zh/guides/basic-features/data/data-cache.mdx

+63-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ sidebar_position: 4
44
---
55
# 数据缓存
66

7-
`cache` 函数可以让你缓存数据获取或计算的结果。
7+
`cache` 函数可以让你缓存数据获取或计算的结果,相比整页[渲染缓存](/guides/basic-features/render/ssr-cache),它提供了更精细的数据粒度控制,并且适用于客户端渲染(CSR)、服务端渲染(SSR)、API 服务(BFF)等多种场景
88

99
:::info
1010
需要 x.65.5 及以上版本
@@ -214,6 +214,68 @@ const getUserD = cache(
214214
);
215215
```
216216

217+
#### `onCache` 参数
218+
219+
`onCache` 参数允许你跟踪缓存统计信息,例如命中率。这是一个回调函数,接收有关每次缓存操作的信息,包括状态、键、参数和结果。
220+
221+
```ts
222+
import { cache, CacheTime } from '@modern-js/runtime/cache';
223+
224+
// 跟踪缓存统计
225+
const stats = {
226+
total: 0,
227+
hits: 0,
228+
misses: 0,
229+
stales: 0,
230+
hitRate: () => stats.hits / stats.total
231+
};
232+
233+
const getUser = cache(
234+
fetchUserData,
235+
{
236+
maxAge: CacheTime.MINUTE * 5,
237+
onCache({ status, key, params, result }) {
238+
// status 可以是 'hit'、'miss' 或 'stale'
239+
stats.total++;
240+
241+
if (status === 'hit') {
242+
stats.hits++;
243+
} else if (status === 'miss') {
244+
stats.misses++;
245+
} else if (status === 'stale') {
246+
stats.stales++;
247+
}
248+
249+
console.log(`缓存${status === 'hit' ? '命中' : status === 'miss' ? '未命中' : '陈旧'},键:${String(key)}`);
250+
console.log(`当前命中率:${stats.hitRate() * 100}%`);
251+
}
252+
}
253+
);
254+
255+
// 使用示例
256+
await getUser(1); // 缓存未命中
257+
await getUser(1); // 缓存命中
258+
await getUser(2); // 缓存未命中
259+
```
260+
261+
`onCache` 回调接收一个包含以下属性的对象:
262+
263+
- `status`: 缓存操作状态,可以是:
264+
- `hit`: 缓存命中,返回缓存内容
265+
- `miss`: 缓存未命中,执行函数并缓存结果
266+
- `stale`: 缓存命中但数据陈旧,返回缓存内容同时在后台重新验证
267+
- `key`: 缓存键,可能是 `customKey` 的结果或默认生成的键
268+
- `params`: 传递给缓存函数的参数
269+
- `result`: 结果数据(来自缓存或新计算的)
270+
271+
这个回调只在提供 `options` 参数时被调用。当使用不带选项的缓存函数(用于 SSR 请求范围内的缓存)时,不会调用 `onCache` 回调。
272+
273+
`onCache` 回调对以下场景非常有用:
274+
- 监控缓存性能
275+
- 计算命中率
276+
- 记录缓存操作
277+
- 实现自定义指标
278+
217279
### 存储
218280

219281
目前不管是客户端还是服务端,缓存都存储在内存中,默认情况下所有缓存函数共享的存储上限是 1GB,当达到存储上限后,使用 LRU 算法移除旧的缓存。

packages/toolkit/runtime-utils/src/universal/cache.ts

+42-3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,15 @@ export const CacheTime = {
1616
MONTH: 30 * 24 * 60 * 60 * 1000,
1717
} as const;
1818

19+
export type CacheStatus = 'hit' | 'stale' | 'miss';
20+
21+
export interface CacheStatsInfo {
22+
status: CacheStatus;
23+
key: string | symbol;
24+
params: any[];
25+
result: any;
26+
}
27+
1928
interface CacheOptions {
2029
tag?: string | string[];
2130
maxAge?: number;
@@ -25,6 +34,7 @@ interface CacheOptions {
2534
fn: (...args: Args) => any;
2635
generatedKey: string;
2736
}) => string | symbol;
37+
onCache?: (info: CacheStatsInfo) => void;
2838
}
2939

3040
interface CacheConfig {
@@ -155,6 +165,7 @@ export function cache<T extends (...args: any[]) => Promise<any>>(
155165
maxAge = CacheTime.MINUTE * 5,
156166
revalidate = 0,
157167
customKey,
168+
onCache,
158169
} = options || {};
159170
const store = getLRUCache();
160171

@@ -198,10 +209,11 @@ export function cache<T extends (...args: any[]) => Promise<any>>(
198209
}
199210
}
200211
} else if (typeof options !== 'undefined') {
201-
const key = generateKey(args);
212+
const genKey = generateKey(args);
202213
const now = Date.now();
203214

204-
const cacheKey = getCacheKey(args, key);
215+
const cacheKey = getCacheKey(args, genKey);
216+
const finalKey = typeof cacheKey === 'function' ? genKey : cacheKey;
205217

206218
tags.forEach(t => addTagKeyRelation(t, cacheKey));
207219

@@ -211,17 +223,34 @@ export function cache<T extends (...args: any[]) => Promise<any>>(
211223
}
212224

213225
const storeKey =
214-
customKey && typeof cacheKey === 'symbol' ? 'symbol-key' : key;
226+
customKey && typeof cacheKey === 'symbol' ? 'symbol-key' : genKey;
215227

216228
const cached = cacheStore.get(storeKey);
217229
if (cached) {
218230
const age = now - cached.timestamp;
219231

220232
if (age < maxAge) {
233+
if (onCache) {
234+
onCache({
235+
status: 'hit',
236+
key: finalKey,
237+
params: args,
238+
result: cached.data,
239+
});
240+
}
221241
return cached.data;
222242
}
223243

224244
if (revalidate > 0 && age < maxAge + revalidate) {
245+
if (onCache) {
246+
onCache({
247+
status: 'stale',
248+
key: finalKey,
249+
params: args,
250+
result: cached.data,
251+
});
252+
}
253+
225254
if (!cached.isRevalidating) {
226255
cached.isRevalidating = true;
227256
Promise.resolve().then(async () => {
@@ -252,6 +281,7 @@ export function cache<T extends (...args: any[]) => Promise<any>>(
252281
}
253282

254283
const data = await fn(...args);
284+
255285
cacheStore.set(storeKey, {
256286
data,
257287
timestamp: now,
@@ -260,6 +290,15 @@ export function cache<T extends (...args: any[]) => Promise<any>>(
260290

261291
store.set(cacheKey, cacheStore);
262292

293+
if (onCache) {
294+
onCache({
295+
status: 'miss',
296+
key: finalKey,
297+
params: args,
298+
result: data,
299+
});
300+
}
301+
263302
return data;
264303
} else {
265304
console.warn(

0 commit comments

Comments
 (0)