Skip to content

Heuristic freshness computed from Last-Modified #3

Closed

Description

@knoan

challenge / use case

A response stating no explicit expiration is revalidated on every exchange, even where it carries a Last-Modified
field that says how volatile the content actually is. An origin whose content has not changed in a year is re-fetched
as eagerly as one edited a minute ago.

proposal

Derive a freshness lifetime from the response header fields where the origin states no explicit expiration, as
permitted by RFC 9111 § 4.2.2: take the interval since Last-Modified and treat a fraction of it as the lifetime,
around 10% as the RFC encourages.

Guards:

  • apply only where no Cache-Control freshness directive and no Expires is stated; no-cache and max-age=0 are
    stated expirations and must keep winning, so the test is whether a directive is present, not whether the computed
    lifetime is 0
  • apply only for the heuristically cacheable status codes of RFC 9110 § 15.1
  • bound the result, so a distant Last-Modified does not yield an unbounded lifetime
  • let ttl cap the result as it caps any computed lifetime

alternatives

Leaving it to the consumer to state a flat lifetime. That covers an origin stating nothing at all, but ignores the
volatility an origin does state, so a stable and a churning resource are reused for the same span.

notes

Ranks between the two cases already settled: a stated expiration wins over this, and this wins over a flat lifetime
the consumer states for an origin whose responses carry no Last-Modified either.

Activity

  1. added theissue type on Sep 3, 2026
  2. changed the title [-]Heuristic freshness for responses stating no explicit expiration[/-] [+]Heuristic freshness computed from Last-Modified[/+] on Sep 3, 2026
  3. moved this from Backlog to Active in Packageson Sep 3, 2026
  4. added a commit that references this issue on Sep 3, 2026
    4dc394d
  5. knoan commented on Sep 3, 2026

    @knoan
    ContributorAuthor

    Implemented in 4dc394d on release/0.1.

    Approach

    A response stating no expiration but reporting a usable Last-Modified is now reused for a tenth of the interval its content had already gone unchanged when the response was generated, measured up to the instant the origin server states in Date and falling back to the reception time where it states none, matching how the lifetime Expires states is measured.

    Guards

    • a Cache-Control no-cache, max-age, must-revalidate or proxy-revalidate directive or an Expires field keeps winning, tested by presence rather than by the lifetime it computes
    • the assumption applies to the heuristically cacheable status codes of RFC 9110 § 15.1 alone
    • ttl caps the assumed share as it caps a stated expiration, and supplies the freshness where no usable Last-Modified is reported
    • content stated as changed after generation yields no freshness

    Open point

    The bound is ttl alone, so the default ttl of 0 leaves the assumed share uncapped: a Last-Modified ten years back yields a one-year lifetime. Filed separately if a built-in ceiling is wanted.

  6. moved this from Active to Done in Packageson Sep 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    • Status
      Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions