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

Commit

Permalink
Merge pull request #4434 from matrix-org/erikj/fix_user_ips_dedup
Browse files Browse the repository at this point in the history
Fix bug when removing duplicate rows from user_ips
  • Loading branch information
erikjohnston authored Jan 22, 2019
2 parents 388c164 + 7f503f8 commit 12699a7
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 26 deletions.
1 change: 1 addition & 0 deletions changelog.d/4434.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Apply a unique index to the user_ips table, preventing duplicates.
61 changes: 35 additions & 26 deletions synapse/storage/client_ips.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,13 @@ def f(conn):

@defer.inlineCallbacks
def _remove_user_ip_dupes(self, progress, batch_size):
# This works function works by scanning the user_ips table in batches
# based on `last_seen`. For each row in a batch it searches the rest of
# the table to see if there are any duplicates, if there are then they
# are removed and replaced with a suitable row.

last_seen_progress = progress.get("last_seen", 0)
# Fetch the start of the batch
begin_last_seen = progress.get("last_seen", 0)

def get_last_seen(txn):
txn.execute(
Expand All @@ -122,29 +127,23 @@ def get_last_seen(txn):
LIMIT 1
OFFSET ?
""",
(last_seen_progress, batch_size)
(begin_last_seen, batch_size)
)
results = txn.fetchone()
return results

# Get a last seen that's sufficiently far away enough from the last one
last_seen = yield self.runInteraction(
row = txn.fetchone()
if row:
return row[0]
else:
return None

# Get a last seen that has roughly `batch_size` since `begin_last_seen`
end_last_seen = yield self.runInteraction(
"user_ips_dups_get_last_seen", get_last_seen
)

if not last_seen:
# If we get a None then we're reaching the end and just need to
# delete the last batch.
last = True

# We fake not having an upper bound by using a future date, by
# just multiplying the current time by two....
last_seen = int(self.clock.time_msec()) * 2
else:
last = False
last_seen = last_seen[0]
# If it returns None, then we're processing the last batch
last = end_last_seen is None

def remove(txn, last_seen_progress, last_seen):
def remove(txn):
# This works by looking at all entries in the given time span, and
# then for each (user_id, access_token, ip) tuple in that range
# checking for any duplicates in the rest of the table (via a join).
Expand All @@ -153,20 +152,31 @@ def remove(txn, last_seen_progress, last_seen):
# all other duplicates.
# It is efficient due to the existence of (user_id, access_token,
# ip) and (last_seen) indices.

# Define the search space, which requires handling the last batch in
# a different way
if last:
clause = "? <= last_seen"
args = (begin_last_seen,)
else:
clause = "? <= last_seen AND last_seen < ?"
args = (begin_last_seen, end_last_seen)

txn.execute(
"""
SELECT user_id, access_token, ip,
MAX(device_id), MAX(user_agent), MAX(last_seen)
FROM (
SELECT user_id, access_token, ip
FROM user_ips
WHERE ? <= last_seen AND last_seen < ?
WHERE {}
ORDER BY last_seen
) c
INNER JOIN user_ips USING (user_id, access_token, ip)
GROUP BY user_id, access_token, ip
HAVING count(*) > 1""",
(last_seen_progress, last_seen)
HAVING count(*) > 1
""".format(clause),
args
)
res = txn.fetchall()

Expand Down Expand Up @@ -194,12 +204,11 @@ def remove(txn, last_seen_progress, last_seen):
)

self._background_update_progress_txn(
txn, "user_ips_remove_dupes", {"last_seen": last_seen}
txn, "user_ips_remove_dupes", {"last_seen": end_last_seen}
)

yield self.runInteraction(
"user_ips_dups_remove", remove, last_seen_progress, last_seen
)
yield self.runInteraction("user_ips_dups_remove", remove)

if last:
yield self._end_background_update("user_ips_remove_dupes")

Expand Down

0 comments on commit 12699a7

Please sign in to comment.