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

Commit

Permalink
Fix port script to handle foreign key constraints (#8730)
Browse files Browse the repository at this point in the history
  • Loading branch information
erikjohnston authored Nov 11, 2020
1 parent 89700df commit 5829872
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 6 deletions.
Binary file modified .buildkite/test_db.db
Binary file not shown.
1 change: 1 addition & 0 deletions changelog.d/8730.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix port script to correctly handle foreign key constraints. Broke in v1.21.0.
68 changes: 62 additions & 6 deletions scripts/synapse_port_db
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import logging
import sys
import time
import traceback
from typing import Optional
from typing import Dict, Optional, Set

import yaml

Expand Down Expand Up @@ -292,6 +292,34 @@ class Porter(object):

return table, already_ported, total_to_port, forward_chunk, backward_chunk

async def get_table_constraints(self) -> Dict[str, Set[str]]:
"""Returns a map of tables that have foreign key constraints to tables they depend on.
"""

def _get_constraints(txn):
# We can pull the information about foreign key constraints out from
# the postgres schema tables.
sql = """
SELECT DISTINCT
tc.table_name,
ccu.table_name AS foreign_table_name
FROM
information_schema.table_constraints AS tc
INNER JOIN information_schema.constraint_column_usage AS ccu
USING (table_schema, constraint_name)
WHERE tc.constraint_type = 'FOREIGN KEY';
"""
txn.execute(sql)

results = {}
for table, foreign_table in txn:
results.setdefault(table, set()).add(foreign_table)
return results

return await self.postgres_store.db_pool.runInteraction(
"get_table_constraints", _get_constraints
)

async def handle_table(
self, table, postgres_size, table_size, forward_chunk, backward_chunk
):
Expand Down Expand Up @@ -619,15 +647,43 @@ class Porter(object):
consumeErrors=True,
)
)
# Map from table name to args passed to `handle_table`, i.e. a tuple
# of: `postgres_size`, `table_size`, `forward_chunk`, `backward_chunk`.
tables_to_port_info_map = {r[0]: r[1:] for r in setup_res}

# Step 4. Do the copying.
#
# This is slightly convoluted as we need to ensure tables are ported
# in the correct order due to foreign key constraints.
self.progress.set_state("Copying to postgres")
await make_deferred_yieldable(
defer.gatherResults(
[run_in_background(self.handle_table, *res) for res in setup_res],
consumeErrors=True,

constraints = await self.get_table_constraints()
tables_ported = set() # type: Set[str]

while tables_to_port_info_map:
# Pulls out all tables that are still to be ported and which
# only depend on tables that are already ported (if any).
tables_to_port = [
table
for table in tables_to_port_info_map
if not constraints.get(table, set()) - tables_ported
]

await make_deferred_yieldable(
defer.gatherResults(
[
run_in_background(
self.handle_table,
table,
*tables_to_port_info_map.pop(table),
)
for table in tables_to_port
],
consumeErrors=True,
)
)
)

tables_ported.update(tables_to_port)

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

0 comments on commit 5829872

Please sign in to comment.