RedisManager.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  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("settings-sharelatex");
  17. const redis = require("redis-sharelatex");
  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) { callback = function(error, jsonUpdates) {}; }
  24. const key = Keys.uncompressedHistoryOps({doc_id});
  25. return rclient.lrange(key, 0, batchSize - 1, callback);
  26. },
  27. expandDocUpdates(jsonUpdates, callback) {
  28. let rawUpdates;
  29. if (callback == null) { callback = function(error, rawUpdates) {}; }
  30. try {
  31. rawUpdates = ( Array.from(jsonUpdates || []).map((update) => JSON.parse(update)) );
  32. } catch (e) {
  33. return callback(e);
  34. }
  35. return callback(null, rawUpdates);
  36. },
  37. deleteAppliedDocUpdates(project_id, doc_id, docUpdates, callback) {
  38. if (callback == null) { callback = function(error) {}; }
  39. const multi = rclient.multi();
  40. // Delete all the updates which have been applied (exact match)
  41. for (const update of Array.from(docUpdates || [])) {
  42. multi.lrem(Keys.uncompressedHistoryOps({doc_id}), 1, update);
  43. }
  44. return multi.exec(function(error, results) {
  45. if (error != null) { return callback(error); }
  46. // It's ok to delete the doc_id from the set here. Even though the list
  47. // of updates may not be empty, we will continue to process it until it is.
  48. return rclient.srem(Keys.docsWithHistoryOps({project_id}), doc_id, function(error) {
  49. if (error != null) { return callback(error); }
  50. return callback(null);
  51. });
  52. });
  53. },
  54. getDocIdsWithHistoryOps(project_id, callback) {
  55. if (callback == null) { callback = function(error, doc_ids) {}; }
  56. return rclient.smembers(Keys.docsWithHistoryOps({project_id}), callback);
  57. },
  58. // iterate over keys asynchronously using redis scan (non-blocking)
  59. // handle all the cluster nodes or single redis server
  60. _getKeys(pattern, callback) {
  61. const nodes = (typeof rclient.nodes === 'function' ? rclient.nodes('master') : undefined) || [ rclient ];
  62. const doKeyLookupForNode = (node, cb) => RedisManager._getKeysFromNode(node, pattern, cb);
  63. return async.concatSeries(nodes, doKeyLookupForNode, callback);
  64. },
  65. _getKeysFromNode(node, pattern, callback) {
  66. let cursor = 0; // redis iterator
  67. const keySet = {}; // use hash to avoid duplicate results
  68. // scan over all keys looking for pattern
  69. var doIteration = cb =>
  70. node.scan(cursor, "MATCH", pattern, "COUNT", 1000, function(error, reply) {
  71. let keys;
  72. if (error != null) { return callback(error); }
  73. [cursor, keys] = Array.from(reply);
  74. for (const key of Array.from(keys)) {
  75. keySet[key] = true;
  76. }
  77. if (cursor === '0') { // note redis returns string result not numeric
  78. return callback(null, Object.keys(keySet));
  79. } else {
  80. return doIteration();
  81. }
  82. })
  83. ;
  84. return doIteration();
  85. },
  86. // extract ids from keys like DocsWithHistoryOps:57fd0b1f53a8396d22b2c24b
  87. // or DocsWithHistoryOps:{57fd0b1f53a8396d22b2c24b} (for redis cluster)
  88. _extractIds(keyList) {
  89. const ids = (() => {
  90. const result = [];
  91. for (const key of Array.from(keyList)) {
  92. const m = key.match(/:\{?([0-9a-f]{24})\}?/); // extract object id
  93. result.push(m[1]);
  94. }
  95. return result;
  96. })();
  97. return ids;
  98. },
  99. getProjectIdsWithHistoryOps(callback) {
  100. if (callback == null) { callback = function(error, project_ids) {}; }
  101. return RedisManager._getKeys(Keys.docsWithHistoryOps({project_id:"*"}), function(error, project_keys) {
  102. if (error != null) { return callback(error); }
  103. const project_ids = RedisManager._extractIds(project_keys);
  104. return callback(error, project_ids);
  105. });
  106. },
  107. getAllDocIdsWithHistoryOps(callback) {
  108. // return all the docids, to find dangling history entries after
  109. // everything is flushed.
  110. if (callback == null) { callback = function(error, doc_ids) {}; }
  111. return RedisManager._getKeys(Keys.uncompressedHistoryOps({doc_id:"*"}), function(error, doc_keys) {
  112. if (error != null) { return callback(error); }
  113. const doc_ids = RedisManager._extractIds(doc_keys);
  114. return callback(error, doc_ids);
  115. });
  116. }
  117. });