Jak obejść brak transakcji w MongoDB?

Wiem, że są tu podobne pytania, ale albo mówią mi , aby przełączyć się z powrotem na zwykłe systemy RDBMS, jeśli potrzebuję transakcji lub użyć atomic operations lub dwufazowy commit. Drugie rozwiązanie wydaje się najlepszym wyborem. Trzeciego nie chcę śledzić, ponieważ wydaje się, że wiele rzeczy może pójść nie tak i nie mogę tego przetestować w każdym aspekcie. Mam problem z refaktoryzacją mojego projektu do przeprowadzenia operacji atomowych. Nie wiem czy to pochodzi z mojego Ograniczony punkt widzenia (do tej pory pracowałem tylko z bazami danych SQL), czy faktycznie nie da się tego zrobić.

Chcielibyśmy przetestować MongoDB w naszej firmie. Wybraliśmy stosunkowo prosty projekt-bramkę SMS. Pozwala naszemu oprogramowaniu wysyłać WIADOMOŚCI SMS do sieci komórkowej, a Brama wykonuje brudną robotę: faktycznie komunikuje się z dostawcami za pośrednictwem różnych protokołów komunikacyjnych. Bramka zarządza również rozliczaniem wiadomości. Każdy klient, który ubiega się o serwis musi kupić kredyty. System automatycznie zmniejsza saldo użytkownika po wysłaniu wiadomości i odmawia dostępu, jeśli saldo jest niewystarczające. Również dlatego, że jesteśmy klientami zewnętrznych dostawców wiadomości SMS, możemy również mieć własne salda z nimi. Musimy ich też śledzić.

Zacząłem myśleć o tym, jak Mogę przechowywać wymagane dane w MongoDB, jeśli zmniejszę trochę złożoności(rozliczenia zewnętrzne, wysyłanie SMS w kolejce). Pochodzący ze świata SQL, I utworzy osobną tabelę dla użytkowników, inną dla wiadomości SMS i jedną do przechowywania transakcji dotyczących salda użytkowników. Załóżmy, że tworzę osobne kolekcje dla wszystkich tych w MongoDB.

Wyobraź sobie zadanie wysyłania wiadomości SMS z następującymi krokami w tym uproszczonym systemie:

  1. Sprawdź, czy użytkownik ma wystarczające saldo; Odmowa dostępu, jeśli nie ma wystarczającej ilości kredytów

  2. Wysyłać i przechowywać wiadomość w kolekcji SMS ze szczegółami i koszt (w systemie live wiadomość miałaby atrybut status i zadanie odebrałoby ją do dostarczenia i ustawiłoby cenę SMS zgodnie z jego aktualnym stanem)

  3. Zmniejsz saldo użytkowników o koszt wysłanej wiadomości

  4. Zaloguj transakcję w kolekcji transakcji

W czym problem? MongoDB może dokonywać aktualizacji atomowych tylko w jednym dokumencie. W poprzednim przepływie może się zdarzyć, że jakiś błąd wkrada się i wiadomość zostaje zapisana w bazie danych, ale saldo użytkownika nie jest aktualizowane i / lub transakcja nie jest rejestrowana.

Wpadłem na dwa pomysły:

  • Utwórz pojedynczą kolekcję dla użytkowników i przechowuj saldo jako pole, transakcje związane z użytkownikiem i wiadomości jako dokumenty podrzędne w dokumencie użytkownika. Ponieważ możemy aktualizować dokumenty atomicznie, to faktycznie rozwiązuje problem transakcji. Wady: jeśli użytkownik wysyła wiele wiadomości SMS, rozmiar dokumentu może stać się duży i można osiągnąć limit 4 MB dokumentu. Może w takich scenariuszach mogę tworzyć dokumenty historyczne, ale nie sądzę, żeby to był dobry pomysł. Nie wiem też, jak szybki byłby system, gdybym wypchnął coraz więcej danych do tego samego dużego dokumentu.

  • Utwórz jedną kolekcję dla użytkowników i jedną dla transakcji. Mogą być dwa rodzaje transakcji: zakup kredytu z dodatnią zmianą salda oraz wiadomości wysyłane Z ujemna zmiana salda. Transakcja może mieć subdokument; na przykład w wiadomościach wysłanych szczegóły wiadomości SMS mogą być osadzone w transakcji. Wady: nie przechowuję bieżącego salda użytkownika, więc muszę go obliczyć za każdym razem, gdy użytkownik próbuje wysłać wiadomość, aby powiedzieć, czy wiadomość może przejść, czy nie. Obawiam się, że ta kalkulacja może stać się powolna wraz ze wzrostem liczby przechowywanych transakcji.

