mirror of
https://git.anonymousland.org/anonymousland/synapse.git
synced 2025-05-02 22:14:55 -04:00
Reduce serialization errors in MultiWriterIdGen (#8456)
We call `_update_stream_positions_table_txn` a lot, which is an UPSERT that can conflict in `REPEATABLE READ` isolation level. Instead of doing a transaction consisting of a single query we may as well run it outside of a transaction.
This commit is contained in:
parent
52a50e8686
commit
ae5b2a72c0
7 changed files with 112 additions and 8 deletions
|
@ -463,6 +463,24 @@ class DatabasePool:
|
|||
*args: Any,
|
||||
**kwargs: Any
|
||||
) -> R:
|
||||
"""Start a new database transaction with the given connection.
|
||||
|
||||
Note: The given func may be called multiple times under certain
|
||||
failure modes. This is normally fine when in a standard transaction,
|
||||
but care must be taken if the connection is in `autocommit` mode that
|
||||
the function will correctly handle being aborted and retried half way
|
||||
through its execution.
|
||||
|
||||
Args:
|
||||
conn
|
||||
desc
|
||||
after_callbacks
|
||||
exception_callbacks
|
||||
func
|
||||
*args
|
||||
**kwargs
|
||||
"""
|
||||
|
||||
start = monotonic_time()
|
||||
txn_id = self._TXN_ID
|
||||
|
||||
|
@ -566,7 +584,12 @@ class DatabasePool:
|
|||
sql_txn_timer.labels(desc).observe(duration)
|
||||
|
||||
async def runInteraction(
|
||||
self, desc: str, func: "Callable[..., R]", *args: Any, **kwargs: Any
|
||||
self,
|
||||
desc: str,
|
||||
func: "Callable[..., R]",
|
||||
*args: Any,
|
||||
db_autocommit: bool = False,
|
||||
**kwargs: Any
|
||||
) -> R:
|
||||
"""Starts a transaction on the database and runs a given function
|
||||
|
||||
|
@ -576,6 +599,18 @@ class DatabasePool:
|
|||
database transaction (twisted.enterprise.adbapi.Transaction) as
|
||||
its first argument, followed by `args` and `kwargs`.
|
||||
|
||||
db_autocommit: Whether to run the function in "autocommit" mode,
|
||||
i.e. outside of a transaction. This is useful for transactions
|
||||
that are only a single query.
|
||||
|
||||
Currently, this is only implemented for Postgres. SQLite will still
|
||||
run the function inside a transaction.
|
||||
|
||||
WARNING: This means that if func fails half way through then
|
||||
the changes will *not* be rolled back. `func` may also get
|
||||
called multiple times if the transaction is retried, so must
|
||||
correctly handle that case.
|
||||
|
||||
args: positional args to pass to `func`
|
||||
kwargs: named args to pass to `func`
|
||||
|
||||
|
@ -596,6 +631,7 @@ class DatabasePool:
|
|||
exception_callbacks,
|
||||
func,
|
||||
*args,
|
||||
db_autocommit=db_autocommit,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
|
@ -609,7 +645,11 @@ class DatabasePool:
|
|||
return cast(R, result)
|
||||
|
||||
async def runWithConnection(
|
||||
self, func: "Callable[..., R]", *args: Any, **kwargs: Any
|
||||
self,
|
||||
func: "Callable[..., R]",
|
||||
*args: Any,
|
||||
db_autocommit: bool = False,
|
||||
**kwargs: Any
|
||||
) -> R:
|
||||
"""Wraps the .runWithConnection() method on the underlying db_pool.
|
||||
|
||||
|
@ -618,6 +658,9 @@ class DatabasePool:
|
|||
database connection (twisted.enterprise.adbapi.Connection) as
|
||||
its first argument, followed by `args` and `kwargs`.
|
||||
args: positional args to pass to `func`
|
||||
db_autocommit: Whether to run the function in "autocommit" mode,
|
||||
i.e. outside of a transaction. This is useful for transaction
|
||||
that are only a single query. Currently only affects postgres.
|
||||
kwargs: named args to pass to `func`
|
||||
|
||||
Returns:
|
||||
|
@ -633,6 +676,13 @@ class DatabasePool:
|
|||
start_time = monotonic_time()
|
||||
|
||||
def inner_func(conn, *args, **kwargs):
|
||||
# We shouldn't be in a transaction. If we are then something
|
||||
# somewhere hasn't committed after doing work. (This is likely only
|
||||
# possible during startup, as `run*` will ensure changes are
|
||||
# committed/rolled back before putting the connection back in the
|
||||
# pool).
|
||||
assert not self.engine.in_transaction(conn)
|
||||
|
||||
with LoggingContext("runWithConnection", parent_context) as context:
|
||||
sched_duration_sec = monotonic_time() - start_time
|
||||
sql_scheduling_timer.observe(sched_duration_sec)
|
||||
|
@ -642,10 +692,17 @@ class DatabasePool:
|
|||
logger.debug("Reconnecting closed database connection")
|
||||
conn.reconnect()
|
||||
|
||||
db_conn = LoggingDatabaseConnection(
|
||||
conn, self.engine, "runWithConnection"
|
||||
)
|
||||
return func(db_conn, *args, **kwargs)
|
||||
try:
|
||||
if db_autocommit:
|
||||
self.engine.attempt_to_set_autocommit(conn, True)
|
||||
|
||||
db_conn = LoggingDatabaseConnection(
|
||||
conn, self.engine, "runWithConnection"
|
||||
)
|
||||
return func(db_conn, *args, **kwargs)
|
||||
finally:
|
||||
if db_autocommit:
|
||||
self.engine.attempt_to_set_autocommit(conn, False)
|
||||
|
||||
return await make_deferred_yieldable(
|
||||
self._db_pool.runWithConnection(inner_func, *args, **kwargs)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue