Pytanie Czy mogę wykonywać transakcje i blokady w CouchDB?


Potrzebuję wykonać transakcje (rozpocząć, zatwierdzić lub wycofać), zablokować (wybierz dla aktualizacji). Jak mogę to zrobić w db modelu dokumentu?

Edytować:

Sprawa jest następująca:

  • Chcę uruchomić witrynę aukcyjną.
  • I myślę też, jak sfinalizować zakup.
  • Przy bezpośrednim zakupie muszę zmniejszyć pole ilości w rekordzie pozycji, ale tylko wtedy, gdy ilość jest większa od zera. Właśnie dlatego potrzebuję zamków i transakcji.
  • Nie wiem, jak rozwiązać ten problem bez blokad i / lub transakcji.

Czy mogę rozwiązać to z CouchDB?


76
2017-11-18 18:46


pochodzenie




Odpowiedzi:


Nie. CouchDB używa modelu "optymistycznej współbieżności". Najprościej mówiąc, oznacza to, że wysyłasz wersję dokumentu wraz z aktualizacją, a CouchDB odrzuca zmianę, jeśli aktualna wersja dokumentu nie pasuje do tego, co wysłałeś.

To naprawdę pozornie proste. Możesz zmienić wiele standardowych scenariuszy opartych na transakcji dla CouchDB. Jednak podczas nauki CouchDB musisz raczej wyrzucić swoją wiedzę o RDBMS. Przydaje się podejście do problemów z wyższego poziomu, zamiast próbować przekształcić kanapę w świat oparty na SQL.

Śledzenie zapasów

Problem, który opisałeś, to przede wszystkim problem z ekwipunkiem. Jeśli masz dokument opisujący przedmiot i zawiera on pole "dostępna ilość", możesz poradzić sobie z takimi problemami związanymi z współbieżnością:

  1. Pobierz dokument, zapoznaj się z _rev właściwość, którą CouchDB wysyła razem
  2. Zmniejsz pole ilości, jeśli jest większe od zera
  3. Wyślij zaktualizowany dokument z powrotem, używając _rev własność
  4. Jeśli _rev dopasowuje aktualnie zapisany numer, gotowe!
  5. Jeśli wystąpi konflikt (kiedy _rev nie pasuje), pobierz najnowszą wersję dokumentu

W tym przypadku można pomyśleć o dwóch scenariuszach awarii. Jeśli najnowsza wersja dokumentu ma liczbę 0, postępujesz tak, jak robisz to w RDBMS i ostrzegasz użytkownika, że ​​nie może faktycznie kupić tego, co chciał kupić. Jeśli najnowsza wersja dokumentu ma ilość większą niż 0, po prostu powtórz operację ze zaktualizowanymi danymi i od razu rozpocznij od nowa. To zmusza cię do zrobienia nieco więcej pracy niż RDBMS, i może trochę irytować, jeśli są częste, sprzeczne aktualizacje.

Teraz odpowiedź, którą właśnie podałem, zakłada, że ​​będziesz robił rzeczy w CouchDB w taki sam sposób, jak w RDBMS. Mogę podejść do tego problemu nieco inaczej:

Zacznę od dokumentu "produktu wzorcowego", który zawiera wszystkie dane deskryptora (nazwisko, zdjęcie, opis, cenę itp.). Następnie dodałbym dokument "bilet inwentarzowy" dla każdej konkretnej instancji, z polami dla product_key i claimed_by. Jeśli sprzedajesz model młota, a 20 sprzedajesz, możesz mieć dokumenty z takimi kluczami jak hammer-1, hammer-2itp., aby przedstawić każdy dostępny młotek.

Następnie utworzyłbym widok, który dałby mi listę dostępnych młotów, z funkcją zmniejszania, która pozwala mi zobaczyć "całość". Są całkowicie poza mankietem, ale powinny dać ci wyobrażenie o tym, jak będzie wyglądał widok roboczy.

Mapa

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

Daje mi to listę dostępnych "biletów" według klucza produktu. Mogę pobrać grupę tych, gdy ktoś chce kupić młotek, a następnie iterować poprzez wysyłanie aktualizacji (przy użyciu id i _rev), dopóki nie uda mi się go odebrać (poprzednio zgłoszone bilety spowodują błąd aktualizacji).

Zmniejszyć

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

Ta funkcja redukująca po prostu zwraca całkowitą liczbę nieodebranych inventory_ticket przedmioty, dzięki czemu można stwierdzić, ile "młotów" można kupić.

Ostrzeżenia

To rozwiązanie reprezentuje około 3,5 minuty całkowitego myślenia na temat określonego problemu, który przedstawiłeś. Mogą istnieć lepsze sposoby robienia tego! Powiedział, że znacznie zmniejsza sprzeczne aktualizacje i zmniejsza potrzebę reagowania na konflikt z nową aktualizacją. W tym modelu wielu użytkowników nie będzie próbowało zmieniać danych w pozycji produktu podstawowego. W najgorszym wypadku będziesz mieć wielu użytkowników próbujących odebrać jeden bilet, a jeśli złapałeś kilka z nich z widoku, po prostu przejdź do następnego biletu i spróbuj ponownie.

Odniesienie: https://wiki.apache.org/couchdb/Frequently_asked_questions#How_do_I_use_transactions_withCouchDB.3F


133
2017-11-18 18:51



Nie jest dla mnie jasne, czy posiadanie "biletów", które próbujesz zgłosić w sekwencji, jest znaczną poprawą w porównaniu z prostą ponowną próbą odczytu / modyfikacji / zapisu w celu aktualizacji jednostki głównej. Z pewnością nie wydaje się to warte dodatkowych kosztów, szczególnie jeśli masz duże ilości zapasów. - Nick Johnson
Z mojej perspektywy konwencja dotycząca biletów jest "łatwiejsza do zbudowania". Nieudane aktualizacje wpisu głównego wymagają ponownego załadowania dokumentu, wykonania operacji ponownie, a następnie zapisania. Bilet pozwala spróbować i "zażądać" czegoś, nie żądając więcej danych. - MrKurt
Co więcej, zależy to od rodzaju obawy, o którą się martwisz. Będziesz walczyć ze zwiększoną rywalizacją lub będziesz mieć dodatkowe wymagania dotyczące miejsca na dane. Biorąc pod uwagę, że bilet może również podwoić się jako rekord zakupu, nie wiem, że byłby tak duży problem z przechowywaniem, jak myślisz. - MrKurt
Edytuję pole ilości w dokumencie produktu. Potem muszę utworzyć tysiące "biletów", jeśli np. Ilość = 2K. Następnie zmniejszając ilość, muszę usunąć niektóre bilety. Brzmi zupełnie nieopierzony dla mnie. Dużo bólu głowy w podstawowych przypadkach użycia. Być może brakuje mi czegoś, ale dlaczego nie przywrócić wcześniej usuniętego zachowania transakcji, po prostu uczyń opcjonalnym z czymś takim jak _bulk_docs? Reject_on_conflict = true. Bardzo przydatne w konfiguracjach jednomodowych. - Sam
@mehaase: Przeczytaj to: guide.couchdb.org/draft/recipes.html, odpowiedź sprowadza się do wewnętrznej bazy danych couchdb "nigdy nie zmieniasz danych, po prostu dodajesz nowe". W twoim scenariuszu oznacza to utworzenie jednej (atomowej) transakcji z konta na konto tranzytowe dla transakcji debetowej i drugiej (atomowej) z konta w drodze do przodu (lub z powrotem). Tak robią to prawdziwe banki. Każdy krok jest zawsze udokumentowany. - Fabian Zeindl