Nie wiem, którą metodę wybrać. Czy są inne rozwiązania? Nie mogłem znaleźć żadnych najlepszych praktyk w Internecie na temat tego, jak poradzić sobie z tego rodzaju problemami. Domyślam się, że wielu programistów, którzy próbują zapoznać się ze światem NoSQL, na początku boryka się z podobnymi problemami.
Author: Community, 2011-07-09

10 answers

Check this out, by Tokutek. Opracowują wtyczkę dla Mongo, która obiecuje nie tylko transakcje, ale także zwiększenie wydajności.

 23
Author: Giovanni Bitliner,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-14 18:20:51

Życie Bez Transakcji

Obsługa transakcji właściwości ACID ale mimo, że nie ma transakcji w MongoDB, mamy operacje atomowe. Operacje atomowe oznaczają, że kiedy pracujesz nad jednym dokumentem, praca ta zostanie zakończona, zanim ktokolwiek inny zobaczy ten dokument. Zobaczą wszystkie zmiany, które wprowadziliśmy, albo żadnej z nich. Korzystając z operacji atomowych, często można osiągnąć to samo, co my, korzystając z transakcji w relacyjnym baza danych. Powodem jest to, że w relacyjnej bazie danych musimy wprowadzać zmiany w wielu tabelach. Zazwyczaj tabele, które muszą być połączone, a więc chcemy to zrobić wszystko na raz. Aby to zrobić, ponieważ istnieje wiele tabel, będziemy musieli rozpocząć transakcję i zrobić wszystkie te aktualizacje, a następnie zakończyć transakcję. Ale z MongoDB, będziemy osadzać dane, ponieważ mamy zamiar pre-join to w dokumentach i są te bogate dokumenty, które mają hierarchię. Często możemy / align = "left" / Na przykład w przykładzie na blogu, jeśli chcieliśmy upewnić się, że zaktualizowaliśmy post na blogu atomicznie, możemy to zrobić, ponieważ możemy zaktualizować cały post na blogu jednocześnie. Gdzie jakby to było kilka tabel relacyjnych, prawdopodobnie musielibyśmy otworzyć transakcję, abyśmy mogli zaktualizować kolekcję postów i kolekcji komentarzy.

Więc jakie są nasze podejścia, które możemy przyjąć MongoDB, aby przezwyciężyć brak transakcji?

  • restrukturyzacja - zrestrukturyzować kod tak, abyśmy pracowali w ramach jednego dokumentu i wykorzystywali operacje atomowe, które oferujemy w tym dokumencie. A jeśli to zrobimy, to zazwyczaj jesteśmy gotowi.
  • implementacja w oprogramowaniu - możemy zaimplementować blokowanie w oprogramowaniu, tworząc sekcję krytyczną. Możemy zbudować test, test i ustawić za pomocą find I modify. W razie potrzeby możemy zbudować semafory. I w pewnym sensie tak działa większy świat. Jeśli się nad tym zastanowimy, jeśli jeden bank musi przelać pieniądze do innego banku, nie żyją w tym samym systemie relacyjnym. I każdy z nich często ma własne relacyjne bazy danych. I muszą być w stanie koordynować tę operację, nawet jeśli nie możemy rozpocząć i zakończyć transakcji w tych systemach baz danych, tylko w ramach jednego systemu w jednym banku. Więc na pewno jest sposób w oprogramowaniu, aby obejść problem.
  • tolerować - końcowe podejście, które często działa w nowoczesnych aplikacjach internetowych a inne aplikacje, które biorą w ogromnej ilości danych jest po prostu tolerować trochę niespójności. Przykładem może być to, że jeśli mówimy o kanale znajomych na Facebook ' u, nie ma znaczenia, czy wszyscy widzą aktualizację ściany jednocześnie. Jeśli okey, jeśli jedna osoba jest kilka uderzeń w tyle przez kilka sekund i dogonić. Często w wielu projektach systemowych nie ma krytycznego znaczenia, aby wszystko było idealnie spójne i aby każdy miał idealnie spójny i ten sam widok na baza danych. Więc możemy po prostu tolerować trochę niekonsekwencji, która jest trochę tymczasowa.

Update, findAndModify, $addToSet (w ramach aktualizacji) & $push (w ramach aktualizacji) operacje działają atomowo w ramach jednego dokumentu.

 78
Author: student,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-29 16:01:08

Od wersji 4.0, MongoDB będzie miał WIELODOKUMENTOWE transakcje ACID. Plan polega na tym, aby najpierw umożliwić te w zestawie replik, a następnie sharded clusters. Transakcje w MongoDB będą przypominać transakcje, które programiści znają z relacyjnych baz danych - będą wielotematyczne, z podobną semantyką i składnią(jak start_transaction i commit_transaction). Co ważne, zmiany w MongoDB, które umożliwiają transakcje, nie wpływają na wydajność obciążeń, które nie wymagają oni.

