|
| 1 | +SET NAMES utf8mb4 COLLATE utf8mb4_bin; |
| 2 | + |
| 3 | +-- We might have gotten ourselves into a state where 'sessionTokensPrunedUntil' |
| 4 | +-- was set to the empty string. Start it over from zero. |
| 5 | +UPDATE dbMetadata SET value = '0' WHERE name = 'sessionTokensPrunedUntil' AND value = ''; |
| 6 | + |
| 7 | +-- Update prune to limit total number of sessionTokens examined, |
| 8 | +-- and avoid producing the above empty-string bug. |
| 9 | +CREATE PROCEDURE `prune_7` (IN `olderThan` BIGINT UNSIGNED) |
| 10 | +BEGIN |
| 11 | + DECLARE EXIT HANDLER FOR SQLEXCEPTION |
| 12 | + BEGIN |
| 13 | + ROLLBACK; |
| 14 | + RESIGNAL; |
| 15 | + END; |
| 16 | + |
| 17 | + SELECT @lockAcquired := GET_LOCK('fxa-auth-server.prune-lock', 3); |
| 18 | + |
| 19 | + IF @lockAcquired THEN |
| 20 | + DELETE FROM accountResetTokens WHERE createdAt < olderThan ORDER BY createdAt LIMIT 10000; |
| 21 | + DELETE FROM passwordForgotTokens WHERE createdAt < olderThan ORDER BY createdAt LIMIT 10000; |
| 22 | + DELETE FROM passwordChangeTokens WHERE createdAt < olderThan ORDER BY createdAt LIMIT 10000; |
| 23 | + DELETE FROM unblockCodes WHERE createdAt < olderThan ORDER BY createdAt LIMIT 10000; |
| 24 | + DELETE FROM signinCodes WHERE createdAt < olderThan ORDER BY createdAt LIMIT 10000; |
| 25 | + |
| 26 | + -- Pruning session tokens is complicated because: |
| 27 | + -- * we can't prune them if there is an associated device record, and |
| 28 | + -- * we have to delete from both sessionTokens and unverifiedTokens tables, and |
| 29 | + -- * MySQL won't allow `LIMIT` to be used in a multi-table delete. |
| 30 | + -- To achieve all this in an efficient manner, we prune tokens within a specific |
| 31 | + -- time window rather than using a `LIMIT` clause. At the end of each run we |
| 32 | + -- record the new lower-bound on creation time for tokens that might have expired. |
| 33 | + START TRANSACTION; |
| 34 | + |
| 35 | + -- Step 1: Find out how far we got on previous iterations. |
| 36 | + SELECT @pruneFrom := value FROM dbMetadata WHERE name = 'sessionTokensPrunedUntil'; |
| 37 | + |
| 38 | + -- Step 2: Calculate what timestamp we will reach on this iteration |
| 39 | + -- if we purge a sensibly-sized batch of tokens. |
| 40 | + -- N.B. We deliberately do not filter on whether the token has |
| 41 | + -- a device here. We want to limit the number of tokens that we |
| 42 | + -- *examine*, regardless of whether it actually delete them. |
| 43 | + SELECT @pruneUntil := MAX(createdAt) FROM ( |
| 44 | + SELECT createdAt FROM sessionTokens |
| 45 | + WHERE createdAt >= @pruneFrom AND createdAt < olderThan |
| 46 | + ORDER BY createdAt |
| 47 | + LIMIT 10000 |
| 48 | + ) AS candidatesForPruning; |
| 49 | + |
| 50 | + -- This will be NULL if there are no expired tokens, |
| 51 | + -- in which case we have nothing to do. |
| 52 | + IF @pruneUntil IS NOT NULL THEN |
| 53 | + |
| 54 | + -- Step 3: Prune sessionTokens and unverifiedTokens tables. |
| 55 | + -- Here we *do* filter on whether a device record exists. |
| 56 | + -- We might not actually delete any tokens, but we will definitely |
| 57 | + -- be able to increase 'sessionTokensPrunedUntil' for the next run. |
| 58 | + DELETE st, ut |
| 59 | + FROM sessionTokens AS st |
| 60 | + LEFT JOIN unverifiedTokens AS ut |
| 61 | + ON st.tokenId = ut.tokenId |
| 62 | + WHERE st.createdAt > @pruneFrom |
| 63 | + AND st.createdAt <= @pruneUntil |
| 64 | + AND NOT EXISTS ( |
| 65 | + SELECT sessionTokenId FROM devices |
| 66 | + WHERE uid = st.uid AND sessionTokenId = st.tokenId |
| 67 | + ); |
| 68 | + |
| 69 | + -- Step 4: Tell following iterations how far we got. |
| 70 | + UPDATE dbMetadata |
| 71 | + SET value = @pruneUntil |
| 72 | + WHERE name = 'sessionTokensPrunedUntil'; |
| 73 | + |
| 74 | + END IF; |
| 75 | + |
| 76 | + COMMIT; |
| 77 | + |
| 78 | + SELECT RELEASE_LOCK('fxa-auth-server.prune-lock'); |
| 79 | + END IF; |
| 80 | +END; |
| 81 | + |
| 82 | +UPDATE dbMetadata SET value = '69' WHERE name = 'schema-patch-level'; |
| 83 | + |
0 commit comments