Skip to content

Bound the lifetime of public-key and HTTP Message Signatures caches #1017

Description

@dahlia

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

  • Successfully resolved public keys have a documented, bounded retention policy.
  • Remembered HTTP Message Signatures specifications have a documented, bounded retention policy.
  • Expired entries are fetched or learned again without breaking signature verification or delivery.
  • Existing shorter-lived behavior for unavailable keys and other Fedify state is preserved.
  • The behavior is consistent across persistent KvStore implementations that support TTL.
  • Existing entries written without a TTL are handled through migration behavior or clear operator guidance.
  • Tests cover expiry and repopulation of both cache families.
  • The KV documentation explains the retention and remote-request tradeoffs.

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Fields

    Priority

    None yet

    Effort

    None yet

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions