RedisBackend.coffee 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. Settings = require "settings-sharelatex"
  2. async = require "async"
  3. _ = require "underscore"
  4. logger = require "logger-sharelatex"
  5. class Client
  6. constructor: (@clients) ->
  7. multi: () ->
  8. return new MultiClient(
  9. @clients.map (client) -> {
  10. rclient: client.rclient.multi()
  11. key_schema: client.key_schema
  12. primary: client.primary
  13. driver: client.driver
  14. }
  15. )
  16. class MultiClient
  17. constructor: (@clients) ->
  18. exec: (callback) ->
  19. jobs = @clients.map (client) ->
  20. (cb) ->
  21. client.rclient.exec (error, result) ->
  22. if client.driver == "ioredis"
  23. # ioredis returns an results like:
  24. # [ [null, 42], [null, "foo"] ]
  25. # where the first entries in each 2-tuple are
  26. # presumably errors for each individual command,
  27. # and the second entry is the result. We need to transform
  28. # this into the same result as the old redis driver:
  29. # [ 42, "foo" ]
  30. filtered_result = []
  31. for entry in result or []
  32. if entry[0]?
  33. return cb(entry[0])
  34. else
  35. filtered_result.push entry[1]
  36. result = filtered_result
  37. if client.primary
  38. # Return this result as the actual result
  39. callback(error, result)
  40. # Send the rest through for comparison
  41. cb(error, result)
  42. async.parallel jobs, (error, results) ->
  43. if error?
  44. logger.error {err: error}, "error in redis backend"
  45. else
  46. compareResults(results)
  47. COMMANDS = {
  48. "get": 0,
  49. "smembers": 0,
  50. "set": 0,
  51. "srem": 0,
  52. "sadd": 0,
  53. "del": 0,
  54. "lrange": 0,
  55. "llen": 0,
  56. "rpush": 0,
  57. "expire": 0,
  58. "ltrim": 0,
  59. "incr": 0,
  60. "eval": 2
  61. }
  62. for command, key_pos of COMMANDS
  63. do (command, key_pos) ->
  64. Client.prototype[command] = (args..., callback) ->
  65. jobs = @clients.map (client) ->
  66. (cb) ->
  67. key_builder = args[key_pos]
  68. key = key_builder(client.key_schema)
  69. args_with_key = args.slice(0)
  70. args_with_key[key_pos] = key
  71. client.rclient[command] args_with_key..., (error, result...) ->
  72. if client.primary
  73. # Return this result as the actual result
  74. callback(error, result...)
  75. # Send the rest through for comparison
  76. cb(error, result...)
  77. async.parallel jobs, (error, results) ->
  78. if error?
  79. logger.error {err: error}, "error in redis backend"
  80. else
  81. compareResults(results)
  82. MultiClient.prototype[command] = (args...) ->
  83. for client in @clients
  84. key_builder = args[key_pos]
  85. key = key_builder(client.key_schema)
  86. args_with_key = args.slice(0)
  87. args_with_key[key_pos] = key
  88. client.rclient[command] args_with_key...
  89. compareResults = (results) ->
  90. return if results.length < 2
  91. first = results[0]
  92. for result in results.slice(1)
  93. if not _.isEqual(first, result)
  94. logger.warn { results }, "redis return values do not match"
  95. module.exports =
  96. createClient: () ->
  97. client_configs = Settings.redis.documentupdater
  98. unless client_configs instanceof Array
  99. client_configs.primary = true
  100. client_configs = [client_configs]
  101. clients = client_configs.map (config) ->
  102. if config.cluster?
  103. Redis = require("ioredis")
  104. rclient = new Redis.Cluster(config.cluster)
  105. driver = "ioredis"
  106. else
  107. {host, port, password} = config
  108. rclient = require("redis-sharelatex").createClient({host, port, password})
  109. driver = "redis"
  110. return {
  111. rclient: rclient
  112. key_schema: config.key_schema
  113. primary: config.primary
  114. driver: driver
  115. }
  116. return new Client(clients)