overleaf/services/document-updater/app/coffee/RedisManager.coffee

132 lines
5.4 KiB
CoffeeScript
Raw Normal View History

2014-02-12 05:40:42 -05:00
Settings = require('settings-sharelatex')
async = require('async')
rclient = require("./RedisBackend").createClient()
2014-02-12 05:40:42 -05:00
_ = require('underscore')
keys = require('./RedisKeyBuilder')
logger = require('logger-sharelatex')
metrics = require('./Metrics')
Errors = require "./Errors"
# Make times easy to read
minutes = 60 # seconds for Redis expire
module.exports = RedisManager =
rclient: rclient
putDocInMemory : (project_id, doc_id, docLines, version, track_changes_entries, _callback)->
2014-02-12 05:40:42 -05:00
timer = new metrics.Timer("redis.put-doc")
callback = (error) ->
2014-02-12 05:40:42 -05:00
timer.done()
_callback(error)
logger.log project_id:project_id, doc_id:doc_id, version: version, "putting doc in redis"
multi = rclient.multi()
multi.set keys.docLines(doc_id:doc_id), JSON.stringify(docLines)
multi.set keys.projectKey({doc_id:doc_id}), project_id
multi.set keys.docVersion(doc_id:doc_id), version
2016-11-28 05:14:42 -05:00
multi.set keys.trackChangesEntries(doc_id:doc_id), JSON.stringify(track_changes_entries)
multi.exec (error) ->
return callback(error) if error?
rclient.sadd keys.docsInProject(project_id:project_id), doc_id, callback
2014-02-12 05:40:42 -05:00
removeDocFromMemory : (project_id, doc_id, _callback)->
2014-02-12 05:40:42 -05:00
logger.log project_id:project_id, doc_id:doc_id, "removing doc from redis"
callback = (err) ->
2014-02-12 05:40:42 -05:00
if err?
logger.err project_id:project_id, doc_id:doc_id, err:err, "error removing doc from redis"
_callback(err)
2014-02-12 05:40:42 -05:00
else
logger.log project_id:project_id, doc_id:doc_id, "removed doc from redis"
_callback()
multi = rclient.multi()
multi.del keys.docLines(doc_id:doc_id)
multi.del keys.projectKey(doc_id:doc_id)
multi.del keys.docVersion(doc_id:doc_id)
2016-11-28 05:14:42 -05:00
multi.del keys.trackChangesEntries(doc_id:doc_id)
multi.exec (error) ->
return callback(error) if error?
rclient.srem keys.docsInProject(project_id:project_id), doc_id, callback
2014-02-12 05:40:42 -05:00
getDoc : (project_id, doc_id, callback = (error, lines, version, track_changes_entries) ->)->
2014-02-12 05:40:42 -05:00
timer = new metrics.Timer("redis.get-doc")
2016-06-01 05:53:50 -04:00
multi = rclient.multi()
multi.get keys.docLines(doc_id:doc_id)
2014-02-12 05:40:42 -05:00
multi.get keys.docVersion(doc_id:doc_id)
2016-09-02 09:47:41 -04:00
multi.get keys.projectKey(doc_id:doc_id)
2016-11-28 05:14:42 -05:00
multi.get keys.trackChangesEntries(doc_id:doc_id)
multi.exec (error, [docLines, version, doc_project_id, track_changes_entries])->
2014-02-12 05:40:42 -05:00
timer.done()
return callback(error) if error?
2016-06-01 05:53:50 -04:00
try
docLines = JSON.parse docLines
track_changes_entries = JSON.parse track_changes_entries
2016-06-01 05:53:50 -04:00
catch e
return callback(e)
version = parseInt(version or 0, 10)
2016-09-02 09:47:41 -04:00
# check doc is in requested project
if doc_project_id? and doc_project_id isnt project_id
logger.error project_id: project_id, doc_id: doc_id, doc_project_id: doc_project_id, "doc not in project"
return callback(new Errors.NotFoundError("document not found"))
callback null, docLines, version, track_changes_entries
2014-02-12 05:40:42 -05:00
getDocVersion: (doc_id, callback = (error, version) ->) ->
rclient.get keys.docVersion(doc_id: doc_id), (error, version) ->
return callback(error) if error?
version = parseInt(version, 10)
callback null, version
getPreviousDocOps: (doc_id, start, end, callback = (error, jsonOps) ->) ->
rclient.llen keys.docOps(doc_id: doc_id), (error, length) ->
return callback(error) if error?
rclient.get keys.docVersion(doc_id: doc_id), (error, version) ->
return callback(error) if error?
version = parseInt(version, 10)
first_version_in_redis = version - length
if start < first_version_in_redis or end > version
error = new Errors.OpRangeNotAvailableError("doc ops range is not loaded in redis")
logger.warn {err: error, doc_id, length, version, start, end}, "doc ops range is not loaded in redis"
2014-02-12 05:40:42 -05:00
return callback(error)
start = start - first_version_in_redis
if end > -1
end = end - first_version_in_redis
if isNaN(start) or isNaN(end)
error = new Error("inconsistent version or lengths")
logger.error {err: error, doc_id, length, version, start, end}, "inconsistent version or length"
2014-02-12 05:40:42 -05:00
return callback(error)
rclient.lrange keys.docOps(doc_id: doc_id), start, end, (error, jsonOps) ->
return callback(error) if error?
try
ops = jsonOps.map (jsonOp) -> JSON.parse jsonOp
catch e
return callback(e)
callback null, ops
DOC_OPS_TTL: 60 * minutes
DOC_OPS_MAX_LENGTH: 100
2016-11-28 05:14:42 -05:00
updateDocument : (doc_id, docLines, newVersion, appliedOps = [], track_changes_entries, callback = (error) ->)->
RedisManager.getDocVersion doc_id, (error, currentVersion) ->
2014-02-12 05:40:42 -05:00
return callback(error) if error?
if currentVersion + appliedOps.length != newVersion
error = new Error("Version mismatch. '#{doc_id}' is corrupted.")
logger.error {err: error, doc_id, currentVersion, newVersion, opsLength: appliedOps.length}, "version mismatch"
return callback(error)
jsonOps = appliedOps.map (op) -> JSON.stringify op
multi = rclient.multi()
multi.set keys.docLines(doc_id:doc_id), JSON.stringify(docLines)
multi.set keys.docVersion(doc_id:doc_id), newVersion
if jsonOps.length > 0
multi.rpush keys.docOps(doc_id: doc_id), jsonOps...
multi.expire keys.docOps(doc_id: doc_id), RedisManager.DOC_OPS_TTL
multi.ltrim keys.docOps(doc_id: doc_id), -RedisManager.DOC_OPS_MAX_LENGTH, -1
2016-11-28 05:14:42 -05:00
multi.set keys.trackChangesEntries(doc_id:doc_id), JSON.stringify(track_changes_entries)
multi.exec (error, replys) ->
return callback(error) if error?
return callback()
2014-02-12 05:40:42 -05:00
getDocIdsInProject: (project_id, callback = (error, doc_ids) ->) ->
rclient.smembers keys.docsInProject(project_id: project_id), callback