Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bust _membership_stream_cache cache when current state changes #17732

Merged

Conversation

MadLittleMods
Copy link
Contributor

@MadLittleMods MadLittleMods commented Sep 18, 2024

Bust _membership_stream_cache cache when current state changes. This is particularly a problem in a state reset scenario where the membership might change without a corresponding event.

This PR is targeting a scenario where a state reset happens which causes room membership to change. Previously, the cache would just hold onto stale data and now we properly bust the cache in this scenario.

We have a few tests for these scenarios which you can see are now fixed because we can remove the FIXME where we were previously manually busting the cache in the test itself.

This is a general Synapse thing so by it's nature it helps out Sliding Sync.

Fix #17368

Prerequisite for #17929


Match when are busting _curr_state_delta_stream_cache

Dev notes

self._membership_stream_cache.entity_has_changed(state_key, stream_ordering)  # type: ignore[attr-defined]

self._membership_stream_cache.all_entities_changed(token)  # type: ignore[attr-defined]
get_sliding_sync_rooms_for_user
_invalidate_state_caches_and_stream
_invalidate_state_caches
process_replication_rows
_process_event_stream_row
_account_data_stream_cache
_device_inbox_stream_cache
_device_federation_outbox_stream_cache
_user_signature_stream_cache
presence_stream_cache

Pull Request Checklist

  • Pull request is based on the develop branch
  • Pull request includes a changelog file. The entry should:
    • Be a short description of your change which makes sense to users. "Fixed a bug that prevented receiving messages from other servers." instead of "Moved X method from EventStore to EventWorkerStore.".
    • Use markdown where necessary, mostly for code blocks.
    • End with either a period (.) or an exclamation mark (!).
    • Start with a capital letter.
    • Feel free to credit yourself, by adding a sentence "Contributed by @github_username." or "Contributed by [Your Name]." to the end of the entry.
  • Code style is correct
    (run the linters)

