RealTimeRedisManager.js 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. /* eslint-disable
  2. no-unused-vars,
  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. * DS207: Consider shorter variations of null checks
  11. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  12. */
  13. const Settings = require('@overleaf/settings')
  14. const { promisifyAll } = require('@overleaf/promise-utils')
  15. const rclient = require('@overleaf/redis-wrapper').createClient(
  16. Settings.redis.documentupdater
  17. )
  18. const pubsubClient = require('@overleaf/redis-wrapper').createClient(
  19. Settings.redis.pubsub
  20. )
  21. const Keys = Settings.redis.documentupdater.key_schema
  22. const logger = require('@overleaf/logger')
  23. const os = require('os')
  24. const crypto = require('crypto')
  25. const metrics = require('./Metrics')
  26. const HOST = os.hostname()
  27. const RND = crypto.randomBytes(4).toString('hex') // generate a random key for this process
  28. let COUNT = 0
  29. const MAX_OPS_PER_ITERATION = 8 // process a limited number of ops for safety
  30. const RealTimeRedisManager = {
  31. getPendingUpdatesForDoc(docId, callback) {
  32. // Make sure that this MULTI operation only operates on doc
  33. // specific keys, i.e. keys that have the doc id in curly braces.
  34. // The curly braces identify a hash key for Redis and ensures that
  35. // the MULTI's operations are all done on the same node in a
  36. // cluster environment.
  37. const multi = rclient.multi()
  38. multi.lrange(
  39. Keys.pendingUpdates({ doc_id: docId }),
  40. 0,
  41. MAX_OPS_PER_ITERATION - 1
  42. )
  43. multi.ltrim(
  44. Keys.pendingUpdates({ doc_id: docId }),
  45. MAX_OPS_PER_ITERATION,
  46. -1
  47. )
  48. return multi.exec(function (error, replys) {
  49. let jsonUpdate
  50. if (error != null) {
  51. return callback(error)
  52. }
  53. const jsonUpdates = replys[0]
  54. for (jsonUpdate of Array.from(jsonUpdates)) {
  55. // record metric for each update removed from queue
  56. metrics.summary('redis.pendingUpdates', jsonUpdate.length, {
  57. status: 'pop',
  58. })
  59. }
  60. const updates = []
  61. for (jsonUpdate of Array.from(jsonUpdates)) {
  62. let update
  63. try {
  64. update = JSON.parse(jsonUpdate)
  65. } catch (e) {
  66. return callback(e)
  67. }
  68. updates.push(update)
  69. }
  70. return callback(error, updates)
  71. })
  72. },
  73. getUpdatesLength(docId, callback) {
  74. return rclient.llen(Keys.pendingUpdates({ doc_id: docId }), callback)
  75. },
  76. sendData(data) {
  77. // create a unique message id using a counter
  78. const messageId = `doc:${HOST}:${RND}-${COUNT++}`
  79. if (data != null) {
  80. data._id = messageId
  81. }
  82. const blob = JSON.stringify(data)
  83. metrics.summary('redis.publish.applied-ops', blob.length)
  84. // publish on separate channels for individual projects and docs when
  85. // configured (needs realtime to be configured for this too).
  86. if (Settings.publishOnIndividualChannels) {
  87. return pubsubClient.publish(`applied-ops:${data.doc_id}`, blob)
  88. } else {
  89. return pubsubClient.publish('applied-ops', blob)
  90. }
  91. },
  92. }
  93. module.exports = RealTimeRedisManager
  94. module.exports.promises = promisifyAll(RealTimeRedisManager, {
  95. without: ['sendData'],
  96. })