Могу ли я делать транзакции и блокировки в CouchDB? - PullRequest
78 голосов
/ 18 ноября 2008

Мне нужно сделать транзакции (начать, зафиксировать или откатить), заблокировать (выбрать для обновления). Как я могу сделать это в модели документа БД?

Edit:

Дело в следующем:

  • Я хочу запустить сайт аукционов.
  • И я думаю, как направить покупку.
  • При прямой покупке я должен уменьшить поле количества в записи позиции, но только если количество больше нуля. Вот почему мне нужны блокировки и транзакции.
  • Я не знаю, как решить эту проблему без блокировок и / или транзакций.

Могу ли я решить эту проблему с помощью CouchDB?

Ответы [ 7 ]

137 голосов
/ 18 ноября 2008

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

Это обманчиво просто, правда. Вы можете переосмыслить многие обычные сценарии на основе транзакций для CouchDB. Тем не менее, при изучении CouchDB вам все равно нужно отбросить свои знания в области СУБД. Полезно подходить к проблемам с более высокого уровня, а не пытаться превратить Couch в мир, основанный на SQL.

Отслеживание инвентаря

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

  1. Получите документ, обратите внимание на свойство _rev, которое CouchDB отправляет вместе
  2. Уменьшить поле количества, если оно больше нуля
  3. Отправьте обновленный документ обратно, используя свойство _rev
  4. Если _rev соответствует текущему сохраненному номеру, сделайте!
  5. Если есть конфликт (когда _rev не совпадает), получить самую новую версию документа

В этом случае есть два возможных сценария отказа, о которых стоит подумать. Если последняя версия документа имеет количество 0, вы обрабатываете ее так же, как в СУБД, и предупреждаете пользователя о том, что он на самом деле не может купить то, что он хотел купить. Если последняя версия документа имеет количество больше 0, вы просто повторяете операцию с обновленными данными и начинаете сначала. Это заставляет вас выполнять немного больше работы, чем это делает СУБД, и может стать немного раздражающим при частых конфликтующих обновлениях.

Теперь, ответ, который я только что дал, предполагает, что вы будете делать вещи в CouchDB почти так же, как в RDBMS. Я мог бы подойти к этой проблеме немного по-другому:

Я бы начал с документа «основной продукт», который включает все данные дескриптора (имя, изображение, описание, цена и т. Д.). Затем я бы добавил документ «инвентарный билет» для каждого конкретного экземпляра с полями для product_key и claimed_by. Если вы продаете модель молотка, и у вас есть 20 для продажи, у вас могут быть документы с ключами, такими как hammer-1, hammer-2 и т. Д., Для представления каждого доступного молотка.

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

Карта

function(doc) 
{ 
    if (doc.type == 'inventory_ticket' && doc.claimed_by == null ) { 
        emit(doc.product_key, { 'inventory_ticket' :doc.id, '_rev' : doc._rev }); 
    } 
}

Это дает мне список доступных "билетов", по ключу продукта. Я мог бы захватить группу из них, когда кто-то хочет купить молот, а затем перебирать отправку обновлений (используя id и _rev), пока я не получу одно из них (ранее заявленные билеты приведут к ошибке обновления).

Снижение

function (keys, values, combine) {
    return values.length;
}

Эта функция сокращения просто возвращает общее количество невостребованных inventory_ticket предметов, поэтому вы можете определить, сколько «молотков» доступно для покупки.

Предостережения

Это решение представляет собой примерно 3,5 минуты на обдумывание конкретной проблемы, которую вы представили. Там могут быть лучшие способы сделать это! Тем не менее, это существенно уменьшает конфликтующие обновления и сокращает необходимость реагировать на конфликт новым обновлением. В этой модели несколько пользователей не будут пытаться изменить данные в первичной записи продукта. В худшем случае у вас будет несколько пользователей, пытающихся получить один билет, и, если вы захватили несколько из них, вы просто перейдете к следующему билету и повторите попытку.

Ссылка: https://wiki.apache.org/couchdb/Frequently_asked_questions#How_do_I_use_transactions_with_CouchDB.3F

24 голосов
/ 25 ноября 2008

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

20 голосов
/ 01 августа 2009

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

  • Создание документа транзакции «Перечисление 10 долларов США со счета 11223 на счет 88733». Это создает напряжение в системе.
  • Чтобы разрешить любое сканирование натяжения для всех документов транзакции и
    • Если исходная учетная запись еще не обновлена, обновите исходную учетную запись (-10 USD)
    • Если исходная учетная запись была обновлена, но документ транзакции не показывает это, обновите документ транзакции (например, установите флаг «sourcedone» в документе)
    • Если целевой аккаунт еще не обновлен, обновите целевой аккаунт (+10 USD)
    • Если целевой счет был обновлен, но документ транзакции не показывает это, обновите документ транзакции
    • Если обе учетные записи были обновлены, вы можете удалить документ транзакции или оставить его для аудита.

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

