Backfill from nearby points past pagination token#19611
Conversation
a09300d to
633f923
Compare
633f923 to
85f3bc2
Compare
| // TODO: Backfill test to make sure we backfill from forks when viewing history (see | ||
| // docstring above). | ||
| // | ||
| // 1. Alice (hs1, engineered homeserver) creates a room with events A, B | ||
| // 1. Bob (hs2) joins the room | ||
| // 1. Bob leaves the room | ||
| // 1. Alice creates a fork from A with some history (1, 2, 3) and connects it back with a new event C | ||
| // 1. Bob joins back | ||
| // 1. Bob paginates `/messages?dir=b&from=<token-after-b>` | ||
| // 1. Ensure Bob sees events: B, 2, 1, A | ||
| // | ||
| // 1 <--- 2 <----- 3 | ||
| // / \ | ||
| // A <------- B ▲ <--- C <-- D | ||
| // | | ||
| // Paginate backwards from this point | ||
| // t.Run("Backfill from nearby backward extremities past token (fork)", func(t *testing.T) { |
There was a problem hiding this comment.
Something for a future PR
Only thing to check here is if the premise/plan makes sense
There was a problem hiding this comment.
I believe this makes sense. Before the changes in this PR, we wouldn't pick up C as a backfill point (and thus miss events (2,1)), is that right?
There was a problem hiding this comment.
Correct 👍 (whatever event that comes before bobs second join event)
| // These utilities match what we're using in the Complement repo (see | ||
| // `matrix-org/complement` -> `tests/csapi/room_messages_test.go`) |
There was a problem hiding this comment.
No need to scrutinize the following utilities that hard. They already exist in Complement, see https://github.com/matrix-org/complement/blob/3adcab373fa98cad2b0a6979a8f11a679a6f3447/tests/csapi/room_messages_test.go#L485-L637
getStateID is "new" but also part of matrix-org/complement#801
Convenience-wise, it would be nice if we could import them but I'd rather duplicate than create a bad abstraction in Complement.
There was a problem hiding this comment.
It does feel a little too test-specific to expose publicly.
Basically reverting back to how this test looked in matrix-org/synapse#11027 This test was updated in matrix-org/synapse#12191 without explanation. And it worked at this point because it didn't actually find any backfill points as no backfill points are <= 1 (`current_depth`).
307d137 to
2be7684
Compare
|
|
||
| self.assertEqual(sg, sg2) | ||
|
|
||
| def test_backfill_with_many_backward_extremities(self) -> None: |
There was a problem hiding this comment.
Removed as ultimately, this test isn't useful in it's current form.
The reason it was looked at is because it started failing with this PR. Because it tries to backfill with current_depth = 1, previously, it found nothing and did nothing. Because this PR changes the behavior to look for backfill points <= 101 (current_depth + limit), we actually find backfill points and try to make an actual backfill request. For some reason this fails (.well-known DNS problems) and I don't understand why that's a problem. I would expect this kind of problem to just be caught by our try/except but it seems like it spawns some sort of DNS process that doesn't get cleaned up or something.
poetry run trial .... test failure
SYNAPSE_TEST_LOG_LEVEL=DEBUG poetry run trial tests.handlers.test_federation.FederationTestCase.test_backfill_with_many_backward_extremities
tests.handlers.test_federation
FederationTestCase
test_backfill_with_many_backward_extremities ... [FAIL]
[ERROR]
[ERROR]
===============================================================================
[FAIL]
Traceback (most recent call last):
File "/home/eric/Documents/github/element/synapse/tests/handlers/test_federation.py", line 325, in test_backfill_with_many_backward_extremities
self.get_success(
File "/home/eric/Documents/github/element/synapse/tests/unittest.py", line 742, in get_success
return self.successResultOf(deferred)
File "/home/eric/.cache/pypoetry/virtualenvs/matrix-synapse-xCtC9ulO-py3.14/lib/python3.14/site-packages/twisted/trial/_synctest.py", line 723, in successResultOf
self.fail(
twisted.trial.unittest.FailTest: Success result expected on <Deferred at 0x7f3469234450>, found no result instead
tests.handlers.test_federation.FederationTestCase.test_backfill_with_many_backward_extremities
===============================================================================
[ERROR]
Traceback (most recent call last):
Failure: twisted.trial.util.DirtyReactorAggregateError: Reactor was unclean.
DelayedCalls: (set twisted.internet.base.DelayedCall.debug = True to debug)
<DelayedCall 0x7f34690afc20 [0.9999024868011475s] called=0 cancelled=0 DNSMixin._clearFailed(<Deferred at 0x7f34691d6450>, 27696)>
<DelayedCall 0x7f34690fd4f0 [59.990769147872925s] called=0 cancelled=0 Resolver.maybeParseConfig()>
tests.handlers.test_federation.FederationTestCase.test_backfill_with_many_backward_extremities
===============================================================================
[ERROR]
Traceback (most recent call last):
Failure: twisted.trial.util.DirtyReactorAggregateError: Reactor was unclean.
Selectables:
<<class 'twisted.names.dns.DNSDatagramProtocol'> on 1562>
tests.handlers.test_federation.FederationTestCase.test_backfill_with_many_backward_extremities
-------------------------------------------------------------------------------
Ran 1 tests in 0.488s
FAILED (failures=1, errors=2)
Exception ignored while closing generator <coroutine object FederationHandler.maybe_backfill at 0x7f346941f340>:
Traceback (most recent call last):
File "/home/eric/Documents/github/element/synapse/synapse/logging/opentracing.py", line 949, in _wrapper
return await func(*args, **kwargs)
File "/home/eric/Documents/github/element/synapse/synapse/logging/opentracing.py", line 949, in _wrapper
return await func(*args, **kwargs)
RuntimeError: coroutine ignored GeneratorExitOverall, we could keep this test if we figure out the DNS problem (I wouldn't waste time on it if you don't know off the top of your head).
If we really want to keep this kind of thing around, I did fix this test up in dc66977 by basically reverting it back to how it was introduced in matrix-org/synapse#11027. It does stress the backfill/extremity selection but since it doesn't actually make any backfill requests, I don't think it's actually useful and is why I decided to remove the test overall.
To document the history, this test was updated in matrix-org/synapse#12191 (without explanation). Given I was able to revert it back to how it was before, I don't see a need reason this was done. Perhaps they wanted to make it feel more "real".
Also as a note, this test was useful when it was first introduced in matrix-org/synapse#11027 and stressed the maybe_backfill code we cared about but we have since updated to be much smarter with backfill point selection (get_backfill_points_in_room which has its own tests).
There was a problem hiding this comment.
I don't recognise the DirtyReactorAggregateError off the top of my head, so I'm going to trust your judgement on removing the test.
2be7684 to
a40e4cc
Compare
| // after joining but this is valid as well. To illustrate a more real example of | ||
| // this, someone can use `/timestamp_to_event` to jump back in history and | ||
| // `/context` to start paginating history. |
There was a problem hiding this comment.
Added a real example for /timestamp_to_event in matrix-org/complement#853
| // This test lives in our in-repo Complement tests for Synapse because the Matrix spec | ||
| // doesn't have any rules for how a homeserver should backfill. Practically speaking, | ||
| // homeservers that don't do anything for this problem will just hide messages from | ||
| // clients. This underscores the fact why it's necessary for homeservers indicate that | ||
| // there is a gap (using MSC3871) at the very least. |
There was a problem hiding this comment.
^ Explains why we have this test here vs in the Complement repo
…19630) Incorrectly labeled in matrix-org/synapse#13535. `maybe_backfill` already accurately describes `limit` (introduced in matrix-org/synapse#8349) Spotted in #19611 (comment)
…inating backwards despite the spec saying "A token that can be used to paginate forwards with." (#852) Relevant spec: https://spec.matrix.org/v1.17/client-server-api/#get_matrixclientv3roomsroomidcontexteventid There is no functional change to these tests. This is spawning from [using `/context` in another Complement test](element-hq/synapse#19611) and noticing this oddity.
There was a problem hiding this comment.
Pull request overview
Adjusts Synapse’s federation backfill heuristics so pagination from tokens near backward extremities (including /context-derived tokens and MSC3871 gap tokens) can still discover and backfill missing history. Adds an in-repo Complement test to cover the real-world /context → /messages pagination scenario.
Changes:
- Pad the depth used to select backfill points (“nearby depth”) so extremities slightly after the pagination token can still be used to backfill into the visible window.
- Introduce a constant for the
/backfillrequest size and use it consistently in the handler. - Add a new in-repo Complement test exercising
/context-token pagination over federation; remove an older Python regression test.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
synapse/storage/databases/main/event_federation.py |
Renames/clarifies the depth parameter (nearby_depth) and documents the padded-depth rationale for selecting backfill points. |
synapse/handlers/federation.py |
Pads the depth passed to get_backfill_points_in_room and uses a shared constant for /backfill size. |
tests/handlers/test_federation.py |
Removes test_backfill_with_many_backward_extremities and its now-unused imports. |
complement/tests/room_messages_test.go |
Adds a Complement test validating back-pagination from a /context token returns historical events over federation. |
changelog.d/19611.bugfix |
Adds a user-facing changelog entry for the backfill/pagination fix. |
Comments suppressed due to low confidence (1)
synapse/storage/databases/main/event_federation.py:1362
- The SQL orders by
backward_extrem.event_id DESC, but the adjacent comment says the tie-break is “alphabetical order”. Either update the comment to match the descending order, or change the query toASCif that’s what you intended.
* Sort from highest (closest to the `nearby_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 ?
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
anoadragon453
left a comment
There was a problem hiding this comment.
So if I understand this correctly, the main change here is moving the depth threshold we use to look for backfill points.
Previously it was just current_depth, now it's current_depth + NUMBER_OF_EVENTS_TO_BACKFILL (100) (we look deeper/newer in the DAG). This helps with a class of edge case where a backfill point that exposes a long chain of events that would otherwise be in the visible window, could be entirely missed.
This doesn't fully fix the gaps problem that MSC3871 aims to solve (clients re-request backfilling of gaps from a certain token) but it is a useful improvement nonetheless.
|
|
||
| self.assertEqual(sg, sg2) | ||
|
|
||
| def test_backfill_with_many_backward_extremities(self) -> None: |
There was a problem hiding this comment.
I don't recognise the DirtyReactorAggregateError off the top of my head, so I'm going to trust your judgement on removing the test.
| // TODO: Backfill test to make sure we backfill from forks when viewing history (see | ||
| // docstring above). | ||
| // | ||
| // 1. Alice (hs1, engineered homeserver) creates a room with events A, B | ||
| // 1. Bob (hs2) joins the room | ||
| // 1. Bob leaves the room | ||
| // 1. Alice creates a fork from A with some history (1, 2, 3) and connects it back with a new event C | ||
| // 1. Bob joins back | ||
| // 1. Bob paginates `/messages?dir=b&from=<token-after-b>` | ||
| // 1. Ensure Bob sees events: B, 2, 1, A | ||
| // | ||
| // 1 <--- 2 <----- 3 | ||
| // / \ | ||
| // A <------- B ▲ <--- C <-- D | ||
| // | | ||
| // Paginate backwards from this point | ||
| // t.Run("Backfill from nearby backward extremities past token (fork)", func(t *testing.T) { |
There was a problem hiding this comment.
I believe this makes sense. Before the changes in this PR, we wouldn't pick up C as a backfill point (and thus miss events (2,1)), is that right?
| // These utilities match what we're using in the Complement repo (see | ||
| // `matrix-org/complement` -> `tests/csapi/room_messages_test.go`) |
There was a problem hiding this comment.
It does feel a little too test-specific to expose publicly.
Co-authored-by: Andrew Morgan <1342360+anoadragon453@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
…f github.com:element-hq/synapse into madlittlemods/backfill-from-nearby-points-past-token
| @@ -0,0 +1 @@ | |||
| Fix Synapse not backfilling new history when attempting to use a pagination token near a backward extremity. | |||
There was a problem hiding this comment.
This doesn't fully fix the gaps problem that MSC3871 aims to solve (clients re-request backfilling of gaps from a certain token) but it is a useful improvement nonetheless.
While this is true, to clarify the actual interaction with MSC3871 here; the pagination tokens returned for the gaps are just before/after the backfill points so without this PR, it wouldn't try to backfill anything. (also mentioned in PR description)
Explained more here:
synapse/complement/tests/room_messages_test.go
Lines 69 to 83 in 2cd1f27
anoadragon453
left a comment
There was a problem hiding this comment.
A well-thought out change with real impact. Nice work! And thanks for explaining it to myself (and others) in such detail.
|
Thanks for the review @anoadragon453 🦍 |
This isn't fixing any particular issue. It's just a follow-up I thought about after merging #19611 since we're now also dealing with backfill points in the nearby range ahead of the `current_depth`. And it's possible that the previous sort could bias to all nearby backfill points ahead of the `current_depth` that don't extend into the visible window of events we're paginating through.
…lement-hq#19630) Incorrectly labeled in matrix-org/synapse#13535. `maybe_backfill` already accurately describes `limit` (introduced in matrix-org/synapse#8349) Spotted in element-hq#19611 (comment)
The juicy details and explanation are in the diff itself. Split out from element-hq#18873 in order to fix paginating from [MSC3871](matrix-org/matrix-spec-proposals#3871) gap tokens actually backfilling history. To be clear, this is a good change to make outside of the [MSC3871](matrix-org/matrix-spec-proposals#3871) use case. For example (as the new Complement test shows), fixes a problem where if you try to paginate `/messages` from tokens returned by `/context`, we could fail to backfill anything new and hide away history. Also fixes matrix-org/complement#853
This isn't fixing any particular issue. It's just a follow-up I thought about after merging element-hq#19611 since we're now also dealing with backfill points in the nearby range ahead of the `current_depth`. And it's possible that the previous sort could bias to all nearby backfill points ahead of the `current_depth` that don't extend into the visible window of events we're paginating through.
Backfill from nearby points past pagination token. The juicy details and explanation are in the diff itself.
Split out from #18873 in order to fix paginating from MSC3871 gap tokens actually backfilling history. To be clear, this is a good change to make outside of the MSC3871 use case. For example (as the new Complement test shows), fixes a problem where if you try to paginate
/messagesfrom tokens returned by/context, we could fail to backfill anything new and hide away history.Also fixes matrix-org/complement#853
Dev notes
Room DAG concept docs:
docs/development/room-dag-concepts.mdPull Request Checklist
EventStoretoEventWorkerStore.".code blocks.