Po Więcej szczegółów zobacz tutaj.

 13
Author: Grigori Melnik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-15 15:00:12

Przejdźmy do rzeczy: jeśli integralność transakcji jest musi, to nie używaj MongoDB, ale używaj tylko komponentów w systemie obsługujących transakcje. Niezwykle trudno jest zbudować coś na komponencie, aby zapewnić podobną do kwasu funkcjonalność Dla komponentów niezgodnych z kwasem. W zależności od indywidualnych przypadków użycia sensowne może być rozdzielenie akcji na akcje transakcyjne i nietransakcyjne...

 11
Author: Andreas Jung,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-10 06:28:50
W czym problem? MongoDB może dokonywać aktualizacji atomowych tylko w jednym dokumencie. W poprzednim przepływie może się zdarzyć, że jakiś błąd wkrada się i wiadomość zostanie zapisana w bazie danych, ale saldo użytkownika nie zostanie zmniejszone i / lub transakcja nie zostanie zalogowana.
To nie jest problem. Wspomniany błąd to błąd logiczny (bug) lub błąd IO (sieć, awaria dysku). Taki rodzaj błędu może pozostawić zarówno transakcję bez sklepy transakcyjne w stanie niespójnym. Na przykład, jeśli już wysłał wiadomość SMS, ale podczas przechowywania wystąpił błąd wiadomości - nie może cofnąć wysyłania wiadomości SMS, co oznacza, że nie będzie zalogowany, saldo użytkownika nie zostanie zmniejszone itp.

Prawdziwym problemem jest to, że użytkownik może wykorzystać stan rasy i wysłać więcej wiadomości, niż pozwala na to jego równowaga. Dotyczy to również RDBMS, chyba że robisz wysyłanie SMS-ów wewnątrz transakcji z blokowaniem pola równowagi (co byłoby wielkim wąskim gardłem). Jako możliwym rozwiązaniem dla MongoDB byłoby użycie findAndModify najpierw do zmniejszenia salda i sprawdzenia go, jeśli jest ujemne, uniemożliwienie wysłania i zwrotu kwoty (przyrost atomowy). Jeśli wynik jest pozytywny, Kontynuuj wysyłkę, a w przypadku niepowodzenia zwróć kwotę. Można również zachować kolekcję historii salda, aby pomóc naprawić / zweryfikować pole salda.

 7
Author: pingw33n,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-10 08:52:45

Projekt jest prosty, ale trzeba obsługiwać transakcje płatności, co utrudnia całość. Tak więc, na przykład, złożony system portalowy z setkami kolekcji (forum, czat, reklamy itp...) jest pod pewnym względem prostsze, bo jeśli stracisz wpis na forum lub czacie, nikogo to naprawdę nie obchodzi. Jeśli z drugiej strony przegrasz transakcję płatniczą, to jest to poważny problem.

Tak więc, jeśli naprawdę chcesz mieć projekt pilotażowy dla MongoDB, wybierz taki, który jest prosty w , że szacunek.

 6
Author: Karoly Horvath,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-09 17:03:43

Transakcje są nieobecne w MongoDB z ważnych powodów. To jedna z tych rzeczy, które sprawiają, że MongoDB jest szybszy.

W Twoim przypadku, jeśli transakcja jest koniecznością, mongo wydaje się nie pasować.

Może być RDMBS + MongoDB, ale to doda złożoności i utrudni zarządzanie i obsługę aplikacji.

 6
Author: kheya,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-10 07:00:54

To chyba najlepszy blog jaki znalazłem dotyczący implementacji funkcji transakcyjnej dla mongodb .!

Flaga synchronizacji: najlepsza do kopiowania danych z dokumentu głównego

Kolejka Zadań: bardzo ogólny cel, rozwiązuje 95% przypadków. Większość systemów i tak musi mieć co najmniej jedną kolejkę zadań!

Commit dwufazowy: ta technika zapewnia, że każda jednostka zawsze ma wszystkie informacje potrzebne do uzyskania spójnego stanu

Logowania: najbardziej solidne technika, idealna dla systemów finansowych

Wersjonowanie: zapewnia izolację i obsługuje złożone struktury

Przeczytaj to, aby uzyskać więcej informacji: https://dzone.com/articles/how-implement-robust-and

 5
Author: Vaibhav,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-27 01:02:33

