RedisManager.js 5.0 KB

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