Другая возможная реализация полностью исключает необходимость транзакций: просто сохраняйте документы о напряжении и оценивайте состояние вашей системы, оценивая каждый задействованный документ о напряжении. В приведенном выше примере это будет означать, что итоговая сумма для счета определяется только как значения суммы в документах транзакции, в которых участвует эта учетная запись. В Couchdb вы можете очень хорошо смоделировать это как представление карты / уменьшения.

5 голосов
/ 09 августа 2013

В ответ на проблему ОП, Couch, вероятно, не лучший выбор здесь. Использование представлений - отличный способ отслеживать инвентарь, но ограничение до 0 более или менее невозможно. Проблема заключается в состоянии гонки, когда вы читаете результат представления, решаете, что вы можете использовать элемент «молоток-1», а затем пишете документ для его использования. Проблема в том, что не существует атомарного способа написать документ, использующий молоток, если результат представления> 0 молотков-1. Если все 100 пользователей одновременно запрашивают представление и видят 1 молоток-1, они все могут написать документ, чтобы использовать молоток 1, в результате чего получается -99 молоток-1. На практике условия гонки будут довольно небольшими - действительно маленькими, если ваша БД работает под управлением localhost. Но как только вы масштабируете и получите сервер или кластер вне базы данных, проблема станет гораздо более заметной. Несмотря на это, недопустимо иметь такое состояние гонки в системе, связанной с важными деньгами.

Обновление ответа MrKurt (возможно, оно устарело или он не знал о некоторых возможностях CouchDB)

Представление - это хороший способ обработки таких вещей, как остатки / запасы в CouchDB.

Вам не нужно испускать docid и rev в представлении. Вы получаете оба из них бесплатно, когда вы получаете результаты просмотра. Их испускание - особенно в многословном формате, таком как словарь, - просто увеличит ваш взгляд излишне большой.

Простой вид для отслеживания остатков инвентаря должен выглядеть примерно так (также с моей головы)

function( doc )
{
    if( doc.InventoryChange != undefined ) {
        for( product_key in doc.InventoryChange ) {
            emit( product_key, 1 );
        }
    }
}

А функция уменьшения еще проще

_sum

При этом используется встроенная функция , которая просто суммирует значения всех строк с соответствующими ключами.

В этом представлении любой документ может иметь члена "InventoryChange", который отображает ключ product_key на изменение общего инвентарного количества их. то есть.

{
    "_id": "abc123",
    "InventoryChange": {
         "hammer_1234": 10,
         "saw_4321": 25
     }
}

Добавил бы 10 hammer_1234 и 25 saw_4321.

{
    "_id": "def456",
    "InventoryChange": {
        "hammer_1234": -5
    }
}

Сожжет 5 молотков из инвентаря.

В этой модели вы никогда не обновляете данные, только добавляете их. Это означает, что нет возможности для конфликтов обновления. Все транзакционные проблемы обновления данных уходят:)

Еще одна приятная вещь в этой модели - то, что ЛЮБОЙ документ в БД может как добавлять, так и вычитать предметы из инвентаря. Эти документы могут содержать все виды других данных. У вас может быть документ «Отгрузка» с кучей данных о дате и времени получения, хранилище, получающем сотруднике и т. Д., И если этот документ определяет InventoryChange, он обновит инвентарь. Как и документ «Продажа», документ «DamagedItem» и т. Д. Глядя на каждый документ, они читают очень четко. И представление справляется со всей тяжелой работой.

5 голосов
/ 25 октября 2011

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

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

Если вы можете гарантировать, что у вас есть только один экземпляр CouchDB или что все, кто изменяет определенный документ, подключаются к одному и тому же экземпляру CouchDB, то вы можете использовать систему обнаружения конфликтов, чтобы создать некую атомарность, используя методы, описанные выше, но если позже вы увеличите масштаб в кластер или использовать размещенный сервис, такой как Cloudant, он сломается, и вам придется переделать эту часть системы.

Итак, я бы предложил использовать что-то кроме CouchDB для баланса вашего счета, так будет намного проще.

3 голосов
/ 20 февраля 2009

На самом деле, вы можете в пути. Взгляните на HTTP Document API и прокрутите вниз до заголовка «Изменить несколько документов с помощью одного запроса».

По сути, вы можете создавать / обновлять / удалять пачку документов в одном запросе на отправку по адресу URI / {имя_библиотеки} / _ bulk_docs , и все они либо будут выполнены успешно, либо все завершатся неудачно. Однако документ предупреждает, что это поведение может измениться в будущем.

РЕДАКТИРОВАТЬ: Как и ожидалось, начиная с версии 0.9, массовые документы больше не работают таким образом.

0 голосов
/ 21 июня 2017

Просто используйте облегченное решение SQlite для транзакций, а после успешного завершения транзакции реплицируйте его и пометьте как реплицированное в SQLite

Таблица SQLite

txn_id    , txn_attribute1, txn_attribute2,......,txn_status
dhwdhwu$sg1   x                    y               added/replicated

Вы также можете удалить транзакции, которые успешно реплицированы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...