Чи можу я робити транзакції та блокування в CouchDB?


81

Мені потрібно робити транзакції (почати, фіксувати або відкат), блокувати (вибрати для оновлення). Як я можу це зробити в моделі документа db?

Редагувати:

Справа в наступному:

  • Я хочу запустити аукціон.
  • І я думаю, як також направити покупку.
  • При прямій покупці я повинен зменшити поле кількості в записі товару, але тільки якщо кількість більше нуля. Тому мені потрібні блокування та транзакції.
  • Я не знаю, як вирішити це питання без блокування та / або транзакцій.

Чи можу я це вирішити за допомогою CouchDB?

Відповіді:


145

Ні. CouchDB використовує модель "оптимістичної одночасності". Найпростішими словами, це просто означає, що ви надсилаєте версію документа разом із вашим оновленням, а CouchDB відхиляє зміни, якщо поточна версія документа не відповідає тому, що ви надіслали.

Насправді це оманливо просто. Ви можете переробити багато звичайних сценаріїв на основі транзакцій для CouchDB. Вам все одно потрібно викидати свої знання домену RDBMS, вивчаючи CouchDB. Корисно підходити до проблем з більш високого рівня, а не намагатися формувати Couch до світу, заснованого на SQL.

Ведення обліку товарно-матеріальних цінностей

Проблема, яку ви окреслили, - це в першу чергу проблема з інвентаризацією. Якщо у вас є документ, що описує товар, і він містить поле для "наявної кількості", ви можете вирішити такі проблеми, як:

  1. Отримайте документ, зверніть увагу на _revвластивість, яку CouchDB надсилає
  2. Зменште поле кількості, якщо воно більше нуля
  3. Відправте оновлений документ назад, використовуючи _revвластивість
  4. Якщо _revзбігається із збереженим на даний момент номером, будьте готові!
  5. Якщо виник конфлікт (коли _revне збігається), завантажте найновішу версію документа

У цьому випадку є два можливих сценарії відмови, про які слід подумати. Якщо найновіша версія документа має кількість 0, ви обробляєте це так само, як і в СУБД, і попереджаєте користувача, що він фактично не може придбати те, що хотів придбати. Якщо остання версія документа має кількість більше 0, ви просто повторюєте операцію з оновленими даними та починаєте спочатку. Це змушує вас робити трохи більше роботи, ніж RDBMS, і може трохи дратувати, якщо часто виникають суперечливі оновлення.

Тепер відповідь, яку я щойно дав, передбачає, що ви збираєтеся робити речі в 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


4
Мені незрозуміло, як наявність "квитків", на які ви намагаєтесь претендувати послідовно, є суттєвим поліпшенням порівняно з простою повторною спробою прочитати / змінити / записати для оновлення основного об'єкта. Звичайно, це не здається вартим зайвих накладних витрат, особливо якщо у вас є великі запаси.
Нік Джонсон,

4
З моєї точки зору, конвенція про квитки є "простішою" для побудови. Невдалі оновлення основного запису вимагають перезавантаження документа, повторного виконання операції та збереження. Що стосується квитків, ви можете спробувати "заявити" щось, не вимагаючи більше даних.
MrKurt

Крім того, це залежить від того, які накладні витрати вас турбують. Ви або будете боротися з підвищеною суперечкою, або матимете додаткові вимоги до зберігання. Враховуючи, що квиток також може подвоїтися як запис про покупку, я не знаю, що проблема зі зберіганням буде такою великою, як ви думаєте.
MrKurt

2
Я редагую поле кількості документа продукту. Тоді я повинен створити тисячі "квитків", якщо кількість = 2K, наприклад. Потім, зменшуючи кількість, я повинен видалити деякі квитки. Звучить для мене абсолютно неспокійно. Багато головного болю в основних випадках використання. Можливо, мені чогось не вистачає, але чому б не повернути раніше видалену поведінку транзакції, просто зробіть це необов’язковим, наприклад, _bulk_docs? Reject_on_conflict = true. Досить корисний у конфігураціях з одним майстром.
Сем