@@ -219,6 +219,8 @@ def process_replication_rows(
room_id = row.keys[0]
members_changed = set(row.keys[1:])
self._invalidate_state_caches(room_id, members_changed)
for user_id in members_changed:
self._membership_stream_cache.entity_has_changed(user_id, token) # type: ignore[attr-defined]
Copy link
Contributor Author

@MadLittleMods MadLittleMods Sep 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kinda weird to just stick this here (same with the others in process_replication_rows). Better way to organize this?

@MadLittleMods MadLittleMods marked this pull request as ready for review September 18, 2024 23:12
@MadLittleMods MadLittleMods requested a review from a team as a code owner September 18, 2024 23:12
@MadLittleMods MadLittleMods marked this pull request as draft September 18, 2024 23:29
Comment on lines +1608 to +1614
stream_id: This is expected to be the minimum `stream_ordering` for the
batch of events that we are persisting; which means we do not end up in a
situation where workers see events before the `current_state_delta` updates.
FIXME: However, this function also gets called with next upcoming
`stream_ordering` when we re-sync the state of a partial stated room (see
`update_current_state(...)`) which may be "correct" but it would be good to
nail down what exactly is the expected value here.
Copy link
Contributor Author

@MadLittleMods MadLittleMods Oct 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previous conversation: #17512 (comment)

I decided to define it in some way given we're using it for cache busting below and was curious if it is actually correct. Still not confident whether it's perfect for cache busting but might be good enough.

Comment on lines +1913 to +1918
for user_id in members_to_cache_bust:
txn.call_after(
self.store._membership_stream_cache.entity_has_changed,
user_id,
stream_id,
)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This matches what we do for _curr_state_delta_stream_cache just above this

Comment on lines +1913 to +1918
for user_id in members_to_cache_bust:
txn.call_after(
self.store._membership_stream_cache.entity_has_changed,
user_id,
stream_id,
)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the actual call that busts the the membership cache for the tests. I assume that is because this is what busts in monolith mode vs the other calls I've added are more for workers over replication

Comment on lines +222 to +226
self._curr_state_delta_stream_cache.entity_has_changed( # type: ignore[attr-defined]
room_id, token
)
for user_id in members_changed:
self._membership_stream_cache.entity_has_changed(user_id, token) # type: ignore[attr-defined]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wherever we are busting _curr_state_delta_stream_cache, we should also be busting _membership_stream_cache (at-least in the general area, expand the hidden diff to find if not visible)

We've forgotten to bust _curr_state_delta_stream_cache in various places which is why it's added and sometimes not.

@MadLittleMods MadLittleMods marked this pull request as ready for review October 10, 2024 00:13
Copy link
Member

@erikjohnston erikjohnston left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for this taking so long.

Not suggesting we do this now, but really we need to go and clean all of this up as there are two many near-identical code paths here that make it very easy to mess these things up.

self._curr_state_delta_stream_cache.entity_has_changed( # type: ignore[attr-defined]
room_id, token
)
self._membership_stream_cache.all_entities_changed(token) # type: ignore[attr-defined]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't actually insert anything into current_state_delta_stream during room deletion, so these are basically no-ops (as reading from the DB won't return anything new).

Given how frequently rooms are deleted nowadays (as we delete rooms where everyone has left after N days, at least on matrix.org) I'm minded to remove these and leave a note. Otherwise I'm worried how this will affect perf

Copy link
Contributor Author

@MadLittleMods MadLittleMods Dec 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the same logic, why do we call self._invalidate_caches_for_room(room_id) at all (just above)?

Perhaps we consider the caches which only affect the room_id to be fine but we also have plenty that clear all of the keys in there (None) which seem just as bad.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the same logic, why do we call self._invalidate_caches_for_room(room_id) at all (just above)?

We do delete a lot of stuff when deleting a room (obviously), and so those caches do contain different data than what is in the DB. So invalidation makes sense in that case?

For the current_state_delta_stream nothing actually gets inserted and so the _curr_state_delta_stream_cache cache wouldn't contain a different answer than what is in the DB

Copy link
Contributor Author

@MadLittleMods MadLittleMods Dec 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So you're saying that we don't delete from current_state_delta_stream when deleting a room. And we don't use _curr_state_delta_stream_cache for anything except when fetching current_state_delta_stream so there is no need to bust the cache at the moment in this scenario.

That looks correct:

# Other tables that we might want to consider clearing out include:
#
# - event_reports
# Given that these are intended for abuse management my initial
# inclination is to leave them in place.
#
# - current_state_delta_stream

But _membership_stream_cache is used for current_state_delta_stream and room_memberships and we do purge from room_memberships when deleting a room so it feels like we need to keep this one around.


Overall, it feels very brittle to comment out _curr_state_delta_stream_cache here because the guarantee we're assuming is bound to change in the future. I can update if you insist.

And given we're only busting the _curr_state_delta_stream_cache for that specific room_id, it doesn't feel disruptive in that case.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue is that self._membership_stream_cache.all_entities_changed(token) will essentially clear that cache. Given we do now purge rooms frequently on matrix.org I worry it will have a noticeable impact on the cache hit ratio. Ideally we'd make it so that we didn't have to clear the entire cache, but that starts getting fiddly quickly. Hence trying to figure out if we really need to clear the cache.

But _membership_stream_cache is used for current_state_delta_stream and room_memberships and we do purge from room_memberships when deleting a room so it feels like we need to keep this one around.

Note that the stream change caches effectively only cache the absence of changes, i.e. "nothing has changed between tokens X and Y and so don't query the database". Hence, I think, if we remove rows from the DB we don't need to clear the cache as at worst it will report that something has changed at token X, at which point it will query the DB and discover nothing is there?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've commented out self._membership_stream_cache.all_entities_changed(token) when we delete a room and added a comment with context on why we think this is safe.

Since the self._curr_state_delta_stream_cache.entity_has_changed(room_id, token) call only invalidates the specific room being deleted, I've left be. I don't think it has the same overarching impact that the all_entities_changed variants do.

synapse/util/caches/stream_change_cache.py Show resolved Hide resolved
Comment on lines 242 to 243
self._invalidate_caches_for_room_events(room_id)
self._invalidate_caches_for_room(room_id)
Copy link
Contributor Author

@MadLittleMods MadLittleMods Dec 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Separate from this PR and in line with the fact that these code paths need to be cleaned up, there is an obvious duplication here because we call self._invalidate_caches_for_room_events(room_id) inside self._invalidate_caches_for_room(room_id) as well.

@MadLittleMods MadLittleMods requested review from erikjohnston and removed request for erikjohnston December 19, 2024 01:39
@MadLittleMods MadLittleMods requested review from erikjohnston and removed request for erikjohnston January 6, 2025 20:09
Copy link
Member

@erikjohnston erikjohnston left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! And sorry this took so long

@MadLittleMods MadLittleMods merged commit aab3672 into develop Jan 8, 2025
39 checks passed
@MadLittleMods MadLittleMods deleted the madlittlemods/17368-bust-_membership_stream_cache branch January 8, 2025 16:11
@MadLittleMods
Copy link
Contributor Author

Thanks for the review and taking the time to understand the intricacies here @erikjohnston 🦚

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Bust _membership_stream_cache cache when current state changes
2 participants