Как хранить документы MongoDB в Redis в виде кеша и сбрасывать по тайм-ауту

У меня есть приложение nodejs, которое обрабатывает вложенные документы JSON, например:

var post = {
  id: 123,
  title: 'Sterling Archer',    
  comments: [
    {text: 'Comment text', tags: ['tag1', 'tag2', 'tag3']},
    {text: 'Comment test', tags: ['tag2', 'tag5']}
  ]  
};

И сохраняет их в базе данных MongoDB. Мои документы требуют частых обновлений, но, как вы знаете, MongoDB очень медленно выполняет запись из-за своей природы. Чтобы решить эту проблему, я решил хранить документы в Redis и сбрасывать их в MongoDB через некоторое время (например, через 1-2 часа).

Итак, вот пример кода моего подхода к обновлению:

var redis_cli = require("redis").createClient();

app.post('/update/:id', function (req, res, next) {

  var id = req.params.id
  redis_cli.get(id, function (err, document) {
      if (err) return next(err);

      // If there is no key, try to get from MongoDB
      if (!document) {
         DocumentModel.findOneByid(id, function (err, document) {
             if (err) return next(err);
             if (!document) return next(new Error("Document with id " + id + " does not exists"));

             // Document in MongoDB, so store in redis
             redis_cli.set(id, JSON.stringify(document), function (err) {
                  if (err) return next(err);

                  updateDocument(document);
             });                    
         });    
      } else {
          updateDocument(JSON.parse(document));
      }

      function updateDocument (document) {
          // Do some updates ...
          document.title = "Updated post title";

          // Store to redis
          redis_cli.set(id, JSON.strinfy(document), function (err) {
              if (err) return next(err);

              // Document updated successful
              return res.status(200).send('OK');
          });              
      }          
  });
});

Мой первый вопрос: что вы думаете о моем подходе к работе с документами? Есть ли проблемы с моим подходом?

И второй вопрос, как сбросить документы в Redis обратно в mongodb и удалить из redis? Моя цель заключается в следующем: я хочу хранить документы в Redis только тогда, когда это необходимо моим пользователям, поэтому, если они не работают с их документом, его следует хранить в MongoDB вместо Redis.


person Erik    schedule 13.09.2014    source источник


Ответы (1)


Ваш подход кажется разумным.

Чтобы «сбросить» документы, сохраните в Redis отсортированный набор идентификаторов документов, которые были обновлены, а их оценки будут установлены на метку времени обновления. Периодически, т.е. каждую минуту выполняйте ZRANGE для этого набора, чтобы получить «старые» (например, последнее обновление более часа назад) идентификаторы документов, и для каждого идентификатора выполняйте GET документа, записывайте его в Mongo, DEL документ и ZREM из заказанный набор.

ИЗМЕНИТЬ непроверенные и полностью вымышленные примеры кода в псевдо-Node.js:

 function updateDocument (document) {
      // Do some updates ...
      document.title = "Updated post title";

      // Store to redis
      multi = redis_cli.multi();
      multi.set(id, JSON.strinfy(document);
      multi.zadd('last_update', time(), id);
      multi.exec(), function (err, replies) {
          if (err) return next(err);

          // Document updated successful
          return res.status(200).send('OK');
      });              
  }

  // call this function periodically, e.g. every minute or so
  function flushOldDocuments () {
      fromTime = time()-3600;
      while (redis_cli.zcount('last_update', '-inf', fromTime) > 0) {
          id = redis_cli.zrangebyscore('last_update', '-inf', fromTime, false, 0, 1); // no scores, offset 0, limit 1 -> get the oldest document
          redis_cli.watch(id);
          Mongo.write(JSON.parse(redis_cli.get(id))); // or something like that
          multi = redis_cli.multi();
          multi.zrem('last_update', id);
          multi.del(id);
          multi.exec(), function(err, replies) {
              // if the watch fails the exec, no harm done and the document will be flushed in an hour
              ...
          };
      };
  }
person Itamar Haber    schedule 13.09.2014
comment
Спасибо за ответ. У меня есть вопрос. Должен ли я как-то заблокировать операцию обновления для документа, подготовленного к очистке? А что будет, если этого не делать? - person Erik; 14.09.2014
comment
В этом случае я не считаю, что есть веская причина блокировать обновления во время очистки, просто ПРОСМОТРИТЕ документ и выполните DEL и ZREM внутри MULTI/EXEC. Если документ обновляется в Redis, пока вы пишете его в Mongo, DEL завершится ошибкой без вреда для себя. - person Itamar Haber; 14.09.2014
comment
спасибо за ответы еще раз. Но, к сожалению, я не совсем понимаю ваш последний ответ. Пожалуйста, не могли бы вы предоставить краткий фрагмент кода вашего подхода? - person Erik; 14.09.2014
comment
Сделал все возможное, чтобы выразить идею в Node.js - он, вероятно, не будет работать как есть :) - person Itamar Haber; 14.09.2014
comment
Спасибо большое, теперь я понимаю твой подход. Ты очень умный :) - person Erik; 14.09.2014
comment
@tamar Haber, последний вопрос: применяется ли MULTI/EXEC только на одной машине? Если да, то как следует применять ваш подход в распределенном кластере Redis? - person Erik; 14.09.2014
comment
Multi/exec действительно для одного сервера, даже в кластере. Если ключи в блоке multi/exec находятся в разных слотах, вы получите ошибку CROSSSLOT. Вы используете кластер Redis? - person Itamar Haber; 14.09.2014
comment
Не сейчас, но думаю о масштабируемости. - person Erik; 14.09.2014
comment
Давайте продолжим обсуждение в чате. - person Erik; 14.09.2014