purge_non_logged_in_sessions.mjs 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. import RedisWrapper from '@overleaf/redis-wrapper'
  2. import Settings from '@overleaf/settings'
  3. import SessionManager from '../app/src/Features/Authentication/SessionManager.js'
  4. import minimist from 'minimist'
  5. const redis = RedisWrapper.createClient(Settings.redis.websessions)
  6. const argv = minimist(process.argv.slice(2), {
  7. string: ['count'],
  8. boolean: ['dry-run', 'help'],
  9. alias: {
  10. count: 'c',
  11. 'dry-run': 'n',
  12. help: 'h',
  13. },
  14. })
  15. if (argv.help) {
  16. console.log(
  17. `Usage: node purge_non_logged_in_sessions.js [--count <count>] [--dry-run]
  18. --count <count> the number of keys to scan on each iteration (default 1000)
  19. --dry-run to not delete any keys
  20. --help to show this help
  21. Note: use --count=10000 to delete faster (this will impact redis performance,
  22. so use with caution)`
  23. )
  24. process.exit()
  25. }
  26. const scanCount = argv.count ? parseInt(argv.count, 10) : 1000
  27. const dryRun = argv['dry-run']
  28. console.log(`Scan count set to ${scanCount}`)
  29. if (dryRun) {
  30. console.log('Dry run, not deleting any keys')
  31. }
  32. // iterate over all redis keys matching sess:* and delete the ones
  33. // that are not logged in using async await and mget and mdel
  34. async function scanAndPurge() {
  35. let totalSessions = 0
  36. let totalDeletedSessions = 0
  37. const stream = redis.scanStream({
  38. match: 'sess:*',
  39. count: scanCount,
  40. })
  41. console.log('Starting scan...')
  42. for await (const resultKeys of stream) {
  43. if (resultKeys.length === 0) {
  44. continue // scan is allowed to return zero elements, the client should not consider the iteration complete
  45. }
  46. console.log(`Keys found, count: ${resultKeys.length}`)
  47. totalSessions += resultKeys.length
  48. const sessions = await redis.mget(resultKeys)
  49. const toDelete = []
  50. for (let i = 0; i < sessions.length; i++) {
  51. const resultKey = resultKeys[i]
  52. const session = sessions[i]
  53. if (!session) {
  54. continue
  55. }
  56. try {
  57. const sessionObject = JSON.parse(session)
  58. if (!SessionManager.isUserLoggedIn(sessionObject)) {
  59. totalDeletedSessions++
  60. toDelete.push(resultKey)
  61. }
  62. } catch (error) {
  63. console.error(`Error parsing session ${resultKeys[i]}: ${error}`)
  64. }
  65. }
  66. if (toDelete.length === 0) {
  67. continue
  68. }
  69. if (dryRun) {
  70. console.log(`Would delete ${toDelete.length} keys`)
  71. } else {
  72. await redis.del(toDelete)
  73. console.log(`Keys deleted so far: ${totalDeletedSessions}`)
  74. }
  75. }
  76. if (dryRun) {
  77. console.log(
  78. `Dry run: ${totalSessions} sessions checked, ${totalDeletedSessions} would have been deleted`
  79. )
  80. } else {
  81. console.log(
  82. `All ${totalSessions} sessions have been checked, ${totalDeletedSessions} deleted`
  83. )
  84. }
  85. redis.quit()
  86. }
  87. try {
  88. await scanAndPurge()
  89. } catch (error) {
  90. console.error(error)
  91. process.exit()
  92. }