3
@mehaase: Прочитайте це: guide.couchdb.org/draft/recipes.html , відповідь зводиться до внутрішньої структури даних couchdb "ви ніколи не змінюєте дані, ви просто додаєте нові". У вашому сценарії це означає створення однієї (атомної) транзакції з рахунку на транзитний рахунок для дебету та другої (атомної) транзакції з транзитного рахунку вперед (або назад). Ось як це роблять справжні банки. Кожен крок завжди задокументований.
Fabian Zeindl

26

Розширюючи відповідь MrKurt. У багатьох сценаріях вам не потрібно викуповувати квитки на фонди в порядку. Замість того, щоб вибрати перший квиток, ви можете вибрати випадковим чином з решти квитків. Враховуючи велику кількість квитків і велику кількість одночасних запитів, ви отримаєте значно меншу суперечку щодо цих квитків, порівняно з усіма, хто намагається отримати перший квиток.


21

Шаблон дизайну для спокійних транзакцій полягає у створенні "напруги" в системі. Для популярного прикладу використання транзакції з банківським рахунком ви повинні забезпечити оновлення суми для обох залучених рахунків:

  • Створіть документ транзакції "перекажіть 10 доларів США з рахунку 11223 на рахунок 88733". Це створює напругу в системі.
  • Щоб вирішити будь-яке напруження, скануйте всі операційні документи та
    • Якщо вихідний рахунок ще не оновлений, оновіть вихідний рахунок (-10 USD)
    • Якщо вихідний рахунок був оновлений, але документ транзакції цього не показує, оновіть документ транзакції (наприклад, встановіть прапорець "sourcedone" у документі)
    • Якщо цільовий рахунок ще не оновлений, оновіть цільовий рахунок (+10 USD)
    • Якщо цільовий рахунок був оновлений, але документ транзакції цього не відображає, оновіть документ транзакції
    • Якщо обидва рахунки оновлені, ви можете видалити документ транзакції або залишити його для аудиту.

Сканування на наявність напруги повинно проводитись у фоновому режимі для всіх "натяжних документів", щоб періоди напруги в системі були короткими. У наведеному вище прикладі очікувана короткочасна невідповідність, коли перший обліковий запис оновлено, але другий ще не оновлений. Це слід враховувати так само, як ви будете мати справу з можливістю узгодженості, якщо ваш Couchdb поширюється.

Інша можлива реалізація дозволяє уникнути потреби у транзакціях повністю: просто зберігайте документи про напругу та оцінюйте стан своєї системи, оцінюючи кожен задіяний документ про напругу. У наведеному вище прикладі це означало б, що загальна сума для рахунку визначається лише як значення суми в документах транзакцій, де цей рахунок бере участь. У Couchdb ви можете дуже добре змоделювати це як карту / зменшити подання.


5
Але як щодо випадків, коли рахунок дебетується, але документ напруги не змінюється? Будь-який сценарій відмови між цими двома точками, якщо вони не є атомними, спричинить постійну невідповідність, чи не так? Щось у процесі має бути атомним, у цьому і полягає суть транзакції.
Ian Varley

Так, ви праві, у цьому випадку - поки напруга не буде вирішена - буде непослідовність. Однак невідповідність лише тимчасова, поки наступне сканування документів на напругу не виявить цього. Це торгівля у цьому випадку, якась можлива послідовність щодо часу. Поки ви спершу відхиляєте вихідний рахунок, а пізніше збільшуєте цільовий рахунок, це може бути прийнятним. Але будьте обережні: документи, що стосуються напруги, не даватимуть вам трансакції з кислотою поверх REST. Але вони можуть стати гарним компромісом між чистими REST та кислотою.
ordnungswidrig

4
Уявіть, що кожен документ про напругу має позначку часу, а в облікових документах є поле "застосовано останнє напруження" - або список застосованих напружень. Коли ви дебетуєте вихідний рахунок, ви також оновлюєте поле "застосовується останнє напруження". Ці дві операції є атомними, оскільки вони знаходяться в одному документі. Цільовий рахунок також має подібне поле. Таким чином система завжди може визначити, які документи напруги були застосовані до яких рахунків.
Джессі Халлетт,

