Как хранить операции json-patch в очереди Redis и гарантировать их согласованность?

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

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

Мой подход заключается в использовании спецификации rfc6902 (JSONPatch) с jsonpatch для исправления документа JSON. Все такие документы хранятся в базе данных MongoDB, и, как вы знаете, последний из них очень медленный для частой записи.

Чтобы получить больше скорости и приложения с высокой нагрузкой, я использую Redis в качестве очереди для операций исправления, например:

{ "op": "add", "path": "/comments/2", "value":  {text: 'Comment test3', tags: ['tag4']}" }

Я просто сохраняю все такие операции с исправлениями в очереди и в полночь запускаю скрипт cron, чтобы получить все исправления, создать полный документ и обновить его в базе данных MongoDB.

Я пока не понимаю, что мне делать в случае испорченного патча, например:

{ "op": "add", "path": "/comments/0/tags/5", "value": 'tag4'}

Приведенный выше патч не применяется к документу выше, потому что массив tags имеет длину всего 3 (согласно официальным документам http://tools.ietf.org/html/rfc6902#page-5)

 The specified index MUST NOT be greater than the number of elements in the array.

Поэтому, когда пользователь находится в сети, он не получает никаких ошибок, потому что его операции исправления сохраняются в очереди Redis, но на следующий день он получает поврежденный документ из-за сломанного исправления, которое не было применено в сценарии cron.

Итак, мой вопрос: как я могу гарантировать, что все исправления, хранящиеся в очереди Redis, верны и не портят первичный документ?


person Erik    schedule 14.09.2014    source источник
comment
на высоком уровне, что бы вы предпочли, чтобы произошло в случае повреждения diff? Вы хотите попытаться исправить проблему или просто выбросить diff?   -  person aembke    schedule 17.09.2014
comment
Как пользователь создает плохой патч?   -  person zenbeni    schedule 18.09.2014
comment
Я не знаю. Мне нужно как-то предотвратить повреждение diff. Может есть лучшие практики?   -  person Erik    schedule 18.09.2014
comment
Как Google docs решает эту проблему?   -  person Erik    schedule 18.09.2014
comment
Как создается поврежденный патч? Это нужно определить в первую очередь. Может ли быть так, что узел исправления JSON, загруженный из самой программы, неверен? Пожалуйста, поделитесь кодом, отправляющим данные JSON и cron.   -  person Vickrant    schedule 19.09.2014
comment
@Vickrant › Как создается поврежденный патч? Например, используйте отправку поврежденного патча через Chrome Developers Tools, иначе у меня будет ошибка в моем коде.   -  person Erik    schedule 19.09.2014
comment
› Может ли быть так, что узел исправления JSON, загруженный из самой программы, неправильный? Да, теоретически я могу ошибаться в моем коде   -  person Erik    schedule 19.09.2014
comment
› Пожалуйста, поделитесь кодом, передающим данные JSON и cron: я использую github.com/Starcounter-Jack/JSON -Пропатчить модуль. Вы можете увидеть много примеров, поэтому в моем cron я использую один и тот же   -  person Erik    schedule 19.09.2014


Ответы (2)


ИМХО, вы вводите ненужную сложность вместо более простого решения. Это были бы мои альтернативные предложения вместо вашего подхода к json patch cron, который очень сложно сделать последовательным и атомарным.

  1. Использовать только mongodb: при правильном проектировании базы данных и индексации, а также при правильном распределении/разбиении аппаратного обеспечения производительность записи mongodb очень высока. И типы операций, которые вы используете в jsonpatch, изначально поддерживаются в документах mongodb BSON и их языке запросов, например, $push, $set, $inc, $pull и т. д. Возможно, вы хотите не прерывать действия пользователей с помощью синхронной записи в Mongodb , для этого решение использует асинхронную очередь, как указано в пункте № 2.

    2.Использовать очереди задач и mongodb: вместо хранения исправлений в Redis, как вы делаете сейчас, вы можете поместить задачу исправления в очередь задач, которая будет асинхронно выполнять обновление mongodb, и пользователь не будет испытывать любое медленное исполнение. Одной из очень хороших очередей задач является Celery , которая может использовать Redis в качестве брокера и серверной части для обмена сообщениями. Таким образом, обновления каждого пользователя получают одну задачу и будут применяться к mongodb очередью задач, и производительность не пострадает.

person DhruvPathak    schedule 20.09.2014
comment
Хотя я дал вам способы анализа ситуации и потенциального решения проблем согласованности, я согласен с этим ответом. - person Vinay; 21.09.2014
comment
@DhruvPathak Мне нравятся ваши предложения, но я не понимаю, как я могу обновить свой документ с помощью $push, $set, $inc, $pull и т. д. Скажем, как я могу обновить документ post с патчем { "op": "add", "path": "/comments/2", "value": {text: 'Comment test3', tags: ['tag4']} с помощью операторов MongoDB. Пожалуйста, не могли бы вы привести краткий пример? - person Erik; 21.09.2014
comment
@ Эрик Это было бы db.myCollection.update({_id:123},{'$push':{'comments':{text: 'Comment test3', tags: ['tag4']}}) - person DhruvPathak; 23.09.2014
comment
@DhruvPathak спасибо за ответ. Я попробовал ваше предложение для всех операций исправления и обнаружил, что не могу выполнить move операцию исправления tools.ietf.org/html/rfc6902#appendix-A.7, поскольку в MongoDB его нет. Можно ли как-то обойти это? - person Erik; 23.09.2014
comment
Вы можете добиться того же, используя docs.mongodb.org/master/reference/operator /update/position , но для этого потребуется 2 команды подряд. Или выполните команду javascript, которая выполняет обе эти команды на стороне сервера. - person DhruvPathak; 24.09.2014

Как и в случае с любой системой, которая может стать несовместимой, вы должны разрешить применение исправлений как можно быстрее, если хотите быстрее обнаруживать конфликты и уменьшать вероятность их возникновения. Скорее всего, это ваша основная проблема, если вы не уведомляете других клиентов о каких-либо обновленных данных как можно скорее (и просто ждете, пока CRON запустится для обновления общих данных, к которым могут получить доступ другие клиенты). ).

Как спрашивали другие, важно понять, как «плохой» патч вообще попал в очередь операций. Вот некоторые предположения с моей точки зрения:

  1. Пользователь применил некоторые операции, которые потерялись при переводе. Как? Я не знаю, но это объяснило бы несоответствие.
  2. Операции не применяются в правильном порядке. Как? Я не знаю. У меня нет кода, чтобы уйти.

Хотя у меня нет кода, на который можно было бы опереться, я могу сделать снимок в темноте и помочь вам проанализировать последний пункт. Первое, что нам нужно проанализировать, — это различные сценарии, которые могут возникнуть при обновлении «общего» ресурса. Важно отметить, что в любой системе, которая в конечном итоге должна быть последовательной, мы заботимся о:

  1. Порядок операций.
  2. Как мы будем справляться с конфликтами.

Последнее действительно зависит от вас, и вам понадобится хорошая система уведомлений / обмена сообщениями, чтобы обновлять «правду», которую видят клиенты.

Сценарий 1

Пользователь A применяет операции 1 и 2. Документ обновляется на сервере, а затем пользователь B уведомляется об этом. Пользователь Б собирался применить операции 3 и 4, но эти операции (в таком порядке) не конфликтуют с операциями 1 и 2. В мире все хорошо. Это хорошая ситуация.

Сценарий 2

Пользователь А применяет операции 1 и 2. Пользователь Б применяет операции 3 и 4.

Если вы применяете операции атомарно для каждого пользователя, вы можете получить следующие очереди:

[1,2,3,4] [3,4,1,2]

В любом месте, если возникает конфликт, вы должны уведомить либо пользователя A, либо пользователя B на основе того, «кто дошел первым» (или любой другой семантики взвешивания, которую вы хотите использовать). Опять же, как вы справляетесь с конфликтами, зависит от вас. Если вы еще не читали о векторных часах, вам следует это сделать.

Если вы не применяете операции атомарно для каждого пользователя, вы можете получить следующие очереди:

[1,2,3,4] [3,4,1,2] [1,3,2,4] [3,1,4,2] [3,1,2,4] [1,3,4,2]

Как видите, отказ от атомарных обновлений для каждого пользователя увеличивает количество комбинаций обновлений и, следовательно, увеличивает вероятность возникновения коллизии. Я призываю вас следить за тем, чтобы операции добавлялись в очередь атомарно для каждого пользователя.

Резюме

Некоторые важные вещи, которые вы должны помнить:

  1. Убедитесь, что обновления очереди применяются атомарно для каждого пользователя.
  2. Выясните, как вы будете справляться с несколькими версиями общего ресурса, возникающими в результате множественных мутаций от разных клиентов (опять же, я предлагаю вам прочитать о векторных часах).
  3. Не обновляйте общий ресурс, к которому могут обращаться несколько клиентов в режиме реального времени, как задание cron.
  4. Когда есть конфликт, который не может быть разрешен, подумайте, как вы будете с ним справляться.
  5. В результате пункта 3 вам нужно будет придумать систему уведомлений, чтобы клиенты могли быстро получать обновленные ресурсы. В результате пункта 4 вы можете включить сообщение клиентам о том, что что-то пошло не так с их обновлением. Что-то, что только что пришло мне в голову, это то, что вы уже используете Redis, который имеет возможности pub/sub.

ИЗМЕНИТЬ:

Похоже, Google Docs разрешает конфликты с помощью преобразований. То есть путем сдвига целых символов/строк, чтобы освободить место для гибридного применения всех операций: docs_22.html" rel="nofollow noreferrer">https://drive.googleblog.com/2010/09/whats- Different-about-new-google-docs_22.html

Как я уже говорил ранее, все зависит от того, как вы хотите справляться со своими собственными конфликтами, которые в значительной степени должны определяться самим приложением/продуктом и вариантами его использования.

person Vinay    schedule 19.09.2014
comment
Спасибо за ответ. Сейчас я использую номер версии и увеличиваю его с каждым патчем. Поэтому, если пользователь отправит неправильный номер версии, я сброшу его патч. Я понимаю ваше предложение и хочу попробовать Redis. что вы думаете о следующем сценарии: я храню свой документ JSON в MongoDB (например) и при необходимости сохраняю в Redis (пока пользователи не в сети), а после того, как пользователи не в сети, я сбрасываю все данные Redis в MongoDB обратно - person Erik; 19.09.2014
comment
Я склонен согласиться с ответом @DhruvPathak. Хотя я дал вам краткий обзор того, как вы можете столкнуться со своими проблемами, я думаю, вам следует начать с простого и сделать что-то вроде Celery + Redis + Mongo. - person Vinay; 21.09.2014