Bouwen voor grote systemen en langlopende achtergrondtaken.Credit: Ilias Chebbi op Unsplash Maanden geleden nam ik de rol op me die vereiste om infrastBouwen voor grote systemen en langlopende achtergrondtaken.Credit: Ilias Chebbi op Unsplash Maanden geleden nam ik de rol op me die vereiste om infrast

Spotify voor preken bouwen.

2025/12/11 21:15

Bouwen voor grote systemen en langlopende achtergrondtaken.

Credit: Ilias Chebbi op Unsplash

Maanden geleden nam ik de rol op me die vereiste dat ik infrastructuur bouwde voor media(audio) streaming. Maar naast het serveren van audio als streambare stukken, waren er langlopende mediaverwerkingstaken en een uitgebreide RAG-pijplijn die zorgde voor transcriptie, transcodering, embedding en sequentiële media-updates. Het bouwen van een MVP met een productiegerichte mindset zorgde ervoor dat we bleven herhalen totdat we een naadloos systeem bereikten. Onze aanpak was er een waarbij we functies en de onderliggende stapel van prioriteiten integreerden.

Van primair belang:

Tijdens het bouwen kwam elke iteratie als reactie op een onmiddellijke en vaak "allesomvattende" behoefte. De eerste zorg was het in de wachtrij plaatsen van taken, wat gemakkelijk voldoende was met Redis; we vuurden gewoon af en vergaten. Bull MQ in het NEST JS-framework gaf ons nog betere controle over nieuwe pogingen, achterstanden en de dead-letter wachtrij. Lokaal en met enkele payloads in productie, kregen we de mediastroom goed. We werden al snel belast door het gewicht van Observeerbaarheid:
Logs → Registratie van taken (verzoeken, antwoorden, fouten).
Metrics → Hoeveel / hoe vaak deze taken worden uitgevoerd, mislukken, voltooien, etc.
Traces → Het pad dat een taak nam over diensten (functies/methoden die binnen het stroompad worden aangeroepen).

Je kunt sommige van deze oplossen door API's te ontwerpen en een aangepast dashboard te bouwen om ze in te pluggen, maar het probleem van schaalbaarheid zal voldoende zijn. En inderdaad, we hebben de API's ontworpen.

Bouwen voor Observeerbaarheid

De uitdaging van het beheren van complexe, langlopende backend workflows, waar fouten herstelbaar moeten zijn en de staat duurzaam moet zijn, werd Inngest onze architecturale redding. Het herformuleerde fundamenteel onze aanpak: elke langlopende achtergrondtaak wordt een achtergrondfunctie, getriggerd door een specifieke gebeurtenis.

Bijvoorbeeld, een Transcription.request gebeurtenis zal een TranscribeAudio functie triggeren. Deze functie kan stap-uitvoeringen bevatten voor: fetch_audio_metadata, deepgram_transcribe, parse_save_trasncription en notify_user.

De Workflow Ontleden: De Inngest Functie en Stap-uitvoeringen

Het kern duurzaamheidsprimitief zijn de stap-uitvoeringen. Een achtergrondfunctie wordt intern opgebroken in deze stap-uitvoeringen, elk met een minimaal, atomisch blok van logica.

  • Atomische Logica: Een functie voert je bedrijfslogica stap voor stap uit. Als een stap mislukt, wordt de staat van de hele uitvoering bewaard en kan de uitvoering opnieuw worden geprobeerd. Dit herstart de functie vanaf het begin. Individuele stappen of stap-uitvoeringen kunnen niet geïsoleerd opnieuw worden geprobeerd.
  • Respons Serialisatie: Een stap-uitvoering wordt gedefinieerd door zijn respons. Deze respons wordt automatisch geserialiseerd, wat essentieel is voor het behouden van complexe of sterk getypeerde datastructuren over uitvoeringsgrenzen heen. Volgende stap-uitvoeringen kunnen deze geserialiseerde respons betrouwbaar parseren, of logica kan worden samengevoegd in een enkele stap voor efficiëntie.
  • Ontkoppeling en Planning: Binnen een functie kunnen we voorwaardelijk nieuwe, afhankelijke gebeurtenissen in de wachtrij plaatsen of plannen, waardoor complexe fan-out/fan-in patronen en langetermijnplanning tot een jaar mogelijk zijn. Fouten en successen op elk punt kunnen worden opgevangen, vertakt en verder in de workflow worden afgehandeld.

Inngest functie abstract:

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 };
},
);
};

Het event-driven model van Inngest biedt gedetailleerd inzicht in elke workflow-uitvoering:

  • Uitgebreide Gebeurtenis Tracering: Elke in de wachtrij geplaatste functie-uitvoering wordt gelogd tegen zijn oorspronkelijke gebeurtenis. Dit biedt een duidelijk, hoogwaardig spoor van alle activiteiten gerelateerd aan een enkele gebruikersactie.
  • Gedetailleerde Uitvoeringsinzichten: Voor elke functie-uitvoering (zowel successen als mislukkingen) biedt Inngest gedetailleerde logs via zijn ack (acknowledge) en nack (negative acknowledgment) rapportage. Deze logs bevatten foutenstapeltraces, volledige verzoekpayloads en de geserialiseerde responspayloads voor elke individuele stap-uitvoering.
  • Operationele Metrics: Naast logs verkregen we kritieke metrics over functiegezondheid, waaronder succespercentages, mislukkingspercentages en aantal nieuwe pogingen, waardoor we de betrouwbaarheid en latentie van onze gedistribueerde workflows continu kunnen monitoren.

