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

Commit 65d0386

Browse files
authored
Always notify replication when a stream advances (#14877)
This ensures that all other workers are told about stream updates in a timely manner, without having to remember to manually poke replication.
1 parent cf18fea commit 65d0386

19 files changed

+104
-29
lines changed

changelog.d/14877.misc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Always notify replication when a stream advances automatically.

synapse/_scripts/synapse_port_db.py

+4
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
make_deferred_yieldable,
5252
run_in_background,
5353
)
54+
from synapse.notifier import ReplicationNotifier
5455
from synapse.storage.database import DatabasePool, LoggingTransaction, make_conn
5556
from synapse.storage.databases.main import PushRuleStore
5657
from synapse.storage.databases.main.account_data import AccountDataWorkerStore
@@ -260,6 +261,9 @@ def get_instance_name(self) -> str:
260261
def should_send_federation(self) -> bool:
261262
return False
262263

264+
def get_replication_notifier(self) -> ReplicationNotifier:
265+
return ReplicationNotifier()
266+
263267

264268
class Porter:
265269
def __init__(

synapse/notifier.py

+26-5
Original file line numberDiff line numberDiff line change
@@ -226,8 +226,7 @@ def __init__(self, hs: "HomeServer"):
226226
self.store = hs.get_datastores().main
227227
self.pending_new_room_events: List[_PendingRoomEventEntry] = []
228228

229-
# Called when there are new things to stream over replication
230-
self.replication_callbacks: List[Callable[[], None]] = []
229+
self._replication_notifier = hs.get_replication_notifier()
231230
self._new_join_in_room_callbacks: List[Callable[[str, str], None]] = []
232231

233232
self._federation_client = hs.get_federation_http_client()
@@ -279,7 +278,7 @@ def add_replication_callback(self, cb: Callable[[], None]) -> None:
279278
it needs to do any asynchronous work, a background thread should be started and
280279
wrapped with run_as_background_process.
281280
"""
282-
self.replication_callbacks.append(cb)
281+
self._replication_notifier.add_replication_callback(cb)
283282

284283
def add_new_join_in_room_callback(self, cb: Callable[[str, str], None]) -> None:
285284
"""Add a callback that will be called when a user joins a room.
@@ -741,8 +740,7 @@ def _user_joined_room(self, user_id: str, room_id: str) -> None:
741740

742741
def notify_replication(self) -> None:
743742
"""Notify the any replication listeners that there's a new event"""
744-
for cb in self.replication_callbacks:
745-
cb()
743+
self._replication_notifier.notify_replication()
746744

747745
def notify_user_joined_room(self, event_id: str, room_id: str) -> None:
748746
for cb in self._new_join_in_room_callbacks:
@@ -759,3 +757,26 @@ def notify_remote_server_up(self, server: str) -> None:
759757
# Tell the federation client about the fact the server is back up, so
760758
# that any in flight requests can be immediately retried.
761759
self._federation_client.wake_destination(server)
760+
761+
762+
@attr.s(auto_attribs=True)
763+
class ReplicationNotifier:
764+
"""Tracks callbacks for things that need to know about stream changes.
765+
766+
This is separate from the notifier to avoid circular dependencies.
767+
"""
768+
769+
_replication_callbacks: List[Callable[[], None]] = attr.Factory(list)
770+
771+
def add_replication_callback(self, cb: Callable[[], None]) -> None:
772+
"""Add a callback that will be called when some new data is available.
773+
Callback is not given any arguments. It should *not* return a Deferred - if
774+
it needs to do any asynchronous work, a background thread should be started and
775+
wrapped with run_as_background_process.
776+
"""
777+
self._replication_callbacks.append(cb)
778+
779+
def notify_replication(self) -> None:
780+
"""Notify the any replication listeners that there's a new event"""
781+
for cb in self._replication_callbacks:
782+
cb()

synapse/server.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@
107107
from synapse.http.matrixfederationclient import MatrixFederationHttpClient
108108
from synapse.metrics.common_usage_metrics import CommonUsageMetricsManager
109109
from synapse.module_api import ModuleApi
110-
from synapse.notifier import Notifier
110+
from synapse.notifier import Notifier, ReplicationNotifier
111111
from synapse.push.bulk_push_rule_evaluator import BulkPushRuleEvaluator
112112
from synapse.push.pusherpool import PusherPool
113113
from synapse.replication.tcp.client import ReplicationDataHandler
@@ -389,6 +389,10 @@ def get_federation_server(self) -> FederationServer:
389389
def get_notifier(self) -> Notifier:
390390
return Notifier(self)
391391

392+
@cache_in_self
393+
def get_replication_notifier(self) -> ReplicationNotifier:
394+
return ReplicationNotifier()
395+
392396
@cache_in_self
393397
def get_auth(self) -> Auth:
394398
return Auth(self)

synapse/storage/databases/main/account_data.py

+2
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ def __init__(
7575
self._account_data_id_gen = MultiWriterIdGenerator(
7676
db_conn=db_conn,
7777
db=database,
78+
notifier=hs.get_replication_notifier(),
7879
stream_name="account_data",
7980
instance_name=self._instance_name,
8081
tables=[
@@ -95,6 +96,7 @@ def __init__(
9596
# SQLite).
9697
self._account_data_id_gen = StreamIdGenerator(
9798
db_conn,
99+
hs.get_replication_notifier(),
98100
"room_account_data",
99101
"stream_id",
100102
extra_tables=[("room_tags_revisions", "stream_id")],

synapse/storage/databases/main/cache.py

+1
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ def __init__(
7575
self._cache_id_gen = MultiWriterIdGenerator(
7676
db_conn,
7777
database,
78+
notifier=hs.get_replication_notifier(),
7879
stream_name="caches",
7980
instance_name=hs.get_instance_name(),
8081
tables=[

synapse/storage/databases/main/deviceinbox.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ def __init__(
9191
MultiWriterIdGenerator(
9292
db_conn=db_conn,
9393
db=database,
94+
notifier=hs.get_replication_notifier(),
9495
stream_name="to_device",
9596
instance_name=self._instance_name,
9697
tables=[("device_inbox", "instance_name", "stream_id")],
@@ -101,7 +102,7 @@ def __init__(
101102
else:
102103
self._can_write_to_device = True
103104
self._device_inbox_id_gen = StreamIdGenerator(
104-
db_conn, "device_inbox", "stream_id"
105+
db_conn, hs.get_replication_notifier(), "device_inbox", "stream_id"
105106
)
106107

107108
max_device_inbox_id = self._device_inbox_id_gen.get_current_token()

synapse/storage/databases/main/devices.py

+1
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ def __init__(
9292
# class below that is used on the main process.
9393
self._device_list_id_gen: AbstractStreamIdTracker = StreamIdGenerator(
9494
db_conn,
95+
hs.get_replication_notifier(),
9596
"device_lists_stream",
9697
"stream_id",
9798
extra_tables=[

synapse/storage/databases/main/end_to_end_keys.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -1181,7 +1181,10 @@ def __init__(
11811181
super().__init__(database, db_conn, hs)
11821182

11831183
self._cross_signing_id_gen = StreamIdGenerator(
1184-
db_conn, "e2e_cross_signing_keys", "stream_id"
1184+
db_conn,
1185+
hs.get_replication_notifier(),
1186+
"e2e_cross_signing_keys",
1187+
"stream_id",
11851188
)
11861189

11871190
async def set_e2e_device_keys(

synapse/storage/databases/main/events_worker.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ def __init__(
191191
self._stream_id_gen = MultiWriterIdGenerator(
192192
db_conn=db_conn,
193193
db=database,
194+
notifier=hs.get_replication_notifier(),
194195
stream_name="events",
195196
instance_name=hs.get_instance_name(),
196197
tables=[("events", "instance_name", "stream_ordering")],
@@ -200,6 +201,7 @@ def __init__(
200201
self._backfill_id_gen = MultiWriterIdGenerator(
201202
db_conn=db_conn,
202203
db=database,
204+
notifier=hs.get_replication_notifier(),
203205
stream_name="backfill",
204206
instance_name=hs.get_instance_name(),
205207
tables=[("events", "instance_name", "stream_ordering")],
@@ -217,12 +219,14 @@ def __init__(
217219
# SQLite).
218220
self._stream_id_gen = StreamIdGenerator(
219221
db_conn,
222+
hs.get_replication_notifier(),
220223
"events",
221224
"stream_ordering",
222225
is_writer=hs.get_instance_name() in hs.config.worker.writers.events,
223226
)
224227
self._backfill_id_gen = StreamIdGenerator(
225228
db_conn,
229+
hs.get_replication_notifier(),
226230
"events",
227231
"stream_ordering",
228232
step=-1,
@@ -300,6 +304,7 @@ def get_chain_id_txn(txn: Cursor) -> int:
300304
self._un_partial_stated_events_stream_id_gen = MultiWriterIdGenerator(
301305
db_conn=db_conn,
302306
db=database,
307+
notifier=hs.get_replication_notifier(),
303308
stream_name="un_partial_stated_event_stream",
304309
instance_name=hs.get_instance_name(),
305310
tables=[
@@ -311,7 +316,10 @@ def get_chain_id_txn(txn: Cursor) -> int:
311316
)
312317
else:
313318
self._un_partial_stated_events_stream_id_gen = StreamIdGenerator(
314-
db_conn, "un_partial_stated_event_stream", "stream_id"
319+
db_conn,
320+
hs.get_replication_notifier(),
321+
"un_partial_stated_event_stream",
322+
"stream_id",
315323
)
316324

317325
def get_un_partial_stated_events_token(self) -> int:

synapse/storage/databases/main/presence.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ def __init__(
7777
self._presence_id_gen = MultiWriterIdGenerator(
7878
db_conn=db_conn,
7979
db=database,
80+
notifier=hs.get_replication_notifier(),
8081
stream_name="presence_stream",
8182
instance_name=self._instance_name,
8283
tables=[("presence_stream", "instance_name", "stream_id")],
@@ -85,7 +86,7 @@ def __init__(
8586
)
8687
else:
8788
self._presence_id_gen = StreamIdGenerator(
88-
db_conn, "presence_stream", "stream_id"
89+
db_conn, hs.get_replication_notifier(), "presence_stream", "stream_id"
8990
)
9091

9192
self.hs = hs

synapse/storage/databases/main/push_rule.py

+1
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ def __init__(
118118
# class below that is used on the main process.
119119
self._push_rules_stream_id_gen: AbstractStreamIdTracker = StreamIdGenerator(
120120
db_conn,
121+
hs.get_replication_notifier(),
121122
"push_rules_stream",
122123
"stream_id",
123124
is_writer=hs.config.worker.worker_app is None,

synapse/storage/databases/main/pusher.py

+1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ def __init__(
6262
# class below that is used on the main process.
6363
self._pushers_id_gen: AbstractStreamIdTracker = StreamIdGenerator(
6464
db_conn,
65+
hs.get_replication_notifier(),
6566
"pushers",
6667
"id",
6768
extra_tables=[("deleted_pushers", "stream_id")],

synapse/storage/databases/main/receipts.py

+2
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ def __init__(
7373
self._receipts_id_gen = MultiWriterIdGenerator(
7474
db_conn=db_conn,
7575
db=database,
76+
notifier=hs.get_replication_notifier(),
7677
stream_name="receipts",
7778
instance_name=self._instance_name,
7879
tables=[("receipts_linearized", "instance_name", "stream_id")],
@@ -91,6 +92,7 @@ def __init__(
9192
# SQLite).
9293
self._receipts_id_gen = StreamIdGenerator(
9394
db_conn,
95+
hs.get_replication_notifier(),
9496
"receipts_linearized",
9597
"stream_id",
9698
is_writer=hs.get_instance_name() in hs.config.worker.writers.receipts,

synapse/storage/databases/main/room.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ def __init__(
126126
self._un_partial_stated_rooms_stream_id_gen = MultiWriterIdGenerator(
127127
db_conn=db_conn,
128128
db=database,
129+
notifier=hs.get_replication_notifier(),
129130
stream_name="un_partial_stated_room_stream",
130131
instance_name=self._instance_name,
131132
tables=[
@@ -137,7 +138,10 @@ def __init__(
137138
)
138139
else:
139140
self._un_partial_stated_rooms_stream_id_gen = StreamIdGenerator(
140-
db_conn, "un_partial_stated_room_stream", "stream_id"
141+
db_conn,
142+
hs.get_replication_notifier(),
143+
"un_partial_stated_room_stream",
144+
"stream_id",
141145
)
142146

143147
async def store_room(

synapse/storage/util/id_generators.py

+24-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from contextlib import contextmanager
2121
from types import TracebackType
2222
from typing import (
23+
TYPE_CHECKING,
2324
AsyncContextManager,
2425
ContextManager,
2526
Dict,
@@ -49,6 +50,9 @@
4950
from synapse.storage.types import Cursor
5051
from synapse.storage.util.sequence import PostgresSequenceGenerator
5152

53+
if TYPE_CHECKING:
54+
from synapse.notifier import ReplicationNotifier
55+
5256
logger = logging.getLogger(__name__)
5357

5458

@@ -182,6 +186,7 @@ class StreamIdGenerator(AbstractStreamIdGenerator):
182186
def __init__(
183187
self,
184188
db_conn: LoggingDatabaseConnection,
189+
notifier: "ReplicationNotifier",
185190
table: str,
186191
column: str,
187192
extra_tables: Iterable[Tuple[str, str]] = (),
@@ -205,6 +210,8 @@ def __init__(
205210
# The key and values are the same, but we never look at the values.
206211
self._unfinished_ids: OrderedDict[int, int] = OrderedDict()
207212

213+
self._notifier = notifier
214+
208215
def advance(self, instance_name: str, new_id: int) -> None:
209216
# Advance should never be called on a writer instance, only over replication
210217
if self._is_writer:
@@ -227,6 +234,8 @@ def manager() -> Generator[int, None, None]:
227234
with self._lock:
228235
self._unfinished_ids.pop(next_id)
229236

237+
self._notifier.notify_replication()
238+
230239
return _AsyncCtxManagerWrapper(manager())
231240

232241
def get_next_mult(self, n: int) -> AsyncContextManager[Sequence[int]]:
@@ -250,6 +259,8 @@ def manager() -> Generator[Sequence[int], None, None]:
250259
for next_id in next_ids:
251260
self._unfinished_ids.pop(next_id)
252261

262+
self._notifier.notify_replication()
263+
253264
return _AsyncCtxManagerWrapper(manager())
254265

255266
def get_current_token(self) -> int:
@@ -296,6 +307,7 @@ def __init__(
296307
self,
297308
db_conn: LoggingDatabaseConnection,
298309
db: DatabasePool,
310+
notifier: "ReplicationNotifier",
299311
stream_name: str,
300312
instance_name: str,
301313
tables: List[Tuple[str, str, str]],
@@ -304,6 +316,7 @@ def __init__(
304316
positive: bool = True,
305317
) -> None:
306318
self._db = db
319+
self._notifier = notifier
307320
self._stream_name = stream_name
308321
self._instance_name = instance_name
309322
self._positive = positive
@@ -535,7 +548,9 @@ def get_next(self) -> AsyncContextManager[int]:
535548
# Cast safety: the second argument to _MultiWriterCtxManager, multiple_ids,
536549
# controls the return type. If `None` or omitted, the context manager yields
537550
# a single integer stream_id; otherwise it yields a list of stream_ids.
538-
return cast(AsyncContextManager[int], _MultiWriterCtxManager(self))
551+
return cast(
552+
AsyncContextManager[int], _MultiWriterCtxManager(self, self._notifier)
553+
)
539554

540555
def get_next_mult(self, n: int) -> AsyncContextManager[List[int]]:
541556
# If we have a list of instances that are allowed to write to this
@@ -544,7 +559,10 @@ def get_next_mult(self, n: int) -> AsyncContextManager[List[int]]:
544559
raise Exception("Tried to allocate stream ID on non-writer")
545560

546561
# Cast safety: see get_next.
547-
return cast(AsyncContextManager[List[int]], _MultiWriterCtxManager(self, n))
562+
return cast(
563+
AsyncContextManager[List[int]],
564+
_MultiWriterCtxManager(self, self._notifier, n),
565+
)
548566

549567
def get_next_txn(self, txn: LoggingTransaction) -> int:
550568
"""
@@ -563,6 +581,7 @@ def get_next_txn(self, txn: LoggingTransaction) -> int:
563581

564582
txn.call_after(self._mark_id_as_finished, next_id)
565583
txn.call_on_exception(self._mark_id_as_finished, next_id)
584+
txn.call_after(self._notifier.notify_replication)
566585

567586
# Update the `stream_positions` table with newly updated stream
568587
# ID (unless self._writers is not set in which case we don't
@@ -787,6 +806,7 @@ class _MultiWriterCtxManager:
787806
"""Async context manager returned by MultiWriterIdGenerator"""
788807

789808
id_gen: MultiWriterIdGenerator
809+
notifier: "ReplicationNotifier"
790810
multiple_ids: Optional[int] = None
791811
stream_ids: List[int] = attr.Factory(list)
792812

@@ -814,6 +834,8 @@ async def __aexit__(
814834
for i in self.stream_ids:
815835
self.id_gen._mark_id_as_finished(i)
816836

837+
self.notifier.notify_replication()
838+
817839
if exc_type is not None:
818840
return False
819841

0 commit comments

Comments
 (0)