Construirea pentru sisteme mari și joburi de fundal de lungă durată.
Credit: Ilias Chebbi pe UnsplashCu câteva luni în urmă, am preluat rolul care necesita construirea infrastructurii pentru streaming media (audio). Dar dincolo de servirea audio ca fragmente streamabile, existau joburi de procesare media de lungă durată și un pipeline RAG extins care se ocupa de transcriere, transcodare, încorporare și actualizări media secvențiale. Construirea unui MVP cu o mentalitate de producție ne-a făcut să reiterăm până am obținut un sistem fără probleme. Abordarea noastră a fost una în care am integrat funcționalități și stiva subiacentă de priorități.
Pe parcursul construirii, fiecare iterație a venit ca răspuns la o nevoie imediată și adesea "cuprinzătoare". Preocuparea inițială a fost punerea în coadă a joburilor, care a fost suficientă cu Redis; am lansat pur și simplu și am uitat. Bull MQ în framework-ul NEST JS ne-a oferit un control și mai bun asupra reîncercărilor, backlog-urilor și cozii de scrisori moarte. Local și cu câteva încărcături în producție, am obținut fluxul media corect. Curând am fost împovărați de greutatea Observabilității:
Loguri → Înregistrarea joburilor (cereri, răspunsuri, erori).
Metrici → Cât de mult / cât de des aceste joburi rulează, eșuează, se finalizează etc.
Trasee → Calea pe care un job a luat-o prin servicii (funcții/metode apelate în calea fluxului).
Poți rezolva unele dintre acestea prin proiectarea API-urilor și construirea unui dashboard personalizat pentru a le conecta, dar problema scalabilității va fi suficientă. Și, de fapt, am proiectat API-urile.
Provocarea de a gestiona fluxuri de lucru backend complexe, de lungă durată, unde eșecurile trebuie să fie recuperabile, iar starea trebuie să fie durabilă, Inngest a devenit salvarea noastră arhitecturală. A reformat fundamental abordarea noastră: fiecare job de fundal de lungă durată devine o funcție de fundal, declanșată de un eveniment specific.
De exemplu, un eveniment Transcription.request va declanșa o funcție TranscribeAudio. Această funcție ar putea conține rulări de pași pentru: fetch_audio_metadata, deepgram_transcribe, parse_save_trasncription și notify_user.
Primitivul de durabilitate de bază sunt rulările de pași. O funcție de fundal este descompusă intern în aceste rulări de pași, fiecare conținând un bloc minimal, atomic de logică.
Abstractul funcției 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 };
},
);
};
Modelul bazat pe evenimente al Inngest oferă o perspectivă granulară asupra fiecărei execuții a fluxului de lucru:
Avertismentul pentru a te baza pe procesarea pură a evenimentelor este că, în timp ce Inngest pune eficient în coadă execuțiile funcțiilor, evenimentele în sine nu sunt puse în coadă intern în sensul tradițional al unui broker de mesaje. Această absență a unei cozi explicite de evenimente poate fi problematică în scenarii cu trafic ridicat din cauza potențialelor condiții de cursă sau a evenimentelor pierdute dacă punctul final de ingestie este copleșit.
Pentru a aborda acest lucru și a impune durabilitatea strictă a evenimentelor, am implementat un sistem dedicat de cozi ca tampon.
AWS Simple Queue System (SQS) a fost sistemul ales (deși orice sistem robust de cozi este realizabil), având în vedere infrastructura noastră existentă pe AWS. Am proiectat un sistem cu două cozi: o Coadă Principală și o Coadă de Scrisori Moarte (DLQ).
Am stabilit un Mediu de Lucru Elastic Beanstalk (EB) configurat special pentru a consuma mesaje direct din Coada Principală. Dacă un mesaj din Coada Principală nu poate fi procesat de Lucrătorul EB de un număr stabilit de ori, Coada Principală mută automat mesajul eșuat în DLQ dedicată. Acest lucru asigură că niciun eveniment nu este pierdut permanent dacă nu reușește să declanșeze sau să fie preluat de Inngest. Acest mediu de lucru diferă de un mediu standard de server web EB, deoarece responsabilitatea sa unică este consumul și procesarea mesajelor (în acest caz, redirecționarea mesajului consumat către punctul final API Inngest).
O parte subestimată și destul de pertinentă a construirii infrastructurii la scară de întreprindere este că aceasta consumă resurse, și acestea sunt de lungă durată. Arhitectura microserviciilor oferă scalabilitate per serviciu. Stocarea, RAM-ul și timeout-urile resurselor vor intra în joc. Specificația noastră pentru tipul de instanță AWS, de exemplu, s-a mutat rapid de la t3.micro la t3.small și acum este fixată la t3.medium. Pentru joburile de fundal de lungă durată, intensive în CPU, scalarea orizontală cu instanțe mici eșuează deoarece blocajul este timpul necesar pentru a procesa un singur job, nu volumul de joburi noi care intră în coadă.
Joburile sau funcțiile precum transcodarea, încorporarea sunt de obicei limitate de CPU și limitate de memorie. Limitate de CPU deoarece necesită o utilizare susținută, intensă a CPU-ului, și limitate de memorie deoarece adesea necesită RAM substanțial pentru a încărca modele mari sau pentru a gestiona eficient fișiere sau încărcături mari.
În cele din urmă, această arhitectură augmentată, plasând durabilitatea SQS și execuția controlată a unui mediu de lucru EB direct în amonte de API-ul Inngest, a oferit reziliență esențială. Am obținut proprietatea strictă a evenimentelor, am eliminat condițiile de cursă în timpul vârfurilor de trafic și am câștigat un mecanism de scrisori moarte non-volatil. Am folosit Inngest pentru capacitățile sale de orchestrare a fluxului de lucru și de depanare, bazându-ne în același timp pe primitivele AWS pentru un debit maxim de mesaje și durabilitate. Sistemul rezultat nu este doar scalabil, ci și foarte auditabil, traducând cu succes joburile de fundal complexe, de lungă durată în micro-pași siguri, observabili și toleranți la eșec.
Building Spotify for Sermons a fost publicat inițial în Coinmonks pe Medium, unde oamenii continuă conversația prin evidențierea și răspunsul la această poveste.

