Budowanie dla dużych systemów i długotrwałych zadań w tle.Credit: Ilias Chebbi on Unsplash Miesiące temu, objąłem rolę, która wymagała budowania infrastBudowanie dla dużych systemów i długotrwałych zadań w tle.Credit: Ilias Chebbi on Unsplash Miesiące temu, objąłem rolę, która wymagała budowania infrast

Tworzenie Spotify dla kazań.

2025/12/11 21:15

Budowanie dla dużych systemów i długotrwałych zadań w tle.

Autor: Ilias Chebbi na Unsplash

Kilka 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.

Główne obawy:

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.

Budowanie dla Obserwowalności

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.

Dekonstrukcja Przepływu Pracy: Funkcja Inngest i Kroki Wykonania

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.

  • Logika Atomowa: Funkcja wykonuje logikę biznesową krok po kroku. Jeśli krok nie powiedzie się, stan całego uruchomienia jest zachowany, a uruchomienie może zostać ponowione. To restartuje funkcję od początku. Poszczególne kroki nie mogą być ponawiane w izolacji.
  • Serializacja Odpowiedzi: Krok wykonania jest definiowany przez jego odpowiedź. Ta odpowiedź jest automatycznie serializowana, co jest niezbędne do zachowania złożonych lub silnie typowanych struktur danych przez granice wykonania. Kolejne kroki mogą niezawodnie analizować tę serializowaną odpowiedź, lub logika może być połączona w jeden krok dla efektywności.
  • Rozdzielanie i Planowanie: W ramach funkcji możemy warunkowo kolejkować lub planować nowe, zależne zdarzenia, umożliwiając złożone wzorce fan-out/fan-in i długoterminowe planowanie do roku. Błędy i sukcesy w dowolnym punkcie mogą być przechwytywane, rozgałęziane i obsługiwane dalej w przepływie pracy.

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:

  • Kompleksowe Śledzenie Zdarzeń: Każde wykonanie funkcji w kolejce jest rejestrowane względem jego zdarzenia źródłowego. Zapewnia to jasny, wysokopoziomowy ślad wszystkich działań związanych z pojedynczą akcją użytkownika.
  • Szczegółowe Informacje o Uruchomieniach: Dla każdego wykonania funkcji (zarówno sukcesów, jak i niepowodzeń), Inngest dostarcza szczegółowe logi za pośrednictwem raportowania ack (potwierdzenie) i nack (negatywne potwierdzenie). Te logi zawierają ślady stosu błędów, pełne ładunki żądań i serializowane ładunki odpowiedzi dla każdego pojedynczego kroku wykonania.
  • Metryki Operacyjne: Poza logami, uzyskaliśmy krytyczne metryki dotyczące kondycji funkcji, w tym wskaźniki powodzeń, niepowodzeń i liczby ponownych prób, pozwalające nam na ciągłe monitorowanie niezawodności i opóźnień naszych rozproszonych przepływów pracy.

Budowanie dla Odporności

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).

ZROZUMIENIE LIMITÓW I SPECYFIKACJI

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ę.

Zastrzeżenie: Artykuły udostępnione na tej stronie pochodzą z platform publicznych i służą wyłącznie celom informacyjnym. Niekoniecznie odzwierciedlają poglądy MEXC. Wszystkie prawa pozostają przy pierwotnych autorach. Jeśli uważasz, że jakakolwiek treść narusza prawa stron trzecich, skontaktuj się z service@support.mexc.com w celu jej usunięcia. MEXC nie gwarantuje dokładności, kompletności ani aktualności treści i nie ponosi odpowiedzialności za jakiekolwiek działania podjęte na podstawie dostarczonych informacji. Treść nie stanowi porady finansowej, prawnej ani innej profesjonalnej porady, ani nie powinna być traktowana jako rekomendacja lub poparcie ze strony MEXC.