Effect 的 Cache 怎么让成功和失败用不同的 TTL?
下游抖了一下,一次失败的查询被缓存了十分钟,之后所有人都读到同一个错误。原因是 Effect 的 Cache 会把查找的整个 Exit 缓存起来,失败也不例外。
用 Cache.makeWith,在 timeToLive 里按结果的 Exit 返回时长:成功返回长 TTL,失败返回短 TTL。 Cache.make 是固定 TTL,makeWith 才能让 TTL 取决于结果。
下面用 TestClock 把时间快进,逐步验证条目什么时候过期、什么时候重新查。TestClock 的更多用法见 Effect 的超时和重试怎么测,才不用真等?。
timeToLive 怎么按结果区分成功和失败?
timeToLive 接收查找结果的 Exit,返回一个 Duration。典型用法是成功缓存久一点,失败缓存短一点。
import { Cache, Duration, Effect, Exit } from "effect"
import { TestClock } from "effect/testing"
const program = Effect.gen(function*() {
let lookups = 0
const cache = yield* Cache.makeWith(
(key: string) =>
Effect.suspend(() => {
lookups++
return key === "bad" ? Effect.fail("boom") : Effect.succeed(key.length)
}),
{
capacity: 100,
timeToLive: (exit) => (Exit.isSuccess(exit) ? Duration.minutes(10) : Duration.seconds(5))
}
)
yield* Cache.get(cache, "hello")
yield* Cache.get(cache, "hello")
console.log(lookups) // 1:第二次命中
yield* Effect.exit(Cache.get(cache, "bad"))
yield* Effect.exit(Cache.get(cache, "bad"))
console.log(lookups) // 2:失败也被缓存,第二次没有重新查
yield* TestClock.adjust("6 seconds")
yield* Effect.exit(Cache.get(cache, "bad"))
console.log(lookups) // 3:失败条目 5 秒后过期,重新查
yield* Cache.get(cache, "hello")
console.log(lookups) // 3:成功条目还在
yield* TestClock.adjust("10 minutes")
yield* Cache.get(cache, "hello")
console.log(lookups) // 4:成功条目过期
}).pipe(Effect.provide(TestClock.layer()))
Effect.runPromise(program)timeToLive 的参数是 (exit, key),所以也可以按 key 区分。
失败也会被缓存吗?
会。上面第二次读 "bad" 没有重新查,lookups 停在 2,就是证据。失败条目要等自己的 TTL 到期才会重新查,所以失败的 TTL 要设短,而不是不设。
另外两个事实来自 JSDoc:对同一个未命中的 key,并发的 Cache.get 共享同一次进行中的查找,不会重复调用 lookup;查找的 Exit 被整体缓存,所以失败也会缓存到过期、失效或刷新为止。
怎么把批处理和带 TTL 的缓存合在一起?
RequestResolver.asCache(resolver, { capacity, timeToLive }) 把 resolver 直接变成一个以 request 为 key 的 Cache,未命中时走 Effect.request,于是你同时得到「批处理合并」与「带 TTL 的缓存、失效、刷新」。我验证了:3 个请求(含一个重复)只触发 1 批,快进 2 分钟后同一请求重新触发 1 批。
批处理本身怎么写,见 Effect 怎么把 N+1 查询合并成一批?。那篇里的 withCache 没有 TTL,需要过期时就换成这里的做法。
缓存的值本身是资源时怎么办?
值需要释放(连接、文件句柄、子进程)时,Cache 不管释放,要用 ScopedCache,见 Effect 的 ScopedCache 过期后,资源什么时候被释放?。
版本是 effect 4.0.5。TTL 的各步行为在TestClock下编译并运行过,asCache的批次数也跑过。「并发Cache.get共享同一次查找」与「Exit 整体缓存」两条来自 JSDoc,没有单独验证。