Dlaczego nie lubię rozproszonych transakcji
Jakiś czas temu po prostu powiedziałbym, że są niewydajne. Na szczęście, zgodnie z ideą challenge everything przeprowadziłem kilka testów, które przekonały mnie, że problem wydajnościowy wcale nie jest taki straszny. Na pewno nie na tyle, aby dyskwalifikować takie rozwiązanie. Co więc je dyskwalifikuje (w moich oczach)? Oto lista problemów:
- zwiększona podatność na awarię
- skomplikowana administracja infrastrukturą
- problemy związane z zaufaniem i polityką
Mam oczywiście na myśli rozwiązanie, w którym jedna transakcja rozproszona współdzielona jest przez wiele niezależnych usług/systemów/aplikacji. Wykorzystanie transakcji rozproszonych w ramach jednej aplikacji do rozwiązania kwestii technicznych (np. integracja transakcyjna bazy danych i MSMQ) jest dla mnie całkowicie OK.
Historia pewnej transakcji
Aby namierzyć problemy, prześledźmy pewien nie-całkiem-nierealny przypadek użycia: klienta łączącego się z własną bazą danych i z zewnętrzną transakcyjną usługą. Oto, co się dzieje:
- Klient rozpoczyna transakcję w swoim kodzie
using (TransactionScope tx = new TransactionScope())
- Klient wykonuje operację na bazie danych. W przypadku bazy SQL Server po tym kroku transakcja jest wciąż lokalna (brak zaangażowania Distributed Transaction Coordinator, DTC). W przypadku innych silników może być inaczej
- Klient łączy się z usługą zdalną “A” w sposób transakcyjny (wykorzystując mechanizmy WCF). W tym momencie transakcja jest promowana i staje się transakcją rozproszoną — otrzymuje odpowiedni identyfikator.
W tym momencie pojawia się problem natury administracyjnej. Aby usługi DTC na różnych maszynach, być może w różnych firmach, na różnych kontynentach, mogły się porozumieć, niezbędne jest stworzenie odpowiednich kanałów (“wybicie dziur” w firewallach). Tego typu działania są ściśle związane z polityką bezpieczeństwa każdej świadomej organizacji i dlatego zwykle wymagają uruchomienia skomplikowanego i długotrwałego procesu. Każda zmiana w logicznej topologii wywoływanych usług, jakkolwiek prosta w kodzie, może się wiązać z koniecznością uruchomienia trwających tygodniami procesów w dziale IT. Ile nic to, idźmy dalej.
- Usługa “A” do wykonania swojego biznesu musi skorzystać z pewnej zewnętrznej funkcji udostępnianej przez usługę “B”. Oczywiście, dla utrudnienia, usługa “B” także jest transakcyjna.
Nagle i niespodziewanie pojawia się nam zależność od usługi “B”. Teoretycznie klient nie musiałby wiedzieć nic o istnieniu “B”. Teoria jednak ma się nijak do praktyki. Praktycznie bowiem, powodzenie transakcji klienta jest zależne o działania usługi “B”. Co jeśli jest ona prowadzona przez firmę konkurencyjną dla naszego klienta? Wcale nie musi jej zależeć na najwyższym poziomie obsługi. Niektóre (te najbardziej lukratywne?) transakcje mogą być umyślnie sabotowane przez zrywanie połączenia. Polityka, polityka, wszędzie polityka. Załóżmy jednak, że tym razem kwestie polityczne zostały rozwiązane pokojowo. Co dalej?
- Usługa “B” wykonuje swoją część pracy i zwraca sterowanie do “A”.
- Usługa “A” kończy pracę i zwraca sterowanie do klienta.
- Klient inicjuje proces zatwierdzania transakcji.
Proces zatwierdzania składa się zwykle z dwóch faz (2-phase commit). Jest on ukoronowaniem trwającej nierzadko kilka lub nawet kilkanaście sekund interakcji mogącej dotyczyć komputerów na wielu kontynentach. Bardzo wiele rzeczy morze pójść “nie tak”. Połączenia mogą zostać zerwane, pakiety zgubione itp.
Każdy, praktycznie, problem powoduje zerwanie misternie utkanej sieci powiązań pomiędzy usługami i koordynatorami transakcji, skutkiem czego cała transakcja zostaje wycofana.
Epilog
Przekonałem Was, że transakcje rozproszone to ryzykowny biznes? Mam nadzieję, że tak. W następnym odcinku postaram się opowiedzieć o technikach, które można stosować, aby ich uniknąć.






about 5 months ago
Czekamy niecierpliwie.
about 5 months ago
eeee…. no tego to przecież w liceum już uczyli; myślałem, że będzie więcej szczegółów, a zatem czekam na kolejny post
about 5 months ago
Przepraszam, że trochę nie na temat, ale czy TransactionScope można też używać do zwykłych transakcji na lokalnej bazie danych? Czy występuje w tym względzie jakaś różnica w wydajności, sposobie obsługi w porównaniu z metodą BeginTransaction obiektu typu IDbConnection?
about 5 months ago
@Michał
Tak, można. Nie znam dokładnych statystyk wydajnościowych, ale wiem, że mechanizm działania TransactionScope w przypadku transakcji lokalnych jest identyczny z transakcjami System.Data: transakcja rozpoczyna się jako transakcja lokalna i jest promowana do rozproszonej dopiero w razie konieczności (czyli próby dołączenia innego transakcyjnego zasobu)