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

Commit 5829872

Browse files
authored
Fix port script to handle foreign key constraints (#8730)
1 parent 89700df commit 5829872

File tree

3 files changed

+63
-6
lines changed

3 files changed

+63
-6
lines changed

.buildkite/test_db.db

16 KB
Binary file not shown.

changelog.d/8730.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix port script to correctly handle foreign key constraints. Broke in v1.21.0.

scripts/synapse_port_db

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import logging
2222
import sys
2323
import time
2424
import traceback
25-
from typing import Optional
25+
from typing import Dict, Optional, Set
2626

2727
import yaml
2828

@@ -292,6 +292,34 @@ class Porter(object):
292292

293293
return table, already_ported, total_to_port, forward_chunk, backward_chunk
294294

295+
async def get_table_constraints(self) -> Dict[str, Set[str]]:
296+
"""Returns a map of tables that have foreign key constraints to tables they depend on.
297+
"""
298+
299+
def _get_constraints(txn):
300+
# We can pull the information about foreign key constraints out from
301+
# the postgres schema tables.
302+
sql = """
303+
SELECT DISTINCT
304+
tc.table_name,
305+
ccu.table_name AS foreign_table_name
306+
FROM
307+
information_schema.table_constraints AS tc
308+
INNER JOIN information_schema.constraint_column_usage AS ccu
309+
USING (table_schema, constraint_name)
310+
WHERE tc.constraint_type = 'FOREIGN KEY';
311+
"""
312+
txn.execute(sql)
313+
314+
results = {}
315+
for table, foreign_table in txn:
316+
results.setdefault(table, set()).add(foreign_table)
317+
return results
318+
319+
return await self.postgres_store.db_pool.runInteraction(
320+
"get_table_constraints", _get_constraints
321+
)
322+
295323
async def handle_table(
296324
self, table, postgres_size, table_size, forward_chunk, backward_chunk
297325
):
@@ -619,15 +647,43 @@ class Porter(object):
619647
consumeErrors=True,
620648
)
621649
)
650+
# Map from table name to args passed to `handle_table`, i.e. a tuple
651+
# of: `postgres_size`, `table_size`, `forward_chunk`, `backward_chunk`.
652+
tables_to_port_info_map = {r[0]: r[1:] for r in setup_res}
622653

623654
# Step 4. Do the copying.
655+
#
656+
# This is slightly convoluted as we need to ensure tables are ported
657+
# in the correct order due to foreign key constraints.
624658
self.progress.set_state("Copying to postgres")
625-
await make_deferred_yieldable(
626-
defer.gatherResults(
627-
[run_in_background(self.handle_table, *res) for res in setup_res],
628-
consumeErrors=True,
659+
660+
constraints = await self.get_table_constraints()
661+
tables_ported = set() # type: Set[str]
662+
663+
while tables_to_port_info_map:
664+
# Pulls out all tables that are still to be ported and which
665+
# only depend on tables that are already ported (if any).
666+
tables_to_port = [
667+
table
668+
for table in tables_to_port_info_map
669+
if not constraints.get(table, set()) - tables_ported
670+
]
671+
672+
await make_deferred_yieldable(
673+
defer.gatherResults(
674+
[
675+
run_in_background(
676+
self.handle_table,
677+
table,
678+
*tables_to_port_info_map.pop(table),
679+
)
680+
for table in tables_to_port
681+
],
682+
consumeErrors=True,
683+
)
629684
)
630-
)
685+
686+
tables_ported.update(tables_to_port)
631687

632688
# Step 5. Set up sequences
633689
self.progress.set_state("Setting up sequence generators")

0 commit comments

Comments
 (0)