Hoppa till innehållet

Datapipeline

en följd av steg som för data från källa till användning

Vad det är

En datapipeline, ofta bara kallad pipeline, är en uppsättning jobb som körs i en fast ordning, där varje jobb tar emot det som jobbet före producerade, så att råa poster från de system som skapar dem hamnar rensade, sammanfogade och lagrade där rapporter, dashboards eller andra program kan läsa dem. Namnet kommer från Unix-skalet, där | matar utdata från ett kommando in i nästa. De flesta pipelines körs utan tillsyn, enligt ett schema eller så fort en ny fil kommer in.

Få pipelines är en rak linje. Ett jobb som läser in dagens försäljning kan behöva både valutakurserna och kundlistan först, medan de två kan köras sida vid sida. Jobben och pilarna för ”måste bli klart först” mellan dem bildar en riktad acyklisk graf: varje pil pekar åt ett håll, och ett jobb kan aldrig hamna i att vänta på sig självt. Apache Airflow, en plattform med öppen källkod för att schemalägga och övervaka sådana arbetsflöden, kallar varje graf för en Dag, skrivet så och inte DAG eftersom betydelsen i Airflow har vuxit förbi den rena matematiska strukturen.

En batchpipeline vaknar enligt ett schema eller vid en händelse, bearbetar en avgränsad del av datan, till exempel ett dygns mätaravläsningar, och stannar. En strömmande pipeline fortsätter att köras och skickar vidare varje händelse när den kommer in. Airflow beskriver sig som en plattform för att orkestrera batcharbetsflöden, bäst lämpad för arbete med en tydlig början och ett tydligt slut som körs enligt ett schema.

Den enklaste pipelinen är en rad i en crontab som startar ett skript klockan två på natten. Det håller tills flera skript måste köras i ordning, ett av dem går sönder och någon måste reda ut vilka som kördes och vad som ska startas om. En orkestrerare tar över den bokföringen: den håller grafen, startar varje jobb när jobben uppströms har lyckats, gör om de jobb som går sönder, sparar en logg per jobb och per körning och noterar vilka perioder av data varje körning täckte. I Airflow täcker varje schemalagd körning ett dataintervall. En daglig körning för den 1 januari startar strax efter midnatt den 2 januari, när dygnet är slut, och dess jobb kan läsa gränser som data_interval_start för att veta exakt vilken del av datan som är deras.

Varför det spelar roll

En pipeline gör en rutin till kod. Ordningen mellan jobben, kontrollerna mellan dem, vad som händer när ett går sönder och hur ofta allt körs flyttar ut ur en enskild persons minne och in i filer som kan granskas, versionshanteras och testas som all annan programvara. Airflow definierar sina arbetsflöden helt i Python, ett angreppssätt som projektet kallar workflows as code, och räknar versionshantering och testning bland fördelarna.

Ett fel får också en adress. När hela processen är ett långt skript betyder en krasch vid minut fyrtio att man börjar om från minut noll. Uppdelad i jobb med sparat tillstånd visar en trasig körning vilket jobb som stannade och varför, och bara det jobbet och de efter det behöver köras igen. Airflow låter en operatör köra om enbart de misslyckade uppgifterna, eller fylla i körningar i efterhand för en tidigare period, och det är så en rättning som görs på onsdagen når måndagens data.

Upprepning blir säker först när varje jobb är skrivet för den. Guiden Best Practices i Airflow-dokumentationen ber att varje uppgift beter sig som en databastransaktion som aldrig lämnar halvfärdiga resultat efter sig, och att den ger samma utfall vid varje omkörning. Ett jobb som är byggt så, ett idempotent jobb, kan göras om av schemaläggaren klockan tre på natten, köras om efter en rättning eller spelas upp igen för förra månaden, utan att någon först behöver granska målet.

Priset är programvara att äga. En orkestrerare är ytterligare en tjänst med egen databas, egna uppgraderingar och egna behörigheter, och varje pipeline i den är kod som behöver granskning och tester. För två skript på samma schema kan cron och en loggfil vara det ärliga svaret. Argumenten för en orkestrerare växer med antalet jobb som beror på varandra, antalet personer som behöver se vad som kördes och kostnaden för en morgonrapport som bygger på en inläsning som stannade halvvägs.

Så ser det ut i praktiken

Tjugo pipelines som måste bete sig likadant, en som måste tåla att köras om och en som måste vara snabb ställer tre olika krav på samma sorts kedja.

På en plattform levererades och övervakades tjugo GIS-datapipelines och ETL-flödet för applikationsdata för hand, med en ständig risk att en av dem skulle stanna i det tysta och lämna fel data längre ned i kedjan innan någon märkte det. Alla fick automatiserad leverans, med schemaläggning, loggning och felhantering standardiserade så att varje pipeline betedde sig likadant och så att det syntes när en gick sönder, och de lades in i den befintliga infrastrukturautomatiseringen och rapporteringen. Alla tjugo kördes sedan automatiskt och förutsägbart, med aktuella data och utan att någon behövde lotsa dem igenom för hand.

En pipeline kan också bygga något komplett med ett enda kommando. En databasdriven generator för CV och portfolio hämtar varje dokument ur en enda SQLite-databas med 23 tabeller, och den databasen byggs upp av en pipeline i 19 steg som går från en tom fil till en komplett databas. Stegen är ordnade och vart och ett är skrivet så att det kan upprepas säkert, så att pipelinen kan köras mot en befintlig databas utan att någon rad dubbleras. Fyrtioen Python-moduler på sammanlagt 10 580 rader renderar sedan sex utdataformat från den enda källan, med bara två beroenden vid körning.

