Read in English · Leer en español · Lire en français
Abschnitt 5.4 von 5.8 · Mittelstufe · 23 Min. Lesezeit
Pipes and Filters und Pipeline-Architekturen
Videolektion
5 Min. · auf Englisch gesprochen, mit UntertitelnNur anhören (Audio)
Transkript lesen
Manche Probleme sind von Natur aus ein Fluss: Daten kommen herein, durchlaufen eine Reihe von Schritten und gehen hinaus. In dieser Lektion lernen Sie den Stil kennen, der dafür gebaut ist, was seine Geschwindigkeit begrenzt und wo er schwach ist.
Denken Sie an eine Autowaschanlage. Jedes Auto durchläuft eine Reihe von Stationen: Einweichen, Bürsten, Spülen, Trocknen. Jede Station erledigt eine Aufgabe, weiß nichts von den anderen und gibt das Auto über ein Förderband an die nächste weiter. Sie können eine Wachsstation ergänzen, ohne die anderen zu ändern. Und wenn das Trocknen langsam ist, stauen sich die Autos davor. Pipes and Filters funktioniert für Daten genauso: Die Stationen sind die Filter, und das Förderband ist die Pipe.
Hier ist eine Pipeline aus der Essenslieferplattform. Ein Producer, auch Quelle genannt, startet den Fluss: Er liest Bestell-Events. Ein Transformer verändert jeden Datensatz. Dieser hier ergänzt Restaurant und Region. Ein Tester prüft eine Bedingung und gibt jeden Datensatz weiter oder verwirft ihn. Hier verwirft er Testbestellungen. Und ein Consumer, auch Senke genannt, beendet den Fluss, indem er ins Data Warehouse schreibt. Zwischen den Filtern puffern Pipes wie diese die Datensätze und transportieren sie in eine Richtung. Die zentralen Einschränkungen: Filter teilen keinen Zustand, und jeder kennt nur das Format dessen, was er liest und schreibt, nie seine Nachbarn.
Diese Einschränkungen erzeugen die Vorteile unmittelbar. Weil ein Filter keinen Zustand teilt, lässt er sich für sich schreiben und testen, mit einer Liste von Eingabedatensätzen und den erwarteten Ausgaben. Er lässt sich in anderen Pipelines wiederverwenden, und Filter lassen sich hinzufügen, entfernen oder umordnen, indem man nur ändert, wie die Pipeline zusammengesetzt ist. Und weil Datensätze einzeln fließen, kann ein Filter mit dem ersten beginnen, bevor der vorherige den letzten fertig hat. Die Stufen laufen nebenläufig und brauchen Speicher für nur wenige Datensätze.
Die bekannteste Heimat des Stils ist Unix. 1964 schrieb Doug McIlroy, Programme sollten wie ein Gartenschlauch gekoppelt werden. Ken Thompson fügte Unix 1973 Pipes hinzu, und die Idee kleiner, durch Pipes verbundener Werkzeuge machte den Stil berühmt. Heute ist er die Standardwahl im Data Engineering. ETL-Jobs bringen die Bestellungen von gestern Schritt für Schritt in ein Data Warehouse. Auch ein klassischer Compiler ist eine Pipeline: Aus Zeichen werden Tokens, aus Tokens ein Syntaxbaum, dann folgen Prüfungen, Optimierung und Maschinencode. Medien- und Log-Verarbeitung funktionieren genauso. Und eine Struktur kann als Batch über eine begrenzte Eingabe laufen oder als Stream, der nie endet.
Jetzt ein ausgearbeitetes Beispiel. Die Plattform lädt Bestell-Events über vier Filter in ihr Data Warehouse. Auf einem Worker schafft Parse fünfzigtausend Datensätze pro Sekunde, Enrich achttausend, Validate vierzigtausend und Load zwölftausend. Eine Pipeline kann nicht schneller sein als ihr langsamster Filter, also läuft diese mit achttausend Datensätzen pro Sekunde. An einem geschäftigen Tag gibt es eine Million Bestell-Events. Das dauert hundertfünfundzwanzig Sekunden. Enrich hat keinen Zustand, also können Sie zwei Kopien parallel betreiben, die jeweils die Hälfte der Datensätze übernehmen. Diese Stufe schafft nun sechzehntausend pro Sekunde, und Load wird mit zwölftausend zum Engpass. Die Daten eines Tages brauchen nun etwa dreiundachtzig Sekunden: dreiunddreißig Prozent Ersparnis, nicht die fünfzig Prozent, die eine naive Lesart nahelegt. Alles außer dem Engpass zu beschleunigen ändert nichts, also messen Sie zuerst.
Tempounterschiede verursachen ein zweites Problem. Ohne Steuerung würde Parse fünfzigtausend Datensätze pro Sekunde zu einer Enrich-Stufe schieben, die achttausend abarbeitet. Der Puffer würde jede Sekunde um zweiundvierzigtausend Datensätze wachsen, bis der Speicher erschöpft ist. Die Antwort ist Backpressure: die schnelle Stufe auf das Tempo der langsamen bremsen. Eine Unix-Pipe tut das automatisch, weil eine volle Pipe den Schreiber blockiert. Fehler brauchen für jeden Filter eine Strategie. Ein fehlerhafter Datensatz sollte die Pipeline nicht anhalten: Schicken Sie ihn mit dem Grund an eine Dead Letter Queue. Und weil Neustarts bedeuten, dass manche Datensätze doppelt verarbeitet werden, sollten Senken idempotent sein.
Setzen Sie Pipes and Filters ein, wenn das Problem von Natur aus ein Datenfluss in eine Richtung ist: Aufnahme, Umwandlung, Anreicherung, Validierung und Export. Es kommt auch innerhalb anderer Stile vor, etwa als nächtlicher Reporting-Job in einem modularen Monolithen. Vermeiden Sie es als Hauptstruktur interaktiver Anwendungen, bei denen Nutzer sofortige Antworten erwarten, und für Logik, bei der viele Schritte denselben Zustand teilen und ändern. Und weil Filter bewusst nichts voneinander wissen, kann ein in Schritt vier gefundener Fehler Schritt zwei nicht ohne Weiteres rückgängig machen. Wenn Sie ein koordiniertes Rollback brauchen, brauchen Sie wahrscheinlich einen anderen Stil.
Der vollständige Abschnitt vergleicht Batch und Streaming, zeigt eine Pipeline in Unix und in Python und bewertet die Merkmale des Stils. Als Nächstes, in Abschnitt fünf Punkt fünf, sehen wir uns Microkernel- und Plug-in-Architekturen an. Bis gleich.
Was Sie wissen müssen
- Pipes and Filters ordnet die Verarbeitung als Kette unabhängiger Filter an, die durch Einweg-Pipes verbunden sind; Filter teilen keinen Zustand und kennen ihre Nachbarn nicht, nur das Datenformat, das sie lesen und schreiben.
- Es gibt vier Arten von Filtern: Producer (Quellen), Transformer, Tester (die Datensätze durchlassen oder verwerfen) und Consumer (Senken).
- Doug McIlroy schlug 1964 in einem Memo vor, Programme wie Gartenschläuche zu koppeln; Ken Thompson fügte Unix 1973 Pipes hinzu, und die Unix-Philosophie kleiner, durch Pipes verbundener Werkzeuge machte den Stil berühmt.
- Typische Einsatzgebiete sind ETL- und Datenpipelines, Compilerphasen, Medienverarbeitung und Log-Verarbeitung; dieselbe Struktur läuft als Batch (begrenzte Eingabe) oder als Stream (unbegrenzte Eingabe).
- Der Durchsatz einer Pipeline wird von ihrem langsamsten Filter bestimmt, und ein schneller Producer überrollt einen langsamen Consumer, sofern die Puffer nicht begrenzt sind und Backpressure den Producer bremst.
- Der Stil schneidet bei Einfachheit, Modularität und Wiederverwendung von Filtern gut ab, bei Interaktivität und beim Umgang mit gemeinsamem Zustand oder komplexer Fehlerbehebung über mehrere Schritte hinweg schlecht.
Definition und Ursprung
Im Stil Pipes and Filters ist die Verarbeitung eine Kette unabhängiger Schritte. Jeder Filter liest Daten von seiner Eingabe, erledigt eine Aufgabe damit und schreibt das Ergebnis in seine Ausgabe. Jede Pipe transportiert Daten in eine Richtung von einem Filter zum nächsten, meist mit einem Puffer dazwischen. Die Beschreibung von Garlan und Shaw (1994) nennt die Einschränkungen, die dem Stil seine Eigenschaften geben: Filter müssen unabhängig sein, dürfen keinen Zustand mit anderen Filtern teilen und dürfen die Identität der Filter an beiden Enden ihrer Pipes nicht kennen. Ein Filter kennt nur das Format dessen, was er liest und was er schreibt.
Die bekannteste Heimat des Stils ist Unix. In einem Memo vom 11. Oktober 1964 schrieb Doug McIlroy, es solle Möglichkeiten geben, Programme wie einen Gartenschlauch zu koppeln und ein weiteres Segment anzuschrauben, wenn die Daten auf andere Weise bearbeitet werden müssen. Ken Thompson implementierte 1973 in Version 3 Unix den Systemaufruf pipe und Pipes in der Shell, und die Notation | folgte in Version 4. Die daraus entstandene Unix-Philosophie, Programme zu schreiben, die eine Sache gut machen und über eine universelle Textschnittstelle zusammenarbeiten, ist der Stil Pipes and Filters, angewandt auf den Werkzeugsatz eines ganzen Betriebssystems. POSA (1996) katalogisierte Pipes and Filters als Architekturmuster, und Hohpe und Woolfs Enterprise Integration Patterns (2003) wandten es auf das Messaging zwischen Systemen an.
Richards und Ford nennen denselben Stil Pipeline-Architektur und unterscheiden vier Arten von Filtern. Ein Producer (oder Quelle) startet den Fluss und liest aus einer Datei, einer Datenbank oder einer Queue. Ein Transformer verändert jeden Datensatz: parsen, umwandeln, anreichern. Ein Tester prüft eine Bedingung und gibt den Datensatz weiter oder verwirft ihn, manchmal leitet er ihn an eine von mehreren Ausgaben. Ein Consumer (oder Senke) beendet den Fluss und schreibt in eine Datenbank, eine Datei oder auf einen Bildschirm.
Filter und Pipes
So funktioniert es
Die Einschränkungen erzeugen die Vorteile unmittelbar. Weil ein Filter keinen Zustand teilt und seine Nachbarn nicht kennt, lässt er sich für sich schreiben und testen, mit einer Liste von Eingabedatensätzen und einer erwarteten Liste von Ausgaben; er lässt sich in anderen Pipelines wiederverwenden; und Filter lassen sich hinzufügen, entfernen oder umordnen, indem man nur ändert, wie die Pipeline zusammengesetzt ist. Weil Datensätze inkrementell fließen, kann ein Filter mit dem ersten Datensatz beginnen, bevor der vorherige Filter den letzten fertig hat; so laufen die Stufen nebenläufig, und die ganze Pipeline braucht Speicher für nur wenige Datensätze auf einmal, nicht für den gesamten Datenbestand.
Die Pipe verbirgt, wie Daten sich bewegen. In einer Unix-Shell ist sie ein Puffer des Betriebssystems zwischen zwei Prozessen; unter Linux fasst er standardmäßig 64 KiB, und ein Schreiber, der ihn füllt, blockiert einfach, bis der Leser aufholt. In einem Python-Programm kann sie ein Generator sein, sodass jeder Filter den nächsten Datensatz vom vorherigen abruft. In einer Datenplattform kann sie eine Message Queue oder ein Topic in einem Log wie Apache Kafka sein, wodurch Filter auf verschiedenen Maschinen laufen und Neustarts überstehen können. Der Filtercode ändert sich dabei kaum; die betrieblichen Eigenschaften ändern sich sehr stark.
Es gibt Varianten der strengen Form. Eine Pipeline kann sich auffächern (ein Tester leitet Datensätze an zwei verschiedene Consumer) oder zusammenführen (zwei Producer speisen einen Transformer). Sie kann linear sein, was am einfachsten nachzuvollziehen ist, oder ein gerichteter azyklischer Graph von Schritten, wie in Workflow-Werkzeugen wie Apache Airflow. Streng genommen bricht ein Filter mit internem Zustand, etwa einer, der zählt oder sortiert, die inkrementelle Eigenschaft, weil er die gesamte Eingabe sehen muss, bevor er Ausgabe erzeugt; sort in einer Unix-Pipeline ist ein solcher Filter. Solche Filter sind erlaubt, aber sie werden zu den Stellen, an denen Speicher, Latenz und Neustarts Nachdenken erfordern.
Beispiele: wo Pipelines vorkommen
ETL- und Datenpipelines. ETL-Jobs (Extract, Transform, Load) bringen Daten aus operativen Systemen in ein Data Warehouse: die Bestellungen von gestern extrahieren, bereinigen und umwandeln, mit Restaurantdaten zusammenführen, in Berichtstabellen laden. Jeder Schritt ist ein Filter, und moderne Datenplattformen (Apache Beam, Spark, dbt-Modelle, Cloud-Dataflow-Dienste) sind im Wesentlichen Werkzeuge, um solche Pipelines in großem Maßstab zu bauen und zu betreiben.
Compiler. Ein klassischer Compiler ist eine Pipeline aus Phasen: Die lexikalische Analyse macht aus Zeichen Tokens, das Parsen macht aus Tokens einen Syntaxbaum, die semantische Analyse prüft Typen und Namen, die Optimierung schreibt eine Zwischendarstellung um, und die Codegenerierung erzeugt Maschinencode. Jede Phase verarbeitet eine Darstellung und erzeugt die nächste, sodass Phasen getrennt entwickelt und ersetzt werden können; das LLVM-Projekt etwa lässt viele Sprach-Frontends einen Optimierer und eine Reihe von Backends gemeinsam nutzen. Echte Compiler lockern den strengen Stil, weil Phasen eine Symboltabelle teilen.
Medienverarbeitung. Videotranskodierung und Audioverarbeitung verketten Decoder, Skalierer, Filter und Encoder. GStreamer baut Medienanwendungen ausdrücklich als Pipelines aus Elementen, die über Pads verbunden sind, und die Filtergraphen von FFmpeg funktionieren genauso.
Log- und Ereignisverarbeitung. Log-Shipper wie Logstash und Fluent Bit werden als Eingabe-, Filter- und Ausgabestufen konfiguriert: Logzeilen lesen, in Felder parsen, das Rauschen von Health-Checks verwerfen, personenbezogene Daten maskieren und alles an einen Suchindex senden. Die Observability-Daten der Essenslieferplattform fließen auf diese Weise.
Feature- und Inferenzpipelines im maschinellen Lernen. Die Aufbereitung von Trainingsdaten sowie die Vor- und Nachverarbeitung rund um ein Modell werden üblicherweise als Pipelines gebaut, worauf Kapitel 12 zurückkommt.
# Top ten URLs that returned HTTP 500 in today's access log
grep '" 500 ' access.log \
| awk '{print $7}' \
| sort \
| uniq -c \
| sort -rn \
| head -10import json
def read_lines(path): # producer
with open(path) as f:
for line in f:
yield line
def parse(lines): # transformer
for line in lines:
yield json.loads(line)
def only_failed(events): # tester
for e in events:
if e["status"] == "payment_failed":
yield e
def count_by_provider(events): # consumer
counts = {}
for e in events:
counts[e["provider"]] = counts.get(e["provider"], 0) + 1
return counts
events = only_failed(parse(read_lines("orders.jsonl")))
print(count_by_provider(events))Batch und Streaming
Dieselbe Struktur läuft in zwei Modi. Die Stapelverarbeitung verarbeitet eine begrenzte Eingabe, etwa alle Bestellungen von gestern, läuft bis zum Ende und stoppt. Sie ist leicht nachzuvollziehen: Schlägt ein Lauf fehl, beheben Sie das Problem und starten ihn erneut, vorausgesetzt, jeder Lauf ersetzt seine Ausgabe, statt an sie anzuhängen. Nächtliche Auszahlungen an Restaurants, monatliche Rechnungen und Warehouse-Ladevorgänge sind typische Batch-Pipelines. Ihre Schwäche ist die Verzögerung: Die Ergebnisse sind so alt wie der letzte Lauf.
Stream Processing verarbeitet eine unbegrenzte Eingabe und jeden Datensatz kurz nach seinem Eintreffen: Kurierpositionen, Zahlungsereignisse, Klicks. Die Ergebnisse sind frisch, doch es tauchen neue Fragen auf. Ein Stream endet nie, daher müssen Aggregate über Zeitfenster berechnet werden (Bestellungen pro Restaurant je fünf Minuten); Datensätze können verspätet oder in falscher Reihenfolge eintreffen; und die Pipeline muss ununterbrochen laufen, sich von Abstürzen erholen, ohne Datensätze zu verlieren oder doppelt zu zählen, und Spitzen der Eingaberate verkraften. Frameworks wie Kafka Streams, Apache Flink und Apache Beam bieten Zeitfenster, Checkpoints und State Stores für diese Probleme. Das Modell von Beam behandelt Batch als Sonderfall von Streaming mit begrenzter Eingabe, sodass eine Pipeline-Definition auf beide Arten laufen kann.
Viele Organisationen betreiben beides: einen Stream für frische, näherungsweise operative Dashboards und einen Batch-Job, der über Nacht die exakten Zahlen neu berechnet. Abschnitt 7.8 behandelt die Seite der Datenarchitektur bei dieser Wahl.
Ausgearbeitetes Beispiel
Ausgearbeitetes Beispiel: den Engpass finden
Die Essenslieferplattform lädt Bestell-Events über vier Filter in ihr Data Warehouse. Gemessen auf einem Worker kann jeder Filter für sich die folgende Anzahl Datensätze pro Sekunde verarbeiten.
| Kapazitäten | Parse 50.000/s; Enrich (schlägt Restaurant und Region nach) 8000/s; Validate 40.000/s; Load 12.000/s. |
|---|---|
| Durchsatz der Pipeline | Eine Pipeline kann nicht schneller sein als ihr langsamster Filter: min(50.000, 8000, 40.000, 12.000) = 8000 Datensätze pro Sekunde. |
| Zeit für die Daten eines Tages | An einem geschäftigen Tag erzeugt die Plattform 1.000.000 Bestell-Events. 1.000.000 ÷ 8000 = 125 Sekunden. |
| Den Engpass skalieren | Enrich ist zustandslos, daher können zwei Instanzen parallel laufen, die jeweils die Hälfte der Datensätze übernehmen: 2 × 8000 = 16.000/s auf dieser Stufe. |
| Neuer Durchsatz | min(50.000, 16.000, 40.000, 12.000) = 12.000/s. Jetzt ist Load der Engpass. 1.000.000 ÷ 12.000 ≈ 83 Sekunden, eine Ersparnis von 33 % statt der 50 %, die eine naive Lesart nahelegen würde. |
| Was die Puffer bewirken | Ohne Backpressure würde Parse Datensätze mit 50.000/s in die Pipe vor Enrich schieben, während Enrich 8000/s abarbeitet; der Puffer würde also um 42.000 Datensätze pro Sekunde wachsen, bis der Speicher erschöpft ist. |
Einen anderen Filter als den Engpass zu beschleunigen ändert nichts; messen Sie zuerst.
Nur zustandslose Filter lassen sich so einfach parallelisieren. Ein Filter mit Zustand, etwa laufenden Summen pro Restaurant, muss seine Eingabe nach Schlüssel partitionieren, damit jede Instanz alle Datensätze zu ihren Schlüsseln sieht.
Das ist dieselbe Überlegung wie bei Amdahls Gesetz und der Auslastung aus Abschnitt 2.4: Die langsamste serielle Stufe setzt die Grenze.
Eine Pipeline zu skalieren verschiebt den Engpass
Backpressure und Fehlerbehandlung
Ist ein Producer schneller als ein Consumer, muss etwas nachgeben. Die Optionen sind: puffern (was das Problem nur aufschiebt, sofern der Puffer nicht begrenzt ist), Datensätze verwerfen oder Stichproben nehmen (für manche Metriken akzeptabel, für Zahlungen nie) oder Backpressure anwenden: die schnelle Stufe auf das Tempo der langsamen bremsen. Unix-Pipes tun das automatisch, weil eine volle Pipe den Schreiber blockiert. Pull-basierte Entwürfe wie Python-Generatoren tun es konstruktionsbedingt, weil nichts erzeugt wird, bevor die nächste Stufe danach fragt. Verteilte Pipelines brauchen es als bewusstes Designelement: begrenzte Queues, Consumer, die in ihrem eigenen Tempo aus einem Log abrufen, oder Protokolle wie die Reactive-Streams-Spezifikation der JVM, in der ein Subscriber signalisiert, für wie viele Datensätze er bereit ist. Abschnitt 9.3 behandelt Queues und Backpressure ausführlich.
Fehler brauchen für jeden Filter eine Strategie, die vorab festgelegt wird. Ein fehlerhafter Datensatz (eine Poison Message) sollte die Pipeline nicht anhalten: Leiten Sie ihn mit dem Grund an eine Dead Letter Queue oder Fehlerdatei weiter, zählen Sie ihn und schlagen Sie Alarm, wenn die Rate steigt. Ein vorübergehender Fehler, etwa ein Timeout bei einem Nachschlagedienst, wird mit Back-off wiederholt. Ein Absturz der Pipeline braucht einen Wiederaufsetzpunkt: Batch-Jobs laufen von vorn oder ab einem Checkpoint neu, und Streaming-Frameworks sichern ihre Position und ihren Zustand in Checkpoints. Weil Neustarts bedeuten, dass manche Datensätze doppelt verarbeitet werden, sollten Consumer, die Ergebnisse schreiben, idempotent sein, etwa durch einen Upsert auf einen natürlichen Schlüssel statt blinden Einfügens. Bei Fehlern ist der Stil am schwächsten: Filter wissen bewusst nichts voneinander, daher kann ein in Schritt vier entdeckter Fehler nicht ohne Weiteres rückgängig machen, was Schritt zwei schon getan hat, und eine Pipeline, die ein solches koordiniertes Rollback braucht, ist vermutlich der falsche Stil.
Merkmale von Pipes and Filters (einzelnes Deployment)
| Merkmal | Bewertung | Begründung |
|---|---|---|
| Einfachheit | Hoch | Jeder Filter tut eine Sache; der Fluss ist leicht zu zeichnen, zu lesen und zu erklären. |
| Modularität und Wiederverwendung | Hoch | Filter teilen keinen Zustand und kennen nur Datenformate, daher lassen sie sich wiederverwenden, umordnen und ersetzen. |
| Testbarkeit | Hoch | Ein Filter wird mit Beispieleingaben und erwarteten Ausgaben getestet, ohne seine Nachbarn. |
| Gesamtkosten | Hoch (günstig) | Ein Programm oder Job; wenig Infrastruktur, sofern nicht verteilt. |
| Durchsatz | Mittel bis hoch | Die Stufen laufen nebenläufig, und zustandslose Stufen lassen sich parallelisieren, doch der langsamste Filter deckelt alles. |
| Latenz und Interaktivität | Niedrig | Gebaut für Datenflüsse, nicht für Anfrage-Antwort-Dialoge mit Nutzern; jede Stufe fügt Verzögerung hinzu. |
| Deploybarkeit | Niedrig bis mittel | Als ein Programm oder Job deployt jede Änderung die ganze Pipeline neu; über Queues verteilte Filter lassen sich getrennt releasen. |
| Fehlertoleranz | Niedrig bis mittel | In einem Prozess stoppt ein Absturz die ganze Pipeline; Checkpoints, Wiederholungen und Dead Letter Queues müssen ergänzt werden. |
| Gemeinsamer Zustand und Transaktionen | Niedrig | Filter sind bewusst zustandslos und unabhängig; Konsistenz oder Rollback über Schritte hinweg ist umständlich. |
| Skalierbarkeit | Niedrig bis mittel in einem Prozess | Höher, wenn Filter über Queues verteilt sind, auf Kosten von Betriebsaufwand und Zustellgarantien. |
Wann man ihn einsetzt, wann nicht, und Anti-Patterns
Setzen Sie Pipes and Filters ein, wenn das Problem von Natur aus ein Datenfluss in eine Richtung durch klar getrennte Transformationen ist: Aufnahme, Umwandlung, Anreicherung, Validierung, Aggregation, Export. Es ist die Standardwahl im Data Engineering, für die Verarbeitung von Dateien und Medien und für jeden Job, bei dem sich die Schritte häufiger ändern als der Gesamtablauf. Es kommt auch innerhalb anderer Stile vor: ein nächtlicher Reporting-Job in einem modularen Monolithen oder ein Stream-Processing-Service in einem ereignisgesteuerten System.
Vermeiden Sie es als Hauptstruktur interaktiver Anwendungen, bei denen Nutzer sofortige, dialogartige Antworten erwarten und die Arbeit kein Fluss ist; für Logik, bei der viele Schritte denselben Zustand teilen und ändern müssen; und für Prozesse, die ein koordiniertes Rollback über Schritte hinweg brauchen, was eine Transaktion oder eine Saga erfordert.
Häufige Anti-Patterns: der Gott-Filter, ein Schritt, der parst, anreichert, validiert und schreibt und damit die Vorteile des Stils verliert; verborgener gemeinsamer Zustand, Filter, die über eine gemeinsame Datenbanktabelle oder globale Variable statt über die Pipe kommunizieren und damit die Kopplung zurückbringen, die der Stil beseitigt; Formatdrift, ein Producer, der still sein Ausgabeformat ändert und jeden nachgelagerten Filter bricht, verhindert durch explizite Schemas und Schemaprüfungen (siehe Abschnitt 7.7); unbegrenzte Puffer, die einen langsamen Consumer in einen Absturz wegen Speichermangels verwandeln; und nicht idempotente Senken, die nach jeder Wiederholung doppelt zählen.
Den Stil im Code durchsetzen
Die Einschränkungen des Stils lassen sich explizit machen. Geben Sie jedem Filter dieselbe einfache Signatur (ein Iterable von Datensätzen hinein, ein Iterable heraus), damit das Zusammensetzen mechanisch wird. Definieren Sie das Datensatzformat an jeder Pipe als Schema (eine Dataclass, ein JSON Schema, eine Avro- oder Protocol-Buffers-Definition) und validieren Sie es an der Grenze. Halten Sie Filter frei von gemeinsamem veränderlichem Zustand: Ein Architekturtest kann Filtermodulen verbieten, die Datenbankschicht oder einander zu importieren, sodass Daten nur über die Pipe zwischen ihnen fließen können. Testen Sie jeden Filter mit kleinen Ein- und Ausgabe-Fixtures und die zusammengesetzte Pipeline Ende-zu-Ende mit einer repräsentativen Stichprobe, einschließlich fehlerhafter Datensätze, um zu belegen, dass die Fehlerstrategie funktioniert.
Überwachen Sie in verteilten Pipelines den Durchsatz pro Stufe, die Queue-Tiefe und den Consumer-Lag: Eine wachsende Queue vor einer Stufe ist das sichtbare Zeichen eines Engpasses oder eines versagenden Filters, und sie ist die erste Metrik, auf die man einen Alarm legen sollte.
Prüfen Sie Ihr Verständnis
Welche drei Einschränkungen legten Garlan und Shaw Filtern auf?
Filter müssen unabhängige Einheiten sein, dürfen keinen Zustand mit anderen Filtern teilen und dürfen die Identität der Filter vor oder hinter ihnen nicht kennen.
Warum bricht der Unix-Befehl sort die inkrementelle Eigenschaft einer Pipeline?
Er muss seine gesamte Eingabe lesen, bevor er die erste Zeile ausgeben kann, hält also den ganzen Datenbestand und verzögert alles Nachfolgende, bis seine Eingabe endet.
Eine Pipeline hat Stufen, die 20.000, 5000 und 15.000 Datensätze pro Sekunde verarbeiten können. Wie hoch ist ihr Durchsatz, und was geschieht, wenn die mittlere Stufe verdoppelt wird?
5000 pro Sekunde, bestimmt durch die langsamste Stufe. Eine Verdopplung auf 10.000 lässt die Pipeline mit 10.000 pro Sekunde laufen, immer noch unter den beiden anderen Stufen.
Warum sollte der letzte Consumer einer neu startbaren Pipeline idempotent sein?
Weil ein Neustart oder eine Wiederholung manche Datensätze doppelt liefern kann; ein idempotenter Schreibvorgang wie ein Upsert auf einen natürlichen Schlüssel liefert dasselbe Ergebnis, egal wie oft ein Datensatz ankommt.
Quellen und weiterführende Literatur
Haben Sie eine Frage zu diesem Abschnitt? ArchitectBot, der KI-Lernassistent des Kurses, antwortet zuerst aus dem Kurs.
Fragen: „Unser nächtlicher ETL-Job dauert vier Stunden, und wir wollen ihn unter eine Stunde bringen. Wo sollten wir anfangen zu suchen?“