Explorar o código

Merge pull request #17291 from overleaf/bg-purge-non-logged-in-sessions

purge sessions using mget/del with multiple keys

GitOrigin-RevId: 9a4c0d1dee1f349282a154da9d25cb78a5bedb77
Brian Gough %!s(int64=2) %!d(string=hai) anos
pai
achega
9127e59599
Modificáronse 1 ficheiros con 81 adicións e 45 borrados
  1. 81 45
      services/web/scripts/purge_non_logged_in_sessions.js

+ 81 - 45
services/web/scripts/purge_non_logged_in_sessions.js

@@ -1,62 +1,98 @@
 const RedisWrapper = require('@overleaf/redis-wrapper')
 const Settings = require('@overleaf/settings')
 const SessionManager = require('../app/src/Features/Authentication/SessionManager')
-const async = require('async')
-const _ = require('lodash')
 
 const redis = RedisWrapper.createClient(Settings.redis.websessions)
 
-let totalDeletedSessions = 0
+const argv = require('minimist')(process.argv.slice(2), {
+  string: ['count'],
+  boolean: ['dry-run', 'help'],
+  alias: {
+    count: 'c',
+    'dry-run': 'n',
+    help: 'h',
+  },
+})
 
-const queue = async.queue(function (sessKey, callback) {
-  const cb = _.once(callback)
-  redis.get(sessKey, (_err, session) => {
-    try {
-      if (SessionManager.isUserLoggedIn(JSON.parse(session))) {
-        cb()
-      } else {
-        redis.del(sessKey, () => {
-          totalDeletedSessions++
-          if (totalDeletedSessions % 1000 === 0) {
-            console.log(`Keys deleted so far: ${totalDeletedSessions}`)
-          }
-          cb()
-        })
-      }
-    } catch (err) {
-      console.log(`${sessKey} couldn't parse`)
-      cb()
-    }
-  })
-}, 20)
+if (argv.help) {
+  console.log(
+    `Usage: node purge_non_logged_in_sessions.js [--count <count>] [--dry-run]
+  --count <count> the number of keys to scan on each iteration (default 1000)
+  --dry-run to not delete any keys
+  --help to show this help
 
-function scanAndPurge(cb) {
-  const stream = redis.scanStream({
-    match: 'sess:*',
-    count: 1000,
-  })
-  console.log('starting scan')
+Note: use --count=10000 to delete faster (this will impact redis performance,
+so use with caution)`
+  )
+  process.exit()
+}
 
-  stream.on('data', resultKeys => {
-    console.log(`Keys found, count:${resultKeys.length}`)
-    queue.push(resultKeys)
-  })
+const scanCount = argv.count ? parseInt(argv.count, 10) : 1000
+const dryRun = argv['dry-run']
 
-  stream.on('end', () => {
-    queue.drain(() => {
-      console.log(
-        `All sessions have been checked, ${totalDeletedSessions} deleted`
-      )
-      cb()
-    })
-  })
+console.log(`Scan count set to ${scanCount}`)
 
-  stream.on('error', err => {
-    console.log(err)
+if (dryRun) {
+  console.log('Dry run, not deleting any keys')
+}
+
+// iterate over all redis keys matching sess:* and delete the ones
+// that are not logged in using async await and mget and mdel
+async function scanAndPurge() {
+  let totalSessions = 0
+  let totalDeletedSessions = 0
+  const stream = redis.scanStream({
+    match: 'sess:*',
+    count: scanCount,
   })
+  console.log('Starting scan...')
+  for await (const resultKeys of stream) {
+    if (resultKeys.length === 0) {
+      continue // scan is allowed to return zero elements, the client should not consider the iteration complete
+    }
+    console.log(`Keys found, count: ${resultKeys.length}`)
+    totalSessions += resultKeys.length
+    const sessions = await redis.mget(resultKeys)
+    const toDelete = []
+    for (let i = 0; i < sessions.length; i++) {
+      const resultKey = resultKeys[i]
+      const session = sessions[i]
+      if (!session) {
+        continue
+      }
+      try {
+        const sessionObject = JSON.parse(session)
+        if (!SessionManager.isUserLoggedIn(sessionObject)) {
+          totalDeletedSessions++
+          toDelete.push(resultKey)
+        }
+      } catch (error) {
+        console.error(`Error parsing session ${resultKeys[i]}: ${error}`)
+      }
+    }
+    if (toDelete.length === 0) {
+      continue
+    }
+    if (dryRun) {
+      console.log(`Would delete ${toDelete.length} keys`)
+    } else {
+      await redis.del(toDelete)
+      console.log(`Keys deleted so far: ${totalDeletedSessions}`)
+    }
+  }
+  if (dryRun) {
+    console.log(
+      `Dry run: ${totalSessions} sessions checked, ${totalDeletedSessions} would have been deleted`
+    )
+  } else {
+    console.log(
+      `All ${totalSessions} sessions have been checked, ${totalDeletedSessions} deleted`
+    )
+  }
+  redis.quit()
 }
 
-scanAndPurge(err => {
+scanAndPurge().catch(err => {
   console.error(err)
   process.exit()
 })