1
Як визначити, чи вже був оновлений вихідний / цільовий документ? Що робити, якщо він не вдається після кроку 1, а потім виконується повторно і знову не вдається, і так далі, ви продовжуватимете відраховувати вихідний рахунок?
wump

1
@wump: вам потрібно буде записати, що документ натягу застосовано до рахунку. наприклад, додавши ідентифікатор документа напруги до властивості списку будь-якого облікового запису. коли всі рахунки, до яких торкається документ про напругу, оновлені, позначте документ про напругу як "готовий" або видаліть його. Згодом ідентифікатор документа можна вилучити зі списку для всіх облікових записів.
ordnungswidrig

6

Ні, CouchDB, як правило, не підходить для транзакційних додатків, оскільки він не підтримує атомні операції в кластерному / тиражованому середовищі.

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

Якщо ви можете гарантувати, що у вас є лише один екземпляр CouchDB або що кожен, хто модифікує певний документ, підключається до того самого екземпляра CouchDB, тоді ви можете використовувати систему виявлення конфліктів для створення свого роду атомності, використовуючи методи, описані вище, але якщо пізніше ви масштабуєтеся до кластера або скористайтеся такою службою, як Cloudant, вона вийде з ладу, і вам доведеться переробити цю частину системи.

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


5

Як відповідь на проблему ОП, диван тут, мабуть, не найкращий вибір. Використання подань - чудовий спосіб відстежувати запаси, але затискання на 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's зі зміною загального їх складу. тобто

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

Додав би 10 молотків_1234 та 25 пилок_4321.

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

Згорів би 5 молотків з інвентаря.

За допомогою цієї моделі ви ніколи не оновлюєте будь-які дані, а лише додаєте їх. Це означає, що немає можливості для конфліктів оновлення. Усі питання транзакцій оновлення даних зникають :)

Ще одна приємна річ цієї моделі полягає в тому, що БУДЬ-ЯКИЙ документ у БД може як додавати, так і віднімати предмети з інвентаризації. Ці документи можуть містити всі види інших даних. Можливо, у вас є документ "Відвантаження" із набором даних про дату та час отримання, склад, співробітника, що приймає тощо, і поки цей документ визначає InventoryChange, він буде оновлювати інвентар. Як і документ "Продаж", і документ "Пошкоджений предмет" тощо. Переглядаючи кожен документ, вони читають дуже чітко. І вид справляється з усією важкою роботою.


Цікава стратегія. Як новачок CouchDB здається, що для того, щоб розрахувати поточну кількість молотків, вам потрібно виконати карту / зменшити всю історію змін компанії в інвентарі для молотків. Це може змінитися на багато років. Чи існує якась вбудована функція CouchDB, яка зробить цей виступ?
chadrik

Так, подання в CouchDB схожі на континууми, постійні карти / зменшення. Ви праві, що для того, щоб зробити це з нуля на великому наборі даних, знадобиться вік, але коли додаються нові документи, вони лише оновлюють існуючий вигляд, не потрібно перераховувати весь вигляд. Майте на увазі, що для переглядів потрібні як простір, так і процесор. Крім того, принаймні, коли я професійно працював з CouchDB (пройшло кілька років), було досить важливо використовувати лише вбудовані функції зменшення, тобто. _сума. Спеціальні функції зменшення Javascript були надзвичайно повільними
wallacer

3

Насправді ви можете певним чином. Погляньте на HTTP-документ API і прокрутіть вниз до заголовка "Змінити кілька документів за допомогою одного запиту".

В основному ви можете створювати / оновлювати / видаляти купу документів в одному запиті на публікацію до URI / {dbname} / _ bulk_docs, і вони або досягнуть успіху, або всі не зможуть. Документ застерігає, що ця поведінка може змінитися в майбутньому.

РЕДАКТУВАТИ: Як і передбачалося, з версії 0.9 масові документи більше не працюють таким чином.


Це насправді не допомогло б у ситуації, яка обговорюється, тобто суперечка щодо окремих документів від кількох користувачів.
Керр

3
Починаючи з CouchDB 0.9, семантика масових оновлень змінилася.
Barry Wark

0

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

Таблиця SQLite

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

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.