Inne i ett enskilt jobb spelar hastighet lika stor roll som ordning. En pipeline som aggregerar elförbrukning per timme tar JSONL-data som rapporteras varje timme, var 15:e minut eller vid oregelbundna tidpunkter, justerar dem till strikta timintervall, summerar produktionsmixen, lägger till import och drar av export och producerar en rad per timme och zon. Den byggdes på tre sätt, i Python, i JQ och i SQL på PostgreSQL, och varje variant benchmarkades på ett dygns och trettio dygns data. Python-varianten var snabbast, med 34 ms för ett dygn och 180 ms för trettio, och koden skrevs så att den kunde byggas ut till fler zoner eller integreras i en ETL-pipeline.

Var det går fel

En omkörning ger ett annat svar. Ett dagligt jobb lägger till sina summor med INSERT, går sönder halvvägs, och schemaläggaren gör om det, så att raderna som skrevs före avbrottet nu finns två gånger. Ett jobb som väljer allt sedan aktuell tid minus ett dygn läser en annan del vid omförsöket än vid första försöket, och ett jobb som läser den senaste versionen av sin indata får med sig det som hann ändras däremellan. Guiden Best Practices tar upp alla tre: ersätt INSERT med en upsert, läs och skriv en bestämd partition med körningens data_interval_start som nyckel, och håll now() utanför varje kritisk beräkning. För en sammanfattningstabell per timme i PostgreSQL är ett enkelt sätt att ersätta hela intervallet inne i en enda transaktion.

BEGIN;
DELETE FROM hourly_load
WHERE hour_start >= :'start' AND hour_start < :'end';
INSERT INTO hourly_load (zone, hour_start, mwh)
SELECT zone, date_trunc('hour', read_at), sum(mwh)
FROM reading
WHERE read_at >= :'start' AND read_at < :'end'
GROUP BY zone, date_trunc('hour', read_at);
COMMIT;

Körd som psql -v ON_ERROR_STOP=1 -v start=... -v end=... -f hourly_load.sql, med början och slutet av körningens dataintervall inskickade av orkestreraren, raderar jobbet och skriver om exakt det intervallets rader, så att en andra körning lämnar tabellen som den första gjorde. Formen :'start' är psql-syntax för att infoga en variabel citerad som en SQL-literal.

Ett jobb blir grönt och datan är fel. En orkestrerare ser om processen för ett jobb avslutades rent, och det är allt den ser. Verktyget psql är ett tydligt exempel: som standard fortsätter det efter ett fel i ett skript och returnerar 0 till skalet, så ett steg som kör psql -f rapporterar framgång även när en sats i det gick sönder. Med ON_ERROR_STOP satt stannar psql vid det första felet och avslutas med slutkod 3, så att steget fallerar där det ska. En ren avslutning bevisar ändå bara att programmet kördes, och därför föreslår guiden självkontroller: en uppgift längre ned i kedjan som bekräftar att partitionen skrevs och att datan ser rimlig ut innan något läser den. För en körning som hänger sig i stället för att gå sönder utlöser Deadline Alerts, som kom i Airflow 3.1 och ersätter SLA-funktionen som togs bort i 3.0, en callback, till exempel ett mejl, när en körning passerar en tidsgräns.

En pipeline som slås på igen startar en flod av körningar. Med catchup=True skapar schemaläggaren en körning för varje dataintervall som den hoppade över sedan startdatumet, och dokumentationen för Airflow tillägger att catchup också utlöses när en Dag stängs av en tid och sedan aktiveras igen. En daglig pipeline som pausats i en månad kan komma tillbaka med trettio körningar som väntar mot samma källa. Standardvärdet, catchup_by_default=False, gör att schemaläggaren bara skapar en körning för det senaste intervallet, och dokumentationen säger att en Dag vars jobb läser aktuell tid i stället för sitt intervall bör ha catchup avstängt. Historik fylls hellre i med avsikt genom airflow backfill create, som tar ett datumintervall och en gräns för parallella körningar genom --max-active-runs.

Tung kod överst i en Dag-fil gör schemaläggaren långsam. Airflow tolkar varje Dag-fil om och om igen, med minst min_file_process_interval sekunder mellan tolkningarna, så kod på översta nivån i filen, utanför alla uppgifter, körs långt oftare än själva pipelinen. En databasfråga, ett nätverksanrop eller en långsam import där kostar tid vid varje tolkning, och guiden Best Practices visar ett exempel där ett enda sådant anrop gör att en Dag tar ytterligare 1 000 sekunder att tolka. Begränsa översta nivån till att deklarera uppgifter och deras ordning, och flytta dyra importer och anrop in i de uppgiftsfunktioner som behöver dem.

Läs mer

  • Apache Airflow — projektets egen översikt: arbetsflöden som Python-kod, orkestrering av batcher, backfill och omkörning av enbart de misslyckade uppgifterna.
  • Best Practices — uppgifter som behandlas som transaktioner, samma utfall vid varje omkörning, kod på översta nivån och självkontroller.
  • Dags — hur en Dag deklarerar sitt schema, sina uppgifter, beroenden och standardargument som omförsök, och var Deadline Alerts passar in.
  • Dag Runs — dataintervall, catchup, backfill och rensning av uppgifter så att de körs igen.
  • Tasks — beroenden mellan uppgifter, tidsgränser, policyer för omförsök och övergången från SLA till Deadline Alerts.
  • psql — variabler och hur de citeras, ON_ERROR_STOP och de slutkoder som en orkestrerare läser.

Ordlista