Skip to content

Instantly share code, notes, and snippets.

@VyrCossont
Last active May 29, 2024 00:38
Show Gist options
  • Save VyrCossont/239272d87b6dcd3b611347835267d3c0 to your computer and use it in GitHub Desktop.
Save VyrCossont/239272d87b6dcd3b611347835267d3c0 to your computer and use it in GitHub Desktop.
GotoSocial visibility cache extension proposal

Visibility cache extension proposal

Current or near future state of things

Visibility cache

We have a VisibilityCache, keyed by VisibilityType and requesting account and item IDs, and storing a CachedVisibility. The type can be one of these:

  • VisibilityTypeAccount: item is account, account is visible to requester
  • VisibilityTypeStatus: item is status, status is visible at all to requester (partially depends on VisibilityTypeAccount)
  • VisibilityTypeHome: item is status, status is suitable for requester's home timeline and lists (partially depends on VisibilityTypeStatus)
  • VisibilityTypePublic: item is status, status is suitable for requester's public timelines (partially depends on VisibilityTypeStatus)

CachedVisibility stores all the lookup keys and a boolean for visibility, which takes into account blocks, suspensions, locked accounts, a status's visibility level, etc. These are populated or fetched through cache methods like StatusHomeTimelineable and StatusPublicTimelineable.

Filters

We have caches for filters, filter keywords, and filter statuses, keyed by ID and by owning account ID. Filters apply to statuses in one of five FilterContexts:

  • FilterContextHome: home timeline and lists
  • FilterContextNotifications: notifications timeline
  • FilterContextPublic: public timelines
  • FilterContextThread: thread context (ancestors and descendants of a status)
  • FilterContextAccount: account timeline (looking at an account's statuses)

A filter can show, hide, or warn for a given status. Statuses that result in FilterActionWarn are still returned to the client, but accompanied by a list of matching filters, at which point the client gets the final decision on whether to show it or not. Unlike blocks or timeline visibility rules, filters can expire.

We don't currently cache filtering decisions at all: filters are applied every time statuses from a given filter context are requested.

User mutes

My current draft for user muting closely follows how filters are implemented. User mutes always apply to FilterContextHome, FilterContextPublic, and FilterContextThread. They can optionally apply to FilterContextNotifications. (They don't apply to FilterContextAccount; it's assumed that if you're looking at an account on purpose, you actually want to see its statuses.) User mutes can expire, but there is no equivalent of a warn filter.

The draft doesn't cache muting decisions. But I got to thinking about what it'd look like if we did…

Proposed changes

The goal here is to spend less time checking to see if a status is filtered or muted.

We'd extend VisibilityCache to cover all filter contexts (and thus all mute contexts), to store an expiration time, and to support warn filters. VisibilityType gets three new cases for statuses, corresponding to the rest of the filter contexts:

  • VisibilityTypeNotifications
  • VisibilityTypeThread
  • VisibilityTypeAccount

CachedVisibility.Value, currently a show/hide bool, changes type to a new CachedVisibilityDecision enum with three values:

  • CachedVisibilityDecisionShow
  • CachedVisibilityDecisionWarn
  • CachedVisibilityDecisionHide

The type change would force us to update code paths that couldn't handle the warn case before; for cases where warn doesn't make sense, we should determine visibility with .Value == CachedVisibilityDecisionShow.

We'd also add some new fields to CachedVisibility:

  • ExpiresAt is the minimum expiration time of all filters or user mutes involved in a cached visibility decision. If it's zero, the decision never expires based on time. If it's nonzero, the decision is not valid after that time, and if a cache method gets an expired CachedVisibility, it should evict it from the cache.
  • FilterIDs is only used when Value is CachedVisibilityDecisionWarn, and contains a list of filter IDs that will be retrieved when the status is serialized, and sent along with the status.

If the Type is VisibilityTypeAccount or VisibilityTypeStatus, they don't apply and aren't used.

Cache invalidation

I'm assuming that changing filters and mutes is much less common than checking a status's visibility.

  • Whenever a filter is updated or removed, visibility decisions and filter IDs may change for any status, so we should invalidate all CachedVisibility entries where the requester is the account that owns the filter and the VisibilityType corresponds to one of the filter's contexts.
    • It's not necessary to invalidate when a filter is added, since a new filter has no keywords or statuses yet, and thus can't match anything.
  • Filter keywords can match any status, so whenever a filter keyword is added, updated, or removed, we should invalidate all CachedVisibility entries where the requester is the account that owns the filter and the VisibilityType corresponds to one of the filter's contexts.
  • Filter statuses can only match one status, so whenever a filter status is added or removed (they currently have no state that can be updated), we should invalidate all CachedVisibility entries where the requester is the account that owns the filter, the item is the status, and the VisibilityType corresponds to one of the filter's contexts.
  • Whenever a user mute is added, updated, or removed, visibility decisions may change for any status, so we should invalidate all CachedVisibility entries where the requester is the account that owns the mute and the VisibilityType corresponds to one of the mute's contexts.

Suspected problems with current cache invalidation

Entries for VisibilityTypeHome and VisibilityTypePublic use info previously cached in VisibilityTypeStatus when calculating their own visibility, and VisibilityTypeStatus likewise depends on VisibilityTypeAccount, but invalidating an entry in VisibilityTypeAccount or VisibilityTypeStatus (for example, when a block is added or removed) doesn't seem to have any mechanism for invalidating downstream caches, so outdated visibility info could persist indefinitely.

We might be able to fix this by adding an additional OwnerID key to VisibilityCache that lets us find entries for statuses by the account that owns them. This would let us invalidate all visibility cache entries for an account when it's blocked or unblocked, suspended or unsuspended, or muted or unmuted, and thus improve correctness at the cost of however much memory it takes to have another key.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment