Bouwen voor grote systemen en langlopende achtergrondtaken.
Credit: Ilias Chebbi op UnsplashMaanden 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.
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.
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.
Het kern duurzaamheidsprimitief zijn de stap-uitvoeringen. Een achtergrondfunctie wordt intern opgebroken in deze stap-uitvoeringen, elk met een minimaal, atomisch blok van logica.
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:
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).
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.

