Kilka miesięcy temu było tak:
- Zauważam, że w dużej mierze korzystamy z SQLa do zadań, do których nadaje się kolejka komunikatów
Baza danych to nie kolejka, kolejka, to nie baza danych. Niby można kosić trawnik nożyczkami do włosów, a włosy strzyc kosiarką, ale nie są to rozwiązania optymalne.
Jeśli RabbitMQ padnie, trzeba zapisywać zadania do odp. tabeli SQL i gdy tylko Rabbit wstanie / zostanie podniesiony, pchnąć je do Rabbita
Nie, trzeba zrobić tak, żeby te kolejki nie padały. Nie wiem jak można to zrobić w RabbitMQ, ale na 100% istnieją kolejki wspierające DR/HA. Dla SQL obsługa tabeli, do której nieustannie ktoś, coś wrzuca, później zmienia, na koniec usuwa ten rekord to mordęga, nieustanne naparzanie po indeksach, rosnący rozmiar bazy, konieczność systematycznego czyszczenia tej tabeli.
Myślę sobie WTF, mamy rabbita po to, by nie robić pseudo-kolejki w SQL, a tu się okazuje że nawet z Rabbitem musi być pseudo-kolejka w SQL, po co ten Rabbit?
Śmiem twierdzić, ze w większości dzisiaj działających systemów wcale nie trzeba SQL, baz relacyjnych używa się bo:
- nasi dziadowie tak robili i ich dziadowie
- nie mamy pojęcia co ma robić ten system w przyszłości, a pomimo wszelkich możliwych wad, SQL ma jednak tę zaletę, że wciśnie się w niego wszystko, wyciągnie co potrzeba. Będzie wolno, ale będzie działać (czasami).
Próbuję odpowiedzieć:
Jeśli Rabbit padnie, to nie można po prostu zwrócić HTTP 500?
Nie można:
Nie po to piszemy nasze rozwiązanie, by zmniejszyć niezawodność całego systemu. Zwracanie 500 dlatego, że zależymy od Rabbita, to strzał w stopę!
Zrobić tak, żeby ten rabbit nie padł. Baza SQL też może paść i pada. Oba rozwiązania da się uodpornić na padanie (DR), ale akurat kolejki łatwiej. Generalnie wystarczy mieć spięty drugi serwer kolejek w innej lokalizacji (mirroring), wrzucasz dane do obu, dodatkowo same między sobą gadają, jeżeli wpada jakaś wiadomość, to ląduje w obu lokalizacjach, następnie serwery próbują ją przesłać do siebie wzajemnie i odrzucają, bo id wiadomości (nadane zewnętrznie) jest identyczne.
Realistycznie, jaka jest szansa, że Rabbit padnie? To jest chyba stabilne, dobrze przetestowane oprogramowanie, a jeśli nawet padnie, to winno być automatycznie podniesione przez środowisko?
Może paść, pada, jak nie rabbit, to maszyna na której stoi, albo atom pierdyknie w datacenter.
Niestety, odpowiedź jest niewzruszona:
Rabbit jest aplikacją. Każda aplikacja może paść. Rabbit może paść.
W tym momencie poddałem się i zrezygnowałem z Rabbita. Z mojej perspektywy nie spełniałby on swojego celu, tj. usunięcia pokracznych, chałupniczych pseudo-kolejek implementowanych czy to jako tabele SQL, czy to za pomocą rozmaitych dirty-bitów dokładanych do różnych pierdółek.
I niesłusznie. Z punktu widzenia niezawodności, wyobraź sobie, że wszystkie żądania zmieniające stan danych wpadają w formie komunikatów do kolejki. Sprawa jest banalna, bo ostatecznie masz wystawiony jakiś endpoint, tłumaczysz go na komunikat i wrzucasz do kolejki. Jeżeli zabezpieczysz (np. przez zwielokrotnienie) ten kawałek oprogramowania, który przyjmuje żądanie z zewnątrz i przerabia je na komunikat i zabezpieczysz przed padnięciem kolejkę, to jesteś pewien, że dane nie zginą (wyłączając niewielkie prawdopodobieństwo, że atom pierdyknie jednocześnie we wszystkie datacenter). Jeżeli padnie ci baza, to za chwilę ktoś ją podniesie, komunikaty zostaną przetworzone. Nastąpi to za godzinę, albo rok, ale po podniesieniu się wszystkich elementów systemu, system sam, bez ingerencji z zewnątrz doprowadzi stan danych do właściwego. Wadą jest to, że nie wiesz kiedy to nastąpi (eventual consistency).
- Czy istotnie zakłada się, że każda zależność może paść
Tak
i w związku z tym by zapewnić działanie całego systemu wymaga się, by ręcznie reimplementować różne zależności, tak by można było tych reimplementacji użyć gdy "preferowana" zależność padnie?
Nie
- Jeśli nie, to co innego się robi w takich sytuacjach?
Redukujesz do 0 liczbę SPOF, tak jak ci pisałem wyżej - można sprawić, że jedyne krytyczne zależności systemu, to jakiś API gateway, mały kawałek tłumaczący requesta na komunikat i serwer kolejek sam w sobie.
Zajmujesz się tym co zostało, w zależności od rodzaju ryzyka, które chcesz zminimalizować w tym przypadku można np.
- Umieścić API gatewaye + te "tłumacze" w 2..n różnych datacenter i na poziomie DNS kierować ruch do losowego
- Wysyłać wiadomości jednocześnie do wszystkich MQ, dodatkowo spiętych w klaster
Jak padnie ci 1 z tych datacenter (albo nawet niech będzie ten Rabbit), to w najgorszym przypadku 1/n żądań skończy się błędem, ale dane nie zostaną utracone.
Do zera w ten sposób nie zejdziesz, bo jeżeli na poziomie jednego datacenter masz 99% niezawodności, to dodanie drugiego da ci wzrost do 99.99% (prawdopodobieństwo, że oba, całkowicie niezależne od siebie elementy walną w tym samym momencie). Czyli na język biznesu - statystycznie system nie będzie działać przez 53 minuty rocznie.
Natomiast twój szef ma trochę racji w tym, ze dodawanie elementów do systemu (mogą walnąć, wiadomo), podnosi zawodność całego systemu. Ale dzieje się tak, kiedy prawidłowe działanie systemu zależy od obu. Czyli jak masz w 99% niezawodne DB i MQ, ale system wywali się zawsze jeżeli dowolne z nich przestanie działać, to prawdopodobieństwo, że system przestanie działać spada ci do 98.01% Czyli cała zabawa w tym, żeby mnożyć prawdopodobieństwa niedziałania, a nie prawdopodobieństwa działania elementów systemu.
Niestety, patrząc na argumenty, jakimi posługuje się twój szef i opisane w innym wątku sposoby radzenia sobie z problemami w kodzie, nie wróżę tak optymistycznego scenariusza.