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

Fix port script to handle foreign key constraints #8730

Merged
merged 2 commits into from
Nov 11, 2020
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
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}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a comment to say what this means would be useful.


# 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