Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Limit and filter the number of backfill points to get from the database #13879

Merged
Merged
Show file tree
Hide file tree
Changes from 82 commits
Commits
Show all changes
93 commits
Select commit Hold shift + click to select a range
e0d7fab
Keep track when we tried to backfill an event
MadLittleMods Aug 23, 2022
b8d55d3
Record in some fail spots
MadLittleMods Aug 25, 2022
f63d823
Merge branch 'develop' into madlittlemods/keep-track-when-we-tried-to…
MadLittleMods Aug 25, 2022
bec26e2
Record and clear attempts
MadLittleMods Aug 25, 2022
fee37c3
Add changelog
MadLittleMods Aug 25, 2022
d1290be
Remove from when spam checker fails
MadLittleMods Aug 25, 2022
f9119d0
Custom upsert to increment
MadLittleMods Aug 25, 2022
f5c6fe7
Fix lints
MadLittleMods Aug 25, 2022
16ebec0
Remove extra whitespace
MadLittleMods Aug 25, 2022
ce07aa1
Move to correct folder
MadLittleMods Aug 25, 2022
5811ba1
Set the version back
MadLittleMods Aug 25, 2022
cf2b093
Fix `TypeError: not all arguments converted during string formatting`
MadLittleMods Aug 25, 2022
cbb4194
Add test to make sure failed backfill attempts are recorded
MadLittleMods Aug 26, 2022
621c5d3
Clean up test
MadLittleMods Aug 26, 2022
75c07bb
Fix comments
MadLittleMods Aug 26, 2022
783cce5
Add test for clearing backfill attempts
MadLittleMods Aug 26, 2022
54ef84b
Maybe a better comment
MadLittleMods Aug 26, 2022
7bf3e7f
WIP: Just working on the query
MadLittleMods Aug 26, 2022
37ff009
Move comment to where it matters
MadLittleMods Aug 26, 2022
a58d191
Silly graph pt 1
MadLittleMods Aug 26, 2022
f127ad1
Silly graph pt 2
MadLittleMods Aug 26, 2022
18abbf4
Tests running (not working)
MadLittleMods Aug 27, 2022
23310f5
Passing test
MadLittleMods Aug 27, 2022
64e01d8
Add test for A and B
MadLittleMods Aug 27, 2022
47bac25
Add tests for backfill attempts
MadLittleMods Aug 27, 2022
2ebed9d
Remove `GROUP BY backward_extrem.event_id` (seems unnecessary)
MadLittleMods Aug 27, 2022
60b3b92
Clarify why that much time
MadLittleMods Aug 27, 2022
e9f603d
Label ? slot
MadLittleMods Aug 27, 2022
a8f1464
Better explanation
MadLittleMods Aug 27, 2022
bbd1c94
Add changelog
MadLittleMods Aug 27, 2022
dd1db25
Fix lints
MadLittleMods Aug 27, 2022
c583eef
Update docstring
MadLittleMods Aug 27, 2022
ea4a3ad
Apply same changes to `get_insertion_event_backward_extremities_in_room`
MadLittleMods Aug 27, 2022
f495752
Use power and capitalize AS
MadLittleMods Aug 27, 2022
f2061b9
Use SQLite compatible power of 2 (left shift)
MadLittleMods Aug 31, 2022
e4192d7
Update table name with "failed" and include room_id in the primary key
MadLittleMods Aug 31, 2022
7a44932
Rename to record_event_failed_backfill_attempt
MadLittleMods Aug 31, 2022
86d98ca
Merge branch 'develop' into madlittlemods/keep-track-when-we-tried-to…
MadLittleMods Aug 31, 2022
29f584e
Merge branch 'madlittlemods/keep-track-when-we-tried-to-backfill-an-e…
MadLittleMods Aug 31, 2022
506a8dd
Changes after merging madlittlemods/keep-track-when-we-tried-to-backf…
MadLittleMods Aug 31, 2022
361ce5c
Use compatible least/min on each db platform
MadLittleMods Aug 31, 2022
b09d8a2
Fix SQLite no such column error when comparing table to null
MadLittleMods Aug 31, 2022
965d142
Add comment about how these are sorted by depth now
MadLittleMods Aug 31, 2022
267777f
Apply same least compatiblity to insertion event extremities
MadLittleMods Aug 31, 2022
d0cd42a
Fix lints
MadLittleMods Aug 31, 2022
3d9f625
Try fix ambiguous column (remove unsued table)
MadLittleMods Sep 1, 2022
33a3c64
Fix ambiguous column
MadLittleMods Sep 1, 2022
6736d10
Add tests for get_insertion_event_backward_extremities_in_room
MadLittleMods Sep 1, 2022
6eba1d4
Fix up test descriptions
MadLittleMods Sep 1, 2022
1464101
Add _unsafe_to_upsert_tables check
MadLittleMods Sep 1, 2022
71c7738
Add foreign key references
MadLittleMods Sep 1, 2022
df8c76d
Merge branch 'develop' into madlittlemods/keep-track-when-we-tried-to…
MadLittleMods Sep 1, 2022
d45b078
Remove reference to event that won't be in the events table
MadLittleMods Sep 1, 2022
c939422
Merge branch 'madlittlemods/keep-track-when-we-tried-to-backfill-an-e…
MadLittleMods Sep 1, 2022
599e212
Fix approximate typo
MadLittleMods Sep 1, 2022
bc8046b
Clarify what depth sort
MadLittleMods Sep 1, 2022
ea08006
Fix typos
MadLittleMods Sep 1, 2022
9a85bb4
Normal is not obvious
MadLittleMods Sep 1, 2022
7204cce
Fix left-shift math
MadLittleMods Sep 1, 2022
8f214b1
Fix foreign key constraint
MadLittleMods Sep 2, 2022
33ad64e
Merge branch 'develop' into madlittlemods/keep-track-when-we-tried-to…
MadLittleMods Sep 9, 2022
63bec99
Remove emulated upsert code (all of our dbs no support it)
MadLittleMods Sep 9, 2022
31d7502
Table rename `event_failed_pull_attempts`
MadLittleMods Sep 9, 2022
0b5f1db
Add `last_cause` column
MadLittleMods Sep 9, 2022
4ce7709
Merge branch 'develop' into madlittlemods/keep-track-when-we-tried-to…
MadLittleMods Sep 12, 2022
d3a1f84
Merge branch 'develop' into madlittlemods/keep-track-when-we-tried-to…
MadLittleMods Sep 13, 2022
1347686
Update schema version summary
MadLittleMods Sep 13, 2022
57182dc
Remove backfilled check since we plan to go general anyway
MadLittleMods Sep 14, 2022
e58bc65
Merge branch 'develop' into madlittlemods/keep-track-when-we-tried-to…
MadLittleMods Sep 14, 2022
70019d2
Move change to latest delta 73
MadLittleMods Sep 14, 2022
46a1a20
Merge branch 'madlittlemods/keep-track-when-we-tried-to-backfill-an-e…
MadLittleMods Sep 14, 2022
91c5be0
Merge branch 'develop' into madlittlemods/13622-do-not-retry-backfill…
MadLittleMods Sep 14, 2022
7ea40b1
Updates after schema changes in the other PR
MadLittleMods Sep 14, 2022
40ec8d8
Remove debug logging
MadLittleMods Sep 14, 2022
47aa375
Merge branch 'develop' into madlittlemods/13622-do-not-retry-backfill…
MadLittleMods Sep 22, 2022
1208540
Remove orthogonal `current_depth` changes
MadLittleMods Sep 22, 2022
1738619
Revert "Remove orthogonal `current_depth` changes"
MadLittleMods Sep 22, 2022
3f1b695
Typos
MadLittleMods Sep 22, 2022
aae06fd
Merge branch 'develop' into madlittlemods/backfill-points-filtering-a…
MadLittleMods Sep 23, 2022
bae72e4
I didn't try to change the lock (shrug)
MadLittleMods Sep 23, 2022
4f2b3ae
Make sure our timing doesn't get thrown off
MadLittleMods Sep 23, 2022
44e2a5d
Add limit, fix lints and add changelog
MadLittleMods Sep 23, 2022
685bbee
Fixup language
MadLittleMods Sep 27, 2022
9921f24
Remove redundant comment
MadLittleMods Sep 27, 2022
7c96481
Simpler, behavior equivalent condition when nothing to backfill
MadLittleMods Sep 27, 2022
b7368ba
Explain what passing None does
MadLittleMods Sep 27, 2022
4823bb2
Only explain what's necessary to understand current_depth
MadLittleMods Sep 27, 2022
829cef8
Explain what the depth correlates to
MadLittleMods Sep 27, 2022
db90283
More accurate descroption of get_backfill_points_in_room
MadLittleMods Sep 27, 2022
aba8b0f
Remove extra context that describes our usage
MadLittleMods Sep 27, 2022
e727a31
Merge branch 'develop' into madlittlemods/backfill-points-filtering-a…
MadLittleMods Sep 27, 2022
379ccb4
Point at specific event
MadLittleMods Sep 28, 2022
21f5933
Start with a one line description
MadLittleMods Sep 28, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/13879.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Only pull relevant backfill points from the database based on the current depth and limit (instead of all) every time we want to `/backfill`.
100 changes: 58 additions & 42 deletions synapse/handlers/federation.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
from unpaddedbase64 import decode_base64

