Context
While looking into Redis usage on Hackers' Pub, I found that Fedify's public-key cache had grown much larger than expected.
A read-only snapshot from an instance running Fedify 2.3.6 contained:
- 194,424 public-key cache entries, using an estimated 165–170 MiB;
- 14,407 entries remembering the HTTP Message Signatures specification used by remote origins;
- no TTL on the sampled public-key entries.
This led me back to two write paths in Fedify. KvKeyCache.set() gives unavailable keys a short TTL, but stores successfully resolved keys without one. KvSpecDeterminer.rememberSpec() also stores its result without an expiry.
Both values are soft state. A public key can be fetched again, and the preferred signature specification for an origin can be learned again. Keeping either value forever means that a persistent KvStore grows with every remote key and origin the server has encountered, including peers it will never contact again.
Redis made the growth easy to see, but the same behavior applies to every persistent KvStore.
Direction
I think these two caches should have bounded lifetimes. Their size should reflect a useful working set rather than the entire history of a Fedify installation.
I do not want to prescribe a particular API yet. The policy could live at the individual write sites, in federation configuration, in a reusable KV wrapper, or partly in a store adapter. The important part is that the resulting behavior should not depend unnecessarily on Redis.
The retention policy needs to balance freshness against remote requests. Expiring a key means fetching it again, which can fail while a peer is temporarily unavailable. On the other hand, Fedify already retries with a fresh key when verification using a cached key fails, so permanent retention is not required for key rotation.
We also need to account for existing entries. Applying a TTL only to future writes will leave every old entry that is never read or updated in place.
Acceptance criteria
Things to decide
- Whether bounded retention should be the default or opt-in
- Whether the two cache families need different lifetimes
- Where applications should configure the policy, if it is configurable
- Whether Fedify should migrate existing entries or document a one-time cleanup procedure
#107 introduced public-key caching to avoid a remote fetch for every verification. #916 and #917 dealt with a similar unbounded-growth problem in circuit-breaker state, including entries created by older versions.
Context
While looking into Redis usage on Hackers' Pub, I found that Fedify's public-key cache had grown much larger than expected.
A read-only snapshot from an instance running Fedify 2.3.6 contained:
This led me back to two write paths in Fedify.
KvKeyCache.set()gives unavailable keys a short TTL, but stores successfully resolved keys without one.KvSpecDeterminer.rememberSpec()also stores its result without an expiry.Both values are soft state. A public key can be fetched again, and the preferred signature specification for an origin can be learned again. Keeping either value forever means that a persistent
KvStoregrows with every remote key and origin the server has encountered, including peers it will never contact again.Redis made the growth easy to see, but the same behavior applies to every persistent
KvStore.Direction
I think these two caches should have bounded lifetimes. Their size should reflect a useful working set rather than the entire history of a Fedify installation.
I do not want to prescribe a particular API yet. The policy could live at the individual write sites, in federation configuration, in a reusable KV wrapper, or partly in a store adapter. The important part is that the resulting behavior should not depend unnecessarily on Redis.
The retention policy needs to balance freshness against remote requests. Expiring a key means fetching it again, which can fail while a peer is temporarily unavailable. On the other hand, Fedify already retries with a fresh key when verification using a cached key fails, so permanent retention is not required for key rotation.
We also need to account for existing entries. Applying a TTL only to future writes will leave every old entry that is never read or updated in place.
Acceptance criteria
KvStoreimplementations that support TTL.Things to decide
#107 introduced public-key caching to avoid a remote fetch for every verification. #916 and #917 dealt with a similar unbounded-growth problem in circuit-breaker state, including entries created by older versions.