RedisManager.coffee 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. Settings = require('settings-sharelatex')
  2. async = require('async')
  3. rclient = require("./RedisBackend").createClient()
  4. _ = require('underscore')
  5. keys = require('./RedisKeyBuilder')
  6. logger = require('logger-sharelatex')
  7. metrics = require('./Metrics')
  8. Errors = require "./Errors"
  9. # Make times easy to read
  10. minutes = 60 # seconds for Redis expire
  11. module.exports = RedisManager =
  12. rclient: rclient
  13. putDocInMemory : (project_id, doc_id, docLines, version, track_changes_entries, _callback)->
  14. timer = new metrics.Timer("redis.put-doc")
  15. callback = (error) ->
  16. timer.done()
  17. _callback(error)
  18. logger.log project_id:project_id, doc_id:doc_id, version: version, "putting doc in redis"
  19. multi = rclient.multi()
  20. multi.set keys.docLines(doc_id:doc_id), JSON.stringify(docLines)
  21. multi.set keys.projectKey({doc_id:doc_id}), project_id
  22. multi.set keys.docVersion(doc_id:doc_id), version
  23. multi.set keys.trackChangesEntries(doc_id:doc_id), JSON.stringify(track_changes_entries)
  24. multi.exec (error) ->
  25. return callback(error) if error?
  26. rclient.sadd keys.docsInProject(project_id:project_id), doc_id, callback
  27. removeDocFromMemory : (project_id, doc_id, _callback)->
  28. logger.log project_id:project_id, doc_id:doc_id, "removing doc from redis"
  29. callback = (err) ->
  30. if err?
  31. logger.err project_id:project_id, doc_id:doc_id, err:err, "error removing doc from redis"
  32. _callback(err)
  33. else
  34. logger.log project_id:project_id, doc_id:doc_id, "removed doc from redis"
  35. _callback()
  36. multi = rclient.multi()
  37. multi.del keys.docLines(doc_id:doc_id)
  38. multi.del keys.projectKey(doc_id:doc_id)
  39. multi.del keys.docVersion(doc_id:doc_id)
  40. multi.del keys.trackChangesEntries(doc_id:doc_id)
  41. multi.exec (error) ->
  42. return callback(error) if error?
  43. rclient.srem keys.docsInProject(project_id:project_id), doc_id, callback
  44. getDoc : (project_id, doc_id, callback = (error, lines, version, track_changes_entries) ->)->
  45. timer = new metrics.Timer("redis.get-doc")
  46. multi = rclient.multi()
  47. multi.get keys.docLines(doc_id:doc_id)
  48. multi.get keys.docVersion(doc_id:doc_id)
  49. multi.get keys.projectKey(doc_id:doc_id)
  50. multi.get keys.trackChangesEntries(doc_id:doc_id)
  51. multi.exec (error, [docLines, version, doc_project_id, track_changes_entries])->
  52. timer.done()
  53. return callback(error) if error?
  54. try
  55. docLines = JSON.parse docLines
  56. track_changes_entries = JSON.parse track_changes_entries
  57. catch e
  58. return callback(e)
  59. version = parseInt(version or 0, 10)
  60. # check doc is in requested project
  61. if doc_project_id? and doc_project_id isnt project_id
  62. logger.error project_id: project_id, doc_id: doc_id, doc_project_id: doc_project_id, "doc not in project"
  63. return callback(new Errors.NotFoundError("document not found"))
  64. callback null, docLines, version, track_changes_entries
  65. getDocVersion: (doc_id, callback = (error, version) ->) ->
  66. rclient.get keys.docVersion(doc_id: doc_id), (error, version) ->
  67. return callback(error) if error?
  68. version = parseInt(version, 10)
  69. callback null, version
  70. getPreviousDocOps: (doc_id, start, end, callback = (error, jsonOps) ->) ->
  71. rclient.llen keys.docOps(doc_id: doc_id), (error, length) ->
  72. return callback(error) if error?
  73. rclient.get keys.docVersion(doc_id: doc_id), (error, version) ->
  74. return callback(error) if error?
  75. version = parseInt(version, 10)
  76. first_version_in_redis = version - length
  77. if start < first_version_in_redis or end > version
  78. error = new Errors.OpRangeNotAvailableError("doc ops range is not loaded in redis")
  79. logger.warn {err: error, doc_id, length, version, start, end}, "doc ops range is not loaded in redis"
  80. return callback(error)
  81. start = start - first_version_in_redis
  82. if end > -1
  83. end = end - first_version_in_redis
  84. if isNaN(start) or isNaN(end)
  85. error = new Error("inconsistent version or lengths")
  86. logger.error {err: error, doc_id, length, version, start, end}, "inconsistent version or length"
  87. return callback(error)
  88. rclient.lrange keys.docOps(doc_id: doc_id), start, end, (error, jsonOps) ->
  89. return callback(error) if error?
  90. try
  91. ops = jsonOps.map (jsonOp) -> JSON.parse jsonOp
  92. catch e
  93. return callback(e)
  94. callback null, ops
  95. DOC_OPS_TTL: 60 * minutes
  96. DOC_OPS_MAX_LENGTH: 100
  97. updateDocument : (doc_id, docLines, newVersion, appliedOps = [], track_changes_entries, callback = (error) ->)->
  98. RedisManager.getDocVersion doc_id, (error, currentVersion) ->
  99. return callback(error) if error?
  100. if currentVersion + appliedOps.length != newVersion
  101. error = new Error("Version mismatch. '#{doc_id}' is corrupted.")
  102. logger.error {err: error, doc_id, currentVersion, newVersion, opsLength: appliedOps.length}, "version mismatch"
  103. return callback(error)
  104. jsonOps = appliedOps.map (op) -> JSON.stringify op
  105. multi = rclient.multi()
  106. multi.set keys.docLines(doc_id:doc_id), JSON.stringify(docLines)
  107. multi.set keys.docVersion(doc_id:doc_id), newVersion
  108. if jsonOps.length > 0
  109. multi.rpush keys.docOps(doc_id: doc_id), jsonOps...
  110. multi.expire keys.docOps(doc_id: doc_id), RedisManager.DOC_OPS_TTL
  111. multi.ltrim keys.docOps(doc_id: doc_id), -RedisManager.DOC_OPS_MAX_LENGTH, -1
  112. multi.set keys.trackChangesEntries(doc_id:doc_id), JSON.stringify(track_changes_entries)
  113. multi.exec (error, replys) ->
  114. return callback(error) if error?
  115. return callback()
  116. getDocIdsInProject: (project_id, callback = (error, doc_ids) ->) ->
  117. rclient.smembers keys.docsInProject(project_id: project_id), callback