from synapse import event_auth
from synapse.api.constants import EventContentFields, EventTypes, Membership
from synapse.api.constants import MAX_DEPTH, EventContentFields, EventTypes, Membership
from synapse.api.errors import (
AuthError,
CodeMessageException,
Expand Down Expand Up @@ -209,7 +209,7 @@ async def _maybe_backfill_inner(
current_depth: int,
limit: int,
*,
processing_start_time: int,
processing_start_time: Optional[int],
MadLittleMods marked this conversation as resolved.
Show resolved Hide resolved
) -> bool:
"""
Checks whether the `current_depth` is at or approaching any backfill
Expand All @@ -226,15 +226,31 @@ async def _maybe_backfill_inner(
"""
backwards_extremities = [
_BackfillPoint(event_id, depth, _BackfillPointType.BACKWARDS_EXTREMITY)
for event_id, depth in await self.store.get_backfill_points_in_room(room_id)
for event_id, depth in await self.store.get_backfill_points_in_room(
room_id=room_id,
current_depth=current_depth,
# We only need to end up with 5 extremities combined with the
# insertion event extremities to make the `/backfill` request
# but fetch an order of magnitude more to make sure there is
# enough even after we filter them by whether visible in the
# history. This isn't fool-proof as all backfill points within
# our limit could be filtered out but seems like a good amount
# to try with at least.
limit=50,
richvdh marked this conversation as resolved.
Show resolved Hide resolved
)
]

insertion_events_to_be_backfilled: List[_BackfillPoint] = []
if self.hs.config.experimental.msc2716_enabled:
insertion_events_to_be_backfilled = [
_BackfillPoint(event_id, depth, _BackfillPointType.INSERTION_PONT)
for event_id, depth in await self.store.get_insertion_event_backward_extremities_in_room(
room_id
room_id=room_id,
current_depth=current_depth,
# We only need to end up with 5 extremities combined with
# the backfill points to make the `/backfill` request ...
# (see the other comment above for more context).
limit=50,
)
]
logger.debug(
Expand All @@ -243,10 +259,6 @@ async def _maybe_backfill_inner(
insertion_events_to_be_backfilled,
)

if not backwards_extremities and not insertion_events_to_be_backfilled:
logger.debug("Not backfilling as no extremeties found.")
return False

# we now have a list of potential places to backpaginate from. We prefer to
# start with the most recent (ie, max depth), so let's sort the list.
sorted_backfill_points: List[_BackfillPoint] = sorted(
Copy link
Member

Choose a reason for hiding this comment

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

we could maybe use heapq.merge if both these lists are sorted?

Copy link
Contributor Author

@MadLittleMods MadLittleMods Sep 27, 2022

Choose a reason for hiding this comment

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

@squahtx mentioned this in #13635 (comment) when I asked about any potential two already sorted lists optimizations. Do we want to do this though?

In this PR, we're also adding the limit so we're only sorting 100 events max, I am leaning towards keeping it simple or at least more familiar to the rest of the Synapse code 🤷 - No benchmarks done though and I don't have a reference for how common/known heapq.merge is besides the two usages in Synapse already.

Copy link
Member

Choose a reason for hiding this comment

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

🤷

Expand All @@ -267,6 +279,32 @@ async def _maybe_backfill_inner(
sorted_backfill_points,
)

# If we have no backfill points lower than the `current_depth` then
# either we can a) bail or b) still attempt to backfill. We opt to try
# backfilling anyway just in case we do get relevant events.
if not sorted_backfill_points and current_depth != MAX_DEPTH:
logger.debug(
"_maybe_backfill_inner: all backfill points are *after* current depth. Backfilling anyway."
MadLittleMods marked this conversation as resolved.
Show resolved Hide resolved
)
return await self._maybe_backfill_inner(
room_id=room_id,
# We use `MAX_DEPTH` so that we find all backfill points next
# time (all events are below the `MAX_DEPTH`)
current_depth=MAX_DEPTH,
limit=limit,
# We don't want to another timing observation from this nested
# recursive call. The top-most call can record the time overall
# otherwise the smaller one will throw off the results.
processing_start_time=None,
)
elif not sorted_backfill_points and current_depth == MAX_DEPTH:
MadLittleMods marked this conversation as resolved.
Show resolved Hide resolved
# Even after trying again with `MAX_DEPTH`, we didn't find any
MadLittleMods marked this conversation as resolved.
Show resolved Hide resolved
# backward extremities to backfill from.
logger.debug(
"_maybe_backfill_inner: Not backfilling as no backward extremeties found."
)
return False

# If we're approaching an extremity we trigger a backfill, otherwise we
# no-op.
#
Expand All @@ -280,43 +318,16 @@ async def _maybe_backfill_inner(
# XXX: shouldn't we do this *after* the filter by depth below? Again, we don't
# care about events that have happened after our current position.
#
MadLittleMods marked this conversation as resolved.
Show resolved Hide resolved
max_depth = sorted_backfill_points[0].depth
if current_depth - 2 * limit > max_depth:
max_depth_of_backfill_points = sorted_backfill_points[0].depth
if current_depth - 2 * limit > max_depth_of_backfill_points:
logger.debug(
"Not backfilling as we don't need to. %d < %d - 2 * %d",
max_depth,
max_depth_of_backfill_points,
current_depth,
limit,
)
return False

# We ignore extremities that have a greater depth than our current depth
# as:
# 1. we don't really care about getting events that have happened
# after our current position; and
# 2. we have likely previously tried and failed to backfill from that
# extremity, so to avoid getting "stuck" requesting the same
# backfill repeatedly we drop those extremities.
#
# However, we need to check that the filtered extremities are non-empty.
# If they are empty then either we can a) bail or b) still attempt to
# backfill. We opt to try backfilling anyway just in case we do get
# relevant events.
#
filtered_sorted_backfill_points = [
t for t in sorted_backfill_points if t.depth <= current_depth
]
if filtered_sorted_backfill_points:
logger.debug(
"_maybe_backfill_inner: backfill points before current depth: %s",
filtered_sorted_backfill_points,
)
sorted_backfill_points = filtered_sorted_backfill_points
else:
logger.debug(
"_maybe_backfill_inner: all backfill points are *after* current depth. Backfilling anyway."
)

# For performance's sake, we only want to paginate from a particular extremity
# if we can actually see the events we'll get. Otherwise, we'd just spend a lot
# of resources to get redacted events. We check each extremity in turn and
Expand Down Expand Up @@ -450,10 +461,15 @@ async def try_backfill(domains: Collection[str]) -> bool:

return False

processing_end_time = self.clock.time_msec()
backfill_processing_before_timer.observe(
(processing_end_time - processing_start_time) / 1000
)
# If we have the `processing_start_time`, then we can make an
# observation. We wouldn't have the `processing_start_time` in the case
# where `_maybe_backfill_inner` is recursively called to find any
# backfill points regardless of `current_depth`.
if processing_start_time is not None:
processing_end_time = self.clock.time_msec()
backfill_processing_before_timer.observe(
(processing_end_time - processing_start_time) / 1000
)

success = await try_backfill(likely_domains)
if success:
Expand Down
94 changes: 84 additions & 10 deletions synapse/storage/databases/main/event_federation.py
Original file line number Diff line number Diff line change
Expand Up @@ -726,17 +726,39 @@ def _get_auth_chain_difference_txn(
async def get_backfill_points_in_room(
self,
room_id: str,
current_depth: int,
limit: int,
) -> List[Tuple[str, int]]:
"""
Gets the oldest events(backwards extremities) in the room along with the
richvdh marked this conversation as resolved.
Show resolved Hide resolved
approximate depth. Sorted by depth, highest to lowest (descending).
approximate depth. Sorted by depth, highest to lowest (descending) so the closest
events to the `current_depth` are first in the list.

We use this function so that we can compare and see if a client's
`current_depth` at their current scrollback is within pagination range
of the event extremities. If the `current_depth` is close to the depth
MadLittleMods marked this conversation as resolved.
Show resolved Hide resolved
of given oldest event, we can trigger a backfill.

We ignore extremities that have a greater depth than our `current_depth`
as:
richvdh marked this conversation as resolved.
Show resolved Hide resolved
1. we don't really care about getting events that have happened
after our current position; and
2. by the nature of paginating and scrolling back, we have likely
previously tried and failed to backfill from that extremity, so
to avoid getting "stuck" requesting the same backfill repeatedly
we drop those extremities.

Args:
room_id: Room where we want to find the oldest events
current_depth: The depth at the users current scrollback position
MadLittleMods marked this conversation as resolved.
Show resolved Hide resolved
because we only care about finding events older than the given
`current_depth` when scrolling and paginating backwards.
MadLittleMods marked this conversation as resolved.
Show resolved Hide resolved
limit: The max number of backfill points to return

Returns:
List of (event_id, depth) tuples. Sorted by depth, highest to lowest
(descending)
(descending) so the closest events to the `current_depth` are first
in the list.
"""

def get_backfill_points_in_room_txn(
Expand Down Expand Up @@ -784,6 +806,17 @@ def get_backfill_points_in_room_txn(
* necessarily safe to assume that it will have been completed.
*/
AND edge.is_state is ? /* False */
/**
* We only want backwards extremities that are older than or at
* the same position of the given `current_depth` (where older
* means less than the given depth) because we're looking backwards
* from the `current_depth` when backfilling.
*
* current_depth (ignore events that come after this, ignore 2-4)
* |
* <oldest-in-time> [0]<--[1]▼<--[2]<--[3]<--[4] <newest-in-time>
MadLittleMods marked this conversation as resolved.
Show resolved Hide resolved
*/
AND event.depth <= ? /* current_depth */
/**
* Exponential back-off (up to the upper bound) so we don't retry the
* same backfill point over and over. ex. 2hr, 4hr, 8hr, 16hr, etc.
Expand All @@ -798,11 +831,13 @@ def get_backfill_points_in_room_txn(
OR ? /* current_time */ >= failed_backfill_attempt_info.last_attempt_ts + /*least*/%s((1 << failed_backfill_attempt_info.num_attempts) * ? /* step */, ? /* upper bound */)
)
/**
* Sort from highest to the lowest depth. Then tie-break on
* alphabetical order of the event_ids so we get a consistent
* ordering which is nice when asserting things in tests.
* Sort from highest (closest to the `current_depth`) to the lowest depth
* because the closest are most relevant to backfill from first.
* Then tie-break on alphabetical order of the event_ids so we get a
* consistent ordering which is nice when asserting things in tests.
*/
ORDER BY event.depth DESC, backward_extrem.event_id DESC
LIMIT ?
"""

if isinstance(self.database_engine, PostgresEngine):
Expand All @@ -817,9 +852,11 @@ def get_backfill_points_in_room_txn(
(
room_id,
False,
current_depth,
self._clock.time_msec(),
1000 * BACKFILL_EVENT_EXPONENTIAL_BACKOFF_STEP_SECONDS,
1000 * BACKFILL_EVENT_BACKOFF_UPPER_BOUND_SECONDS,
limit,
),
)

Expand All @@ -835,18 +872,40 @@ def get_backfill_points_in_room_txn(
async def get_insertion_event_backward_extremities_in_room(
self,
room_id: str,
current_depth: int,
limit: int,
) -> List[Tuple[str, int]]:
"""
Get the insertion events we know about that we haven't backfilled yet
along with the approximate depth. Sorted by depth, highest to lowest
(descending).
(descending) so the closest events to the `current_depth` are first
in the list.

We use this function so that we can compare and see if someones
`current_depth` at their current scrollback is within pagination range
of the insertion event. If the `current_depth` is close to the depth
of the given insertion event, we can trigger a backfill.

We ignore insertion events that have a greater depth than our `current_depth`
as:
1. we don't really care about getting events that have happened
after our current position; and
2. by the nature of paginating and scrolling back, we have likely
previously tried and failed to backfill from that insertion event, so
to avoid getting "stuck" requesting the same backfill repeatedly
we drop those insertion event.

Args:
room_id: Room where we want to find the oldest events
current_depth: The depth at the users current scrollback position because
we only care about finding events older than the given
`current_depth` when scrolling and paginating backwards.
limit: The max number of insertion event extremities to return

MadLittleMods marked this conversation as resolved.
Show resolved Hide resolved
Returns:
List of (event_id, depth) tuples. Sorted by depth, highest to lowest
(descending)
(descending) so the closest events to the `current_depth` are first
in the list.
"""

def get_insertion_event_backward_extremities_in_room_txn(
Expand All @@ -869,6 +928,17 @@ def get_insertion_event_backward_extremities_in_room_txn(
AND failed_backfill_attempt_info.event_id = insertion_event_extremity.event_id
WHERE
insertion_event_extremity.room_id = ?
/**
* We only want extremities that are older than or at
* the same position of the given `current_depth` (where older
* means less than the given depth) because we're looking backwards
* from the `current_depth` when backfilling.
*
* current_depth (ignore events that come after this, ignore 2-4)
* |
* <oldest-in-time> [0]<--[1]▼<--[2]<--[3]<--[4] <newest-in-time>
*/
AND event.depth <= ? /* current_depth */
/**
* Exponential back-off (up to the upper bound) so we don't retry the
* same backfill point over and over. ex. 2hr, 4hr, 8hr, 16hr, etc
Expand All @@ -883,11 +953,13 @@ def get_insertion_event_backward_extremities_in_room_txn(
OR ? /* current_time */ >= failed_backfill_attempt_info.last_attempt_ts + /*least*/%s((1 << failed_backfill_attempt_info.num_attempts) * ? /* step */, ? /* upper bound */)
)
/**
* Sort from highest to the lowest depth. Then tie-break on
* alphabetical order of the event_ids so we get a consistent
* ordering which is nice when asserting things in tests.
* Sort from highest (closest to the `current_depth`) to the lowest depth
* because the closest are most relevant to backfill from first.
* Then tie-break on alphabetical order of the event_ids so we get a
* consistent ordering which is nice when asserting things in tests.
*/
ORDER BY event.depth DESC, insertion_event_extremity.event_id DESC
LIMIT ?
"""

if isinstance(self.database_engine, PostgresEngine):
Expand All @@ -901,9 +973,11 @@ def get_insertion_event_backward_extremities_in_room_txn(
sql % (least_function,),
(
room_id,
current_depth,
self._clock.time_msec(),
1000 * BACKFILL_EVENT_EXPONENTIAL_BACKOFF_STEP_SECONDS,
1000 * BACKFILL_EVENT_BACKOFF_UPPER_BOUND_SECONDS,
limit,
),
)
return cast(List[Tuple[str, int]], txn.fetchall())
Expand Down
Loading