RedisManager.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. /* eslint-disable
  2. camelcase,
  3. handle-callback-err,
  4. */
  5. // TODO: This file was created by bulk-decaffeinate.
  6. // Fix any style issues and re-enable lint.
  7. /*
  8. * decaffeinate suggestions:
  9. * DS101: Remove unnecessary use of Array.from
  10. * DS102: Remove unnecessary code created because of implicit returns
  11. * DS205: Consider reworking code to avoid use of IIFEs
  12. * DS207: Consider shorter variations of null checks
  13. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  14. */
  15. let RedisManager
  16. const Settings = require('@overleaf/settings')
  17. const redis = require('@overleaf/redis-wrapper')
  18. const rclient = redis.createClient(Settings.redis.history)
  19. const Keys = Settings.redis.history.key_schema
  20. const async = require('async')
  21. module.exports = RedisManager = {
  22. getOldestDocUpdates(doc_id, batchSize, callback) {
  23. if (callback == null) {
  24. callback = function (error, jsonUpdates) {}
  25. }
  26. const key = Keys.uncompressedHistoryOps({ doc_id })
  27. return rclient.lrange(key, 0, batchSize - 1, callback)
  28. },
  29. expandDocUpdates(jsonUpdates, callback) {
  30. let rawUpdates
  31. if (callback == null) {
  32. callback = function (error, rawUpdates) {}
  33. }
  34. try {
  35. rawUpdates = Array.from(jsonUpdates || []).map(update =>
  36. JSON.parse(update)
  37. )
  38. } catch (e) {
  39. return callback(e)
  40. }
  41. return callback(null, rawUpdates)
  42. },
  43. deleteAppliedDocUpdates(project_id, doc_id, docUpdates, callback) {
  44. if (callback == null) {
  45. callback = function (error) {}
  46. }
  47. const multi = rclient.multi()
  48. // Delete all the updates which have been applied (exact match)
  49. for (const update of Array.from(docUpdates || [])) {
  50. multi.lrem(Keys.uncompressedHistoryOps({ doc_id }), 1, update)
  51. }
  52. return multi.exec(function (error, results) {
  53. if (error != null) {
  54. return callback(error)
  55. }
  56. // It's ok to delete the doc_id from the set here. Even though the list
  57. // of updates may not be empty, we will continue to process it until it is.
  58. return rclient.srem(
  59. Keys.docsWithHistoryOps({ project_id }),
  60. doc_id,
  61. function (error) {
  62. if (error != null) {
  63. return callback(error)
  64. }
  65. return callback(null)
  66. }
  67. )
  68. })
  69. },
  70. getDocIdsWithHistoryOps(project_id, callback) {
  71. if (callback == null) {
  72. callback = function (error, doc_ids) {}
  73. }
  74. return rclient.smembers(Keys.docsWithHistoryOps({ project_id }), callback)
  75. },
  76. // iterate over keys asynchronously using redis scan (non-blocking)
  77. // handle all the cluster nodes or single redis server
  78. _getKeys(pattern, callback) {
  79. const nodes = (typeof rclient.nodes === 'function'
  80. ? rclient.nodes('master')
  81. : undefined) || [rclient]
  82. const doKeyLookupForNode = (node, cb) =>
  83. RedisManager._getKeysFromNode(node, pattern, cb)
  84. return async.concatSeries(nodes, doKeyLookupForNode, callback)
  85. },
  86. _getKeysFromNode(node, pattern, callback) {
  87. let cursor = 0 // redis iterator
  88. const keySet = {} // use hash to avoid duplicate results
  89. // scan over all keys looking for pattern
  90. var doIteration = cb =>
  91. node.scan(
  92. cursor,
  93. 'MATCH',
  94. pattern,
  95. 'COUNT',
  96. 1000,
  97. function (error, reply) {
  98. let keys
  99. if (error != null) {
  100. return callback(error)
  101. }
  102. ;[cursor, keys] = Array.from(reply)
  103. for (const key of Array.from(keys)) {
  104. keySet[key] = true
  105. }
  106. if (cursor === '0') {
  107. // note redis returns string result not numeric
  108. return callback(null, Object.keys(keySet))
  109. } else {
  110. return doIteration()
  111. }
  112. }
  113. )
  114. return doIteration()
  115. },
  116. // extract ids from keys like DocsWithHistoryOps:57fd0b1f53a8396d22b2c24b
  117. // or DocsWithHistoryOps:{57fd0b1f53a8396d22b2c24b} (for redis cluster)
  118. _extractIds(keyList) {
  119. const ids = (() => {
  120. const result = []
  121. for (const key of Array.from(keyList)) {
  122. const m = key.match(/:\{?([0-9a-f]{24})\}?/) // extract object id
  123. result.push(m[1])
  124. }
  125. return result
  126. })()
  127. return ids
  128. },
  129. getProjectIdsWithHistoryOps(callback) {
  130. if (callback == null) {
  131. callback = function (error, project_ids) {}
  132. }
  133. return RedisManager._getKeys(
  134. Keys.docsWithHistoryOps({ project_id: '*' }),
  135. function (error, project_keys) {
  136. if (error != null) {
  137. return callback(error)
  138. }
  139. const project_ids = RedisManager._extractIds(project_keys)
  140. return callback(error, project_ids)
  141. }
  142. )
  143. },
  144. getAllDocIdsWithHistoryOps(callback) {
  145. // return all the docids, to find dangling history entries after
  146. // everything is flushed.
  147. if (callback == null) {
  148. callback = function (error, doc_ids) {}
  149. }
  150. return RedisManager._getKeys(
  151. Keys.uncompressedHistoryOps({ doc_id: '*' }),
  152. function (error, doc_keys) {
  153. if (error != null) {
  154. return callback(error)
  155. }
  156. const doc_ids = RedisManager._extractIds(doc_keys)
  157. return callback(error, doc_ids)
  158. }
  159. )
  160. },
  161. }