Budowanie dla dużych systemów i długotrwałych zadań w tle.
Autor: Ilias Chebbi na UnsplashKilka miesięcy temu objąłem rolę wymagającą budowania infrastruktury do strumieniowania mediów (audio). Ale poza dostarczaniem audio jako strumieniowych fragmentów, istniały długotrwałe zadania przetwarzania mediów i rozbudowany potok RAG obsługujący transkrypcję, transkodowanie, osadzanie i sekwencyjne aktualizacje mediów. Budowanie MVP z nastawieniem na produkcję wymagało od nas wielokrotnych iteracji, aż osiągnęliśmy płynnie działający system. Nasze podejście polegało na integracji funkcji i podstawowego stosu priorytetów.
W trakcie budowy każda iteracja była odpowiedzią na natychmiastowe i często "wszechobejmujące" potrzeby. Początkowym problemem było kolejkowanie zadań, co łatwo rozwiązaliśmy za pomocą Redis; po prostu uruchamialiśmy i zapominaliśmy. Bull MQ w frameworku NEST JS dał nam jeszcze lepszą kontrolę nad ponownymi próbami, zaległościami i kolejką martwych listów. Lokalnie i z kilkoma ładunkami na produkcji, uzyskaliśmy prawidłowy przepływ mediów. Wkrótce obciążyła nas waga Obserwowalności:
Logi → Rejestr zadań (żądania, odpowiedzi, błędy).
Metryki → Ile / jak często te zadania działają, zawodzą, kończą się itp.
Śledzenie → Ścieżka, którą zadanie przeszło przez usługi (funkcje/metody wywołane w ścieżce przepływu).
Możesz rozwiązać niektóre z tych problemów projektując API i budując niestandardowy pulpit do ich podłączenia, ale problem skalowalności pozostanie. I faktycznie, zaprojektowaliśmy API.
Wyzwanie zarządzania złożonymi, długotrwałymi przepływami pracy backendu, gdzie awarie muszą być odzyskiwalne, a stan musi być trwały, Inngest stał się naszym architektonicznym zbawieniem. Fundamentalnie przeformułował nasze podejście: każde długotrwałe zadanie w tle staje się funkcją w tle, wyzwalaną przez określone zdarzenie.
Na przykład, zdarzenie Transcription.request wyzwoli funkcję TranscribeAudio. Ta funkcja może zawierać kroki dla: fetch_audio_metadata, deepgram_transcribe, parse_save_trasncription i notify_user.
Podstawowym elementem trwałości są kroki wykonania. Funkcja w tle jest wewnętrznie podzielona na te kroki, z których każdy zawiera minimalny, atomowy blok logiki.
Abstrakt funkcji Inngest:
import { inngest } from 'inngest-client';
export const createMyFunction = (dependencies) => {
return inngest.createFunction(
{
id: 'my-function',
name: 'My Example Function',
retries: 3, // retry the entire run on failure
concurrency: { limit: 5 },
onFailure: async ({ event, error, step }) => {
// handle errors here
await step.run('handle-error', async () => {
console.error('Error processing event:', error);
});
},
},
{ event: 'my/event.triggered' },
async ({ event, step }) => {
const { payload } = event.data;
// Step 1: Define first step
const step1Result = await step.run('step-1', async () => {
// logic for step 1
return `Processed ${payload}`;
});
// Step 2: Define second step
const step2Result = await step.run('step-2', async () => {
// logic for step 2
return step1Result + ' -> step 2';
});
// Step N: Continue as needed
await step.run('final-step', async () => {
// finalization logic
console.log('Finished processing:', step2Result);
});
return { success: true };
},
);
};
Model sterowany zdarzeniami Inngest zapewnia szczegółowy wgląd w każde wykonanie przepływu pracy:
Wadą polegania na czystym przetwarzaniu zdarzeń jest to, że podczas gdy Inngest efektywnie kolejkuje wykonania funkcji, same zdarzenia nie są wewnętrznie kolejkowane w tradycyjnym sensie brokera wiadomości. Ten brak jawnej kolejki zdarzeń może być problematyczny w scenariuszach o dużym natężeniu ruchu ze względu na potencjalne warunki wyścigu lub utracone zdarzenia, jeśli punkt wejścia zostanie przeciążony.
Aby to rozwiązać i wymusić ścisłą trwałość zdarzeń, wdrożyliśmy dedykowany system kolejkowania jako bufor.
AWS Simple Queue System (SQS) był systemem wyboru (choć każdy solidny system kolejkowania jest wykonalny), biorąc pod uwagę naszą istniejącą infrastrukturę na AWS. Zaprojektowaliśmy system dwóch kolejek: Główną Kolejkę i Kolejkę Martwych Listów (DLQ).
Ustanowiliśmy Środowisko Pracownika Elastic Beanstalk (EB) specjalnie skonfigurowane do konsumowania wiadomości bezpośrednio z Głównej Kolejki. Jeśli wiadomość w Głównej Kolejce nie zostanie przetworzona przez Pracownika EB określoną liczbę razy, Główna Kolejka automatycznie przenosi nieudaną wiadomość do dedykowanej DLQ. Zapewnia to, że żadne zdarzenie nie zostanie trwale utracone, jeśli nie uda się go wyzwolić lub zostać pobranym przez Inngest. To środowisko pracownika różni się od standardowego środowiska serwera internetowego EB, ponieważ jego jedyną odpowiedzialnością jest konsumpcja i przetwarzanie wiadomości (w tym przypadku, przekazywanie skonsumowanej wiadomości do punktu końcowego API Inngest).
Niedocenianą i dość istotną częścią budowania infrastruktury na skalę przedsiębiorstwa jest to, że zużywa ona zasoby i są one długotrwałe. Architektura mikroserwisów zapewnia skalowalność dla każdej usługi. Pamięć masowa, RAM i limity czasu zasobów będą odgrywać rolę. Nasza specyfikacja dla typu instancji AWS, na przykład, szybko przeszła z t3.micro do t3.small, a teraz jest ustalona na t3.medium. W przypadku długotrwałych, intensywnych obliczeniowo zadań w tle, skalowanie poziome z małymi instancjami zawodzi, ponieważ wąskim gardłem jest czas potrzebny na przetworzenie pojedynczego zadania, a nie ilość nowych zadań wchodzących do kolejki.
Zadania lub funkcje takie jak transkodowanie, osadzanie są zazwyczaj ograniczone przez CPU i ograniczone przez pamięć. Ograniczone przez CPU, ponieważ wymagają ciągłego, intensywnego wykorzystania CPU, a ograniczone przez pamięć, ponieważ często wymagają znacznej ilości RAM do ładowania dużych modeli lub efektywnej obsługi dużych plików lub ładunków.
Ostatecznie, ta rozszerzona architektura, umieszczająca trwałość SQS i kontrolowane wykonanie środowiska Pracownika EB bezpośrednio przed API Inngest, zapewniła niezbędną odporność. Osiągnęliśmy ścisłą własność zdarzeń, wyeliminowaliśmy warunki wyścigu podczas skoków ruchu i zyskaliśmy nieulotny mechanizm martwych listów. Wykorzystaliśmy Inngest do orkiestracji przepływu pracy i możliwości debugowania, polegając jednocześnie na prymitywach AWS dla maksymalnej przepustowości wiadomości i trwałości. Wynikowy system jest nie tylko skalowalny, ale również wysoce audytowalny, skutecznie przekształcając złożone, długotrwałe zadania backendu w bezpieczne, obserwowalne i odporne na awarie mikro-kroki.
Building Spotify for Sermons. zostało pierwotnie opublikowane w Coinmonks na Medium, gdzie ludzie kontynuują rozmowę, wyróżniając i odpowiadając na tę historię.