Bouwen voor Veerkracht

Het voorbehoud bij het vertrouwen op pure gebeurtenisverwerking is dat, hoewel Inngest functie-uitvoeringen efficiënt in de wachtrij plaatst, de gebeurtenissen zelf niet intern in de wachtrij worden geplaatst in de traditionele zin van een berichtenbroker. Deze afwezigheid van een expliciete gebeurteniswachtrij kan problematisch zijn in scenario's met veel verkeer vanwege mogelijke race-condities of verloren gebeurtenissen als het innamepunt overbelast raakt.

Om dit aan te pakken en strikte gebeurtenisduurzaamheid af te dwingen, implementeerden we een toegewijd wachtrijsysteem als buffer.

AWS Simple Queue System (SQS) was het systeem van keuze (hoewel elk robuust wachtrijsysteem mogelijk is), gezien onze bestaande infrastructuur op AWS. We ontwierpen een tweewachtrijsysteem: een Hoofdwachtrij en een Dead Letter Queue (DLQ).

We hebben een Elastic Beanstalk (EB) Worker Environment opgezet, specifiek geconfigureerd om berichten direct van de Hoofdwachtrij te consumeren. Als een bericht in de Hoofdwachtrij niet kan worden verwerkt door de EB Worker na een ingesteld aantal pogingen, verplaatst de Hoofdwachtrij automatisch het mislukte bericht naar de toegewijde DLQ. Dit zorgt ervoor dat geen enkele gebeurtenis permanent verloren gaat als deze niet kan worden getriggerd of worden opgepikt door Inngest. Deze werkomgeving verschilt van een standaard EB webserveromgeving, aangezien zijn enige verantwoordelijkheid berichtconsumptie en -verwerking is (in dit geval, het doorsturen van het geconsumeerde bericht naar het Inngest API-eindpunt).

BEGRIJPEN VAN LIMIETEN EN SPECIFICATIES

Een onderschat en nogal relevant deel van het bouwen van infrastructuur op ondernemingsschaal is dat het middelen verbruikt, en ze zijn langlopend. Microservices-architectuur biedt schaalbaarheid per service. Opslag, RAM en timeouts van middelen zullen een rol spelen. Onze specificatie voor AWS-instantietype, bijvoorbeeld, verplaatste zich snel van t3.micro naar t3.small, en is nu vastgezet op t3.medium. Voor langlopende, CPU-intensieve achtergrondtaken faalt horizontale schaling met kleine instanties omdat het knelpunt de tijd is die nodig is om een enkele taak te verwerken, niet het volume van nieuwe taken die de wachtrij binnenkomen.

Taken of functies zoals transcodering, embedding zijn typisch CPU-gebonden en Geheugen-gebonden. CPU-gebonden omdat ze aanhoudend, intensief CPU-gebruik vereisen, en Geheugen-gebonden omdat ze vaak aanzienlijke RAM nodig hebben om grote modellen te laden of grote bestanden of payloads efficiënt te verwerken.

Uiteindelijk bood deze uitgebreide architectuur, waarbij de duurzaamheid van SQS en de gecontroleerde uitvoering van een EB Worker-omgeving direct stroomopwaarts van de Inngest API werden geplaatst, essentiële veerkracht. We bereikten strikte gebeurteniseigendom, elimineerden race-condities tijdens verkeerspieken en verkregen een niet-vluchtig dead letter-mechanisme. We maakten gebruik van Inngest voor zijn workflow-orchestratie en debugging-mogelijkheden, terwijl we vertrouwden op AWS-primitieven voor maximale berichtdoorvoer en duurzaamheid. Het resulterende systeem is niet alleen schaalbaar maar ook zeer controleerbaar, en vertaalt met succes complexe, langlopende backendjobs naar veilige, observeerbare en fouttolerantie microstappen.


Building Spotify for Sermons. werd oorspronkelijk gepubliceerd in Coinmonks op Medium, waar mensen het gesprek voortzetten door te highlighten en te reageren op dit verhaal.

Disclaimer: De artikelen die op deze site worden geplaatst, zijn afkomstig van openbare platforms en worden uitsluitend ter informatie verstrekt. Ze weerspiegelen niet noodzakelijkerwijs de standpunten van MEXC. Alle rechten blijven bij de oorspronkelijke auteurs. Als je van mening bent dat bepaalde inhoud inbreuk maakt op de rechten van derden, neem dan contact op met service@support.mexc.com om de content te laten verwijderen. MEXC geeft geen garanties met betrekking tot de nauwkeurigheid, volledigheid of tijdigheid van de inhoud en is niet aansprakelijk voor eventuele acties die worden ondernomen op basis van de verstrekte informatie. De inhoud vormt geen financieel, juridisch of ander professioneel advies en mag niet worden beschouwd als een aanbeveling of goedkeuring door MEXC.