Rozszerzając odpowiedź MrKurt'a. W przypadku wielu scenariuszy nie trzeba mieć biletów czasowych zrealizowanych w kolejności. Zamiast wybierać pierwszy bilet, możesz wybrać losowo z pozostałych biletów. Biorąc pod uwagę dużą liczbę biletów i dużą liczbę równoczesnych żądań, otrzymasz znacznie mniejszą rywalizację o te bilety, w porównaniu do wszystkich, którzy próbują dostać pierwszy bilet.


24
2017-11-25 10:02





Wzór dla restfull transakcji polega na stworzeniu "napięcia" w systemie. W przypadku popularnego przykładu użycia transakcji na rachunku bankowym, musisz zapewnić aktualizację sumy dla obu zaangażowanych kont:

  • Utwórz dokument transakcji "przenieś 10 USD z konta 11223 na konto 88733". To tworzy napięcie w systemie.
  • Aby rozwiązać problem skanowania napięcia dla wszystkich dokumentów transakcji i
    • Jeśli konto źródłowe nie jest jeszcze zaktualizowane, zaktualizuj konto źródłowe (-10 USD)
    • Jeśli konto źródłowe zostało zaktualizowane, ale dokument transakcji tego nie pokazuje, to zaktualizuj dokument transakcji (np. Ustaw flagę "źródło" w dokumencie)
    • Jeśli konto docelowe nie jest jeszcze zaktualizowane, zaktualizuj konto docelowe (+10 USD)
    • Jeśli konto docelowe zostało zaktualizowane, ale dokument transakcji nie pokazuje tego, a następnie zaktualizuj dokument transakcji
    • Jeśli oba konta zostały zaktualizowane, możesz usunąć dokument transakcji lub zachować go do audytu.

Skanowanie napięcia powinno odbywać się w procesie backendu dla wszystkich "dokumentów naprężeniowych", aby skrócić czasy napięć w systemie. W powyższym przykładzie wystąpi krótkotrwała niespójność, gdy pierwsze konto zostanie zaktualizowane, ale drugie nie jest jeszcze aktualizowane. Należy wziąć to pod uwagę w ten sam sposób, w jaki zajmiesz się ostateczną konsekwencją, jeśli Twoje Couchdb zostanie rozprowadzone.

Inna możliwa implementacja pozwala uniknąć całkowicie transakcji: wystarczy przechowywać dokumenty naprężenia i ocenić stan systemu, oceniając każdy zaangażowany dokument naprężenia. W powyższym przykładzie oznaczałoby to, że suma dla konta jest określana tylko jako suma wartości w dokumentach transakcji, w których uczestniczy to konto. W Couchdb możesz modelować to bardzo ładnie jako mapę / zmniejszyć widok.


19
2017-10-25 03:28



Ale co z przypadkami, w których konto jest obciążane, ale dokumentacja dotycząca napięć nie ulega zmianie? Każdy scenariusz awarii między tymi dwoma punktami, jeśli nie są atomowe, spowoduje stałą niespójność, prawda? Coś w tym procesie musi być atomowe, to jest punkt transakcji. - Ian Varley
Tak, masz rację, w tym przypadku - dopóki napięcie nie zostanie rozwiązane - nie będzie niespójności. Jednak niespójność jest tylko tymczasowa, dopóki nie wykryje tego następny skan dokumentów napięciowych. Taki jest handel w tym przypadku, rodzaj ostatecznej konsekwencji czasu. Tak długo, jak odradzasz konto źródłowe, a potem zwiększasz konto docelowe, może to być akceptowalne. Ale uwaga: dokumenty z naprężeniem nie dają transakcji ACID oprócz REST. Ale mogą być dobrym kompromisem między czystym REST i ACID. - ordnungswidrig
Wyobraź sobie, że każdy dokument naprężenia ma znacznik czasu, a dokumenty kont mają pole "ostatnie napięcie" lub listę napięć użytych. Podczas obciążania konta źródłowego aktualizujesz również pole "ostatnie napięcie". Te dwie operacje są atomowe, ponieważ znajdują się na tym samym dokumencie. Konto docelowe ma również podobne pole. W ten sposób system zawsze może stwierdzić, jakie dokumenty naprężenia zostały zastosowane do danych rachunków. - Jesse Hallett
Jak wykryć, czy dokument źródłowy / docelowy został już zaktualizowany? Co się stanie, jeśli zawiedzie po kroku 1, a następnie zostanie ponownie wykonane i znowu się nie powiedzie, i tak dalej będziesz odliczać konto źródłowe? - wump
@wump: musisz zarejestrować, że dokument naprężenia został zastosowany na koncie. na przykład przez dołączenie identyfikatora dokumentu naprężenia do właściwości listy dowolnego konta. gdy wszystkie konta dotknięte dokumentem napięcia zostały zaktualizowane, zaznacz dokument naprężenia jako "gotowe" lub usuń go. Następnie identyfikator dokumentu można usunąć z listy dla wszystkich kont. - ordnungswidrig