Jest późno, ale myślę, że to pomoże w przyszłości. Używam Redis do tworzenia kolejki , aby rozwiązać ten problem.

  • Wymagania:
    Poniższy obrazek pokazuje, że 2 akcje muszą być wykonywane jednocześnie, ale faza 2 i faza 3 Akcji 1 muszą zostać zakończone przed rozpoczęciem fazy 2 Akcji 2 lub odwrotnie (faza może być żądaniem REST api, żądaniem bazy danych lub wykonaniem kodu javascript...). Tutaj wpisz opis obrazka

  • Jak pomoc w kolejce ty
    Queue upewnij się, że każdy kod blokowy pomiędzy lock() i release() w wielu funkcjach nie będzie działał w tym samym czasie, spraw, aby był izolowany.

    function action1() {
      phase1();
      queue.lock("action_domain");
      phase2();
      phase3();
      queue.release("action_domain");
    }
    
    function action2() {
      phase1();
      queue.lock("action_domain");
      phase2();
      queue.release("action_domain");
    }
    
  • Jak zbudować kolejkę
    Skupię się tylko na tym, jak uniknąć wyścigu conditon podczas budowania kolejki na stronie zaplecza. Jeśli nie znasz podstawowej idei kolejki, przyjdź tutaj .
    Poniższy kod pokazuje tylko koncepcję, trzeba wdrożyć w poprawny sposób.

    function lock() {
      if(isRunning()) {
        addIsolateCodeToQueue(); //use callback, delegate, function pointer... depend on your language
      } else {
        setStateToRunning();
        pickOneAndExecute();
      }
    }
    
    function release() {
      setStateToRelease();
      pickOneAndExecute();
    }
    

Ale potrzebujesz isRunning() setStateToRelease() setStateToRunning() odizoluj to samo albo znów staniesz w obliczu rasy. W tym celu wybieram Redis dla ACID cel i skalowalność.
Redis dokument mówi o transakcji:

Wszystkie polecenia w transakcji są serializowane i wykonywane kolejno. Nigdy nie może się zdarzyć, że wniosek złożony przez innego klient jest obsługiwany w trakcie realizacji Redis transakcja. Gwarantuje to, że polecenia są wykonywane jako pojedyncza izolowana operacja.

P/S:
Używam Redis, ponieważ mój serwis już go używa, możesz użyć dowolnego innego sposobu izolacji wsparcia, aby to zrobić.
action_domain w moim kodzie jest powyżej, gdy potrzebujesz tylko działania 1 wywołania przez użytkownika a blok działania 2 Użytkownika A, nie blokuj innego użytkownika. Pomysł jest umieścić unikalny klucz do blokady każdego użytkownika.

 4
Author: Đinh Anh Huy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-05 19:19:42

Transakcje są już dostępne w MongoDB 4.0. Próbka Tutaj

// Runs the txnFunc and retries if TransientTransactionError encountered

function runTransactionWithRetry(txnFunc, session) {
    while (true) {
        try {
            txnFunc(session);  // performs transaction
            break;
        } catch (error) {
            // If transient error, retry the whole transaction
            if ( error.hasOwnProperty("errorLabels") && error.errorLabels.includes("TransientTransactionError")  ) {
                print("TransientTransactionError, retrying transaction ...");
                continue;
            } else {
                throw error;
            }
        }
    }
}

// Retries commit if UnknownTransactionCommitResult encountered

function commitWithRetry(session) {
    while (true) {
        try {
            session.commitTransaction(); // Uses write concern set at transaction start.
            print("Transaction committed.");
            break;
        } catch (error) {
            // Can retry commit
            if (error.hasOwnProperty("errorLabels") && error.errorLabels.includes("UnknownTransactionCommitResult") ) {
                print("UnknownTransactionCommitResult, retrying commit operation ...");
                continue;
            } else {
                print("Error during commit ...");
                throw error;
            }
       }
    }
}

// Updates two collections in a transactions

function updateEmployeeInfo(session) {
    employeesCollection = session.getDatabase("hr").employees;
    eventsCollection = session.getDatabase("reporting").events;

    session.startTransaction( { readConcern: { level: "snapshot" }, writeConcern: { w: "majority" } } );

    try{
        employeesCollection.updateOne( { employee: 3 }, { $set: { status: "Inactive" } } );
        eventsCollection.insertOne( { employee: 3, status: { new: "Inactive", old: "Active" } } );
    } catch (error) {
        print("Caught exception during transaction, aborting.");
        session.abortTransaction();
        throw error;
    }

    commitWithRetry(session);
}

// Start a session.
session = db.getMongo().startSession( { mode: "primary" } );

try{
   runTransactionWithRetry(updateEmployeeInfo, session);
} catch (error) {
   // Do something with error
} finally {
   session.endSession();
}
 1
Author: Manish Jain,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-20 17:43:04