DocOpsManager.coffee 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. RedisManager = require "./RedisManager"
  2. mongojs = require("./mongojs")
  3. db = mongojs.db
  4. ObjectId = mongojs.ObjectId
  5. logger = require "logger-sharelatex"
  6. async = require "async"
  7. Metrics = require("./Metrics")
  8. module.exports = DocOpsManager =
  9. flushDocOpsToMongo: (project_id, doc_id, _callback = (error) ->) ->
  10. timer = new Metrics.Timer("docOpsManager.flushDocOpsToMongo")
  11. callback = (args...) ->
  12. timer.done()
  13. _callback(args...)
  14. DocOpsManager.getDocVersionInMongo doc_id, (error, mongoVersion) ->
  15. return callback(error) if error?
  16. RedisManager.getDocVersion doc_id, (error, redisVersion) ->
  17. return callback(error) if error?
  18. if !mongoVersion? or !redisVersion? or mongoVersion > redisVersion
  19. logger.error doc_id: doc_id, redisVersion: redisVersion, mongoVersion: mongoVersion, "mongo version is ahead of redis"
  20. return callback(new Error("inconsistent versions"))
  21. RedisManager.getPreviousDocOps doc_id, mongoVersion, -1, (error, ops) ->
  22. return callback(error) if error?
  23. if ops.length != redisVersion - mongoVersion
  24. logger.error doc_id: doc_id, redisVersion: redisVersion, mongoVersion: mongoVersion, opsLength: ops.length, "version difference does not match ops length"
  25. return callback(new Error("inconsistent versions"))
  26. logger.log doc_id: doc_id, redisVersion: redisVersion, mongoVersion: mongoVersion, "flushing doc ops to mongo"
  27. DocOpsManager._appendDocOpsInMongo doc_id, ops, redisVersion, (error) ->
  28. return callback(error) if error?
  29. callback null
  30. getPreviousDocOps: (project_id, doc_id, start, end, _callback = (error, ops) ->) ->
  31. timer = new Metrics.Timer("docOpsManager.getPreviousDocOps")
  32. callback = (args...) ->
  33. timer.done()
  34. _callback(args...)
  35. DocOpsManager._ensureOpsAreLoaded project_id, doc_id, start, (error) ->
  36. return callback(error) if error?
  37. RedisManager.getPreviousDocOps doc_id, start, end, (error, ops) ->
  38. return callback(error) if error?
  39. callback null, ops
  40. pushDocOp: (project_id, doc_id, op, callback = (error) ->) ->
  41. RedisManager.pushDocOp doc_id, op, (error, version) ->
  42. return callback(error) if error?
  43. RedisManager.pushUncompressedHistoryOp doc_id, op, (error) ->
  44. return callback(error) if error?
  45. callback null, version
  46. _ensureOpsAreLoaded: (project_id, doc_id, backToVersion, callback = (error) ->) ->
  47. RedisManager.getDocVersion doc_id, (error, redisVersion) ->
  48. return callback(error) if error?
  49. RedisManager.getDocOpsLength doc_id, (error, opsLength) ->
  50. return callback(error) if error?
  51. oldestVersionInRedis = redisVersion - opsLength
  52. if oldestVersionInRedis > backToVersion
  53. # _getDocOpsFromMongo(<id>, 4, 6, ...) will return the ops in positions 4 and 5, but not 6.
  54. logger.log doc_id: doc_id, backToVersion: backToVersion, oldestVersionInRedis: oldestVersionInRedis, "loading old ops from mongo"
  55. DocOpsManager._getDocOpsFromMongo doc_id, backToVersion, oldestVersionInRedis, (error, ops) ->
  56. logger.log doc_id: doc_id, backToVersion: backToVersion, oldestVersionInRedis: oldestVersionInRedis, ops: ops, "loaded old ops from mongo"
  57. return callback(error) if error?
  58. RedisManager.prependDocOps doc_id, ops, (error) ->
  59. return callback(error) if error?
  60. callback null
  61. else
  62. logger.log doc_id: doc_id, backToVersion: backToVersion, oldestVersionInRedis: oldestVersionInRedis, "ops already in redis"
  63. callback()
  64. getDocVersionInMongo: (doc_id, callback = (error, version) ->) ->
  65. t = new Metrics.Timer("mongo-time")
  66. db.docOps.find {
  67. doc_id: ObjectId(doc_id)
  68. }, {
  69. version: 1
  70. }, (error, docs) ->
  71. t.done()
  72. return callback(error) if error?
  73. if docs.length < 1 or !docs[0].version?
  74. return callback null, 0
  75. else
  76. return callback null, docs[0].version
  77. APPEND_OPS_BATCH_SIZE: 100
  78. _appendDocOpsInMongo: (doc_id, docOps, newVersion, callback = (error) ->) ->
  79. currentVersion = newVersion - docOps.length
  80. batchSize = DocOpsManager.APPEND_OPS_BATCH_SIZE
  81. noOfBatches = Math.ceil(docOps.length / batchSize)
  82. if noOfBatches <= 0
  83. return callback()
  84. jobs = []
  85. for batchNo in [0..(noOfBatches-1)]
  86. do (batchNo) ->
  87. jobs.push (callback) ->
  88. batch = docOps.slice(batchNo * batchSize, (batchNo + 1) * batchSize)
  89. currentVersion += batch.length
  90. logger.log doc_id: doc_id, batchNo: batchNo, "appending doc op batch to Mongo"
  91. t = new Metrics.Timer("mongo-time")
  92. db.docOps.update {
  93. doc_id: ObjectId(doc_id)
  94. }, {
  95. $push: docOps: { $each: batch, $slice: -100 }
  96. $set: version: currentVersion
  97. }, {
  98. upsert: true
  99. }, (err)->
  100. t.done()
  101. callback(err)
  102. async.series jobs, (error) -> callback(error)
  103. _getDocOpsFromMongo: (doc_id, start, end, callback = (error, ops) ->) ->
  104. DocOpsManager.getDocVersionInMongo doc_id, (error, version) ->
  105. return callback(error) if error?
  106. offset = - (version - start) # Negative tells mongo to count from the end backwards
  107. limit = end - start
  108. t = new Metrics.Timer("mongo-time")
  109. db.docOps.find {
  110. doc_id: ObjectId(doc_id)
  111. }, {
  112. docOps: $slice: [offset, limit]
  113. }, (error, docs) ->
  114. t.done()
  115. if docs.length < 1 or !docs[0].docOps?
  116. return callback null, []
  117. else
  118. return callback null, docs[0].docOps