Nie, CouchDB nie jest ogólnie odpowiedni dla aplikacji transakcyjnych, ponieważ nie obsługuje operacji atomowych w środowisku klastrowym / replikowanym.

CouchDB poświęcił możliwości transakcyjne na rzecz skalowalności. Aby móc wykonywać operacje atomowe, potrzebny jest centralny system koordynacji, który ogranicza skalowalność.

Jeśli możesz zagwarantować, że masz tylko jedną instancję CouchDB lub że wszyscy modyfikujący dany dokument łączy się z tą samą instancją CouchDB, możesz użyć systemu wykrywania konfliktów, aby utworzyć rodzaj atomowości za pomocą metod opisanych powyżej, ale jeśli później skalujesz do klastra lub skorzystaj z usługi hostowanej, takiej jak Cloudant, ulegnie ona awarii i będziesz musiał powtórzyć tę część systemu.

Tak więc, moim pomysłem byłoby użycie czegoś innego niż CouchDB dla sald konta, będzie to znacznie łatwiejsze w ten sposób.


5
2017-08-09 16:17





W odpowiedzi na problem PO, kanapa prawdopodobnie nie jest najlepszym wyborem. Używanie widoków to świetny sposób na śledzenie zapasów, ale zablokowanie do 0 jest mniej lub bardziej niemożliwe. Problem jest stanem wyścigowym po przeczytaniu wyniku widoku, możesz zdecydować, że możesz użyć elementu "młotek-1", a następnie napisać dokument, aby go użyć. Problem polega na tym, że nie istnieje żaden atomowy sposób, aby napisać dokument tylko po to, aby użyć młotka, jeśli wynik jest taki, że> 0 młotków-1. Jeśli 100 użytkowników wszystkich zapyta o widok w tym samym czasie i zobaczy 1 młot-1, wszyscy mogą napisać dokument, aby użyć młotka 1, co daje -99 młotków-1. W praktyce stan wyścigu będzie dość mały - naprawdę mały, jeśli twój DB uruchamia localhost. Ale gdy skalujesz i masz serwer lub klaster bazy danych poza miejscem pracy, problem stanie się znacznie bardziej zauważalny. Bez względu na to, nie można zaakceptować tego rodzaju wyścigu w systemie o krytycznym pieniądzu.

Aktualizacja odpowiedzi MrKurt'a (może być po prostu datowana, lub może nie był świadomy niektórych funkcji CouchDB)

Widok jest dobrym sposobem na obsługę rzeczy takich jak salda / zapasy w CouchDB.

Nie musisz emitować dokumentu doc ​​i rev w widoku. Dostajesz obie te rzeczy za darmo, gdy odzyskujesz wyniki widoku. Emitowanie ich - zwłaszcza w pełnym formacie, jak słownik - po prostu powiększy twój niepotrzebnie duży widok.

