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

Automatically apply SQL for inconsistent sequence #17305

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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/17305.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
When rolling back to a previous Synapse version and then forwards again to this release, don't require server operators to manually run SQL.
10 changes: 0 additions & 10 deletions docs/postgres.md
Original file line number Diff line number Diff line change
Expand Up @@ -255,13 +255,3 @@ however extreme care must be taken to avoid database corruption.

Note that the above may fail with an error about duplicate rows if corruption
has already occurred, and such duplicate rows will need to be manually removed.

### Fixing inconsistent sequences error

Synapse uses Postgres sequences to generate IDs for various tables. A sequence
and associated table can get out of sync if, for example, Synapse has been
downgraded and then upgraded again.

To fix the issue shut down Synapse (including any and all workers) and run the
SQL command included in the error message. Once done Synapse should start
successfully.
37 changes: 15 additions & 22 deletions synapse/storage/util/sequence.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,21 +36,6 @@
logger = logging.getLogger(__name__)


_INCONSISTENT_SEQUENCE_ERROR = """
Postgres sequence '%(seq)s' is inconsistent with associated
table '%(table)s'. This can happen if Synapse has been downgraded and
then upgraded again, or due to a bad migration.

To fix this error, shut down Synapse (including any and all workers)
and run the following SQL:

SELECT setval('%(seq)s', (
%(max_id_sql)s
));

See docs/postgres.md for more information.
"""

_INCONSISTENT_STREAM_ERROR = """
Postgres sequence '%(seq)s' is inconsistent with associated stream position
of '%(stream_name)s' in the 'stream_positions' table.
Expand Down Expand Up @@ -169,25 +154,33 @@ def check_consistency(
if row:
max_in_stream_positions = row[0]

txn.close()

# If `is_called` is False then `last_value` is actually the value that
# will be generated next, so we decrement to get the true "last value".
if not is_called:
last_value -= 1

if max_stream_id > last_value:
# The sequence is lagging behind the tables. This is probably due to
# rolling back to a version before the sequence was used and then
# forwards again. We resolve this by setting the sequence to the
# right value.
logger.warning(
"Postgres sequence %s is behind table %s: %d < %d",
"Postgres sequence %s is behind table %s: %d < %d. Updating sequence.",
self._sequence_name,
table,
last_value,
max_stream_id,
)
raise IncorrectDatabaseSetup(
_INCONSISTENT_SEQUENCE_ERROR
% {"seq": self._sequence_name, "table": table, "max_id_sql": table_sql}
)

sql = f"""
SELECT setval('{self._sequence_name}', GREATEST(
(SELECT last_value FROM {self._sequence_name}),
({table_sql})
));
"""
txn.execute(sql)

txn.close()

# If we have values in the stream positions table then they have to be
# less than or equal to `last_value`
Expand Down
14 changes: 9 additions & 5 deletions tests/storage/test_id_generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
LoggingDatabaseConnection,
LoggingTransaction,
)
from synapse.storage.engines import IncorrectDatabaseSetup
from synapse.storage.types import Cursor
from synapse.storage.util.id_generators import MultiWriterIdGenerator
from synapse.storage.util.sequence import (
Expand Down Expand Up @@ -525,7 +524,7 @@ async def _get_next_async() -> None:
self.assertEqual(id_gen_5.get_current_token_for_writer("third"), 6)

def test_sequence_consistency(self) -> None:
"""Test that we error out if the table and sequence diverges."""
"""Test that we correct the sequence if the table and sequence diverges."""

# Prefill with some rows
self._insert_row_with_id("master", 3)
Expand All @@ -536,9 +535,14 @@ def _insert(txn: Cursor) -> None:

self.get_success(self.db_pool.runInteraction("_insert", _insert))

# Creating the ID gen should error
with self.assertRaises(IncorrectDatabaseSetup):
self._create_id_generator("first")
# Creating the ID gen should now fix the inconsistency
id_gen = self._create_id_generator()

async def _get_next_async() -> None:
async with id_gen.get_next() as stream_id:
self.assertEqual(stream_id, 27)

self.get_success(_get_next_async())

def test_minimal_local_token(self) -> None:
self._insert_rows("first", 3)
Expand Down
Loading