Prosty widok do śledzenia stanów magazynowych powinien wyglądać bardziej podobnie (również z mojej góry)

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

Funkcja redukcji jest jeszcze prostsza

_sum

To używa a wbudowany w funkcję zmniejszania to po prostu sumuje wartości wszystkich wierszy z pasującymi kluczami.

W tym widoku każdy dokument może mieć element "InventoryChange", który odwzorowuje product_key na zmianę w ich całkowitym inwentarzu. to znaczy.

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

Dodałbym 10 młotów_1234 i 25 piłek_4321.

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

Spaliłby 5 młotów z ekwipunku.

W tym modelu nigdy nie aktualizujesz żadnych danych, tylko dołączasz. Oznacza to, że nie ma możliwości wystąpienia konfliktów aktualizacji. Wszystkie transakcyjne problemy aktualizacji danych znikną :)

Kolejną dobrą rzeczą w tym modelu jest to, że każdy dokument w DB może zarówno dodawać, jak i odejmować elementy z ekwipunku. Te dokumenty mogą zawierać w sobie wszystkie inne dane. Możesz mieć dokument "Przesyłka" zawierający kilka danych na temat daty i godziny odebrania, magazynu, przyjmującego pracownika itp. I dopóki ten dokument definiuje InventoryChange, zaktualizuje inwentarz. Podobnie jak dokument "Sprzedaż" i dokument "DamagedItem" itp. Przeglądając każdy dokument, czytali bardzo wyraźnie. A widok zajmuje się całą ciężką pracą.


5
2018-02-20 08:09



Interesująca strategia. Jako newb CouchDB wydaje się, że aby obliczyć aktualną liczbę młotów, musisz wykonać mapę / zmniejszyć w stosunku do firmy cała historia zmian w zapasach dla młotów. Może to oznaczać lata zmian. Czy jest jakiś wbudowany składnik CouchDB, który sprawi, że będzie to wydajne? - chadrik
Tak, widoki w CouchDB są jak ciągła, trwała mapa / redukcja. Masz rację, że zrobienie tego od podstaw na dużym zestawie danych zajęłoby całe wieki, ale po dodaniu nowych dokumentów, tylko aktualizują istniejący widok, nie musi on ponownie obliczać całego widoku. Pamiętaj, że zarówno widok przestrzeni, jak i procesora jest wymagany. Ponadto, przynajmniej podczas profesjonalnej pracy z CouchDB (minęło kilka lat), było bardzo ważne, aby używać wyłącznie wbudowanych funkcji zmniejszania, tj. _suma. Niestandardowe funkcje redukcji JavaScript były bardzo powolne - wallacer


Właściwie możesz w pewien sposób. Spójrz na API dokumentów HTTP i przewiń w dół do nagłówka "Modyfikuj wiele dokumentów za pomocą pojedynczego żądania".

Zasadniczo można utworzyć / zaktualizować / usunąć kilka dokumentów w jednym żądaniu pocztowym do URI / {dbname} / _ bulk_docs i albo wszystko się powiedzie, albo wszystko zawiedzie. Dokument ostrzega, że ​​to zachowanie może się zmienić w przyszłości.

EDYCJA: Zgodnie z przewidywaniami, od wersji 0.9 masowe dokumenty przestały działać w ten sposób.


3
2018-06-21 04:10



Nie pomogłoby to w przypadku omawianej sytuacji, tj. Sporów dotyczących pojedynczych dokumentów od wielu użytkowników. - Kerr
Począwszy od CouchDB 0.9 zmieniła się semantyka aktualizacji zbiorczych. - Barry Wark


Po prostu używaj lekkich rozwiązań SQlite do transakcji, a gdy transakcja zostanie zakończona powodzeniem ją zreplikuj i oznacz ją jako zreplikowaną w SQLite

Tabela SQLite

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

Można również usunąć transakcje, które zostały pomyślnie zreplikowane.


0