Zum Inhalt

Automatische Übersetzung

Dieser Artikel wurde automatisch aus der englischen Originalversion übersetzt.

Moderne Engines für die Datenverarbeitung im Vergleich: Polars, DataFusion, Daft, Ray Data, Pandas und Spark

Jahrelang übernahm Pandas tabellarische In-Memory-Arbeit und Apache Spark die verteilte Variante. Diese Aufteilung funktionierte gut, solange die Daten strukturiert waren.

Moderne Workloads sind aber nicht mehr immer strukturiert. Bild-, Audio- und Video-Pipelines laufen heute neben tabellarischen Pipelines, und sobald GPU-Inferenz ins Spiel kommt, sind die Garbage Collection der JVM und Pythons GIL keine bloßen Unannehmlichkeiten mehr, sondern der Engpass. Das ist ein wesentlicher Grund dafür, warum sich eine neue Generation von Engines auf Basis von Rust und Apache Arrow durchgesetzt hat.

Ich habe im letzten Jahr die meisten meiner Single-Node-Pipelines auf Polars umgestellt und wollte sehen, wie es sich tatsächlich gegen den Rest des neuen Stacks schlägt. Also habe ich Polars, DataFusion, Daft, Ray Data und Spark auf realen Datensätzen benchmarked — NYC-Taxifahrten für den tabellarischen Teil, Food-101-Bilder für den multimodalen Teil.

Der gesamte Code liegt im Repository engine-comparison-demo. Klone es und führe die Benchmarks auf deiner eigenen Hardware aus — deine Zahlen werden sich von meinen unterscheiden, und genau das ist der Punkt.

Kurzfassung: In marketingtauglichen Benchmarks erreichen Rust-native Engines auf einem Single Node bis zu 94x Speedup gegenüber Pandas. In realen Workloads fällt der Vorteil geringer aus, bleibt aber konsistent — Polars und DataFusion sind beide solide Standardoptionen. Für multimodale Pipelines laufen Pipeline-orientierte Engines wie Ray Data und Daft bis zu 18x schneller als Spark, weil sie GPUs tatsächlich auslasten. Es gibt keinen Gesamtsieger. Die richtige Wahl hängt von deinen Daten, deinem Maßstab und davon ab, ob GPUs im Spiel sind.


Typen von Datenverarbeitungs-Workloads

Engines spezialisieren sich, also ist die erste Frage: Welche Art von Workload hast du eigentlich? Die wichtigste Trennung ist strukturiert vs. multimodal.

Zwei Welten von Daten: Strukturiert vs. multimodal

Strukturierte / tabellarische Daten. Filtern, Aggregieren, Joinen. CPU-gebunden, meist im Speicher. Viel von dieser Arbeit läuft auf verteilten Clustern, die eigentlich nie hätten verteilt sein müssen — etwa 90 % der Abfragen verarbeiten weniger als 1 TB Daten, was heute problemlos auf eine einzelne Maschine passt.

Multimodale KI. Bilder, Audio, Video. Auf der Inferenzseite GPU-gebunden, aber das CPU-Preprocessing bremst. Deep-Learning-Modelle verbrauchen Daten schneller, als CPUs sie dekodieren können, sodass auf einer Standard-g6.xlarge (4 CPUs pro GPU) die GPU-Auslastung unter 20 % fällt. Die gesamte Pipeline wird dann dadurch begrenzt, wie schnell du die GPU füttern kannst.

Die neuen Engines zielen auf beides. Rust liefert Geschwindigkeit, Arrow ermöglicht Zero-Copy-Datenaustausch über Prozessgrenzen hinweg, und Streaming-Ausführung erlaubt die Verarbeitung von Datensätzen, die größer als der RAM sind.


Teil 1: Single-Node-Verarbeitung

Der Pandas-Engpass ist kein Bug. Er ist das Design. Pandas lädt Dateien eager in den RAM, kopiert Zwischenergebnisse bei fast jeder Operation und bleibt wegen des GIL auf einen Thread beschränkt. In den PDS-H-Benchmarks bei Scale Factor 10 braucht Pandas etwa 365 Sekunden, um eine Standard-Query-Suite abzuschließen. Die Streaming-Engine von Polars erledigt dieselbe Arbeit in 3,89 Sekunden — etwa 94x schneller.

Eager vs. Lazy Execution

Polars: Verarbeitung tabellarischer Daten

Polars hat in vielen Teams, mit denen ich gesprochen habe, Pandas als Standard für ernsthafte lokale ETL-Workloads ersetzt. Es ist in Rust geschrieben und nutzt lazy execution: Statt jede Zeile direkt auszuführen, erstellt es einen Query-Plan, optimiert ihn (Predicate Pushdown, Projection Pruning, Column Pruning) und führt ihn dann in Streaming-Batches über alle CPU-Kerne aus.

Mit wachsendem Datenvolumen vergrößert sich der Abstand. Bei Scale Factor 100 (~100 GB) war die Streaming-Engine von Polars nach 23,94 Sekunden fertig, verglichen mit 152,27 Sekunden für die eigene In-Memory-Engine — etwa 6x schneller, auf Daten, die größer als der RAM sind.

Aus engine_comparison_examples.ipynb:

import polars as pl

# scan_parquet reads only the schema — no data loaded yet
q = (
    pl.scan_parquet("yellow_tripdata_2024-01.parquet")
    .filter(
        (pl.col("trip_distance") > 5.0)
        & (pl.col("total_amount") > 30.0)
    )
    .group_by("payment_type")
    .agg(
        pl.col("total_amount").mean().alias("avg_fare"),
        pl.col("trip_distance").mean().alias("avg_distance"),
        pl.len().alias("trip_count"),
    )
)

# The entire plan is optimized and executed here, in parallel
result = q.collect()

Der Unterschied zu Pandas ist nicht die API. Es ist die Architektur. Polars erstellt zuerst den Plan und optimiert die gesamte Pipeline, bevor es Daten anfasst. Das filter wird bis in den Parquet-Reader heruntergereicht, sodass ganze Row Groups übersprungen werden. Nur die in der Abfrage referenzierten Spalten werden von der Platte gelesen. Pandas kann nichts davon — jede Zeile wird ausgeführt, sobald sie geparst ist, und überall entstehen Zwischenkopien.

Apache DataFusion: Erweiterbare Query Engine

Während Polars eine Library ist, die du direkt verwendest, ist DataFusion die Engine, auf der du andere Engines aufbaust. Sie treibt InfluxDB 3.0, GreptimeDB und Apples Comet Spark accelerator an.

Im November 2024 wurde DataFusion zur schnellsten Single-Node-Engine für Abfragen auf Parquet-Dateien in ClickBench, vor DuckDB, chDB und ClickHouse auf derselben Hardware. Das Embucket-Team führte später TPC-H mit Scale Factor 1000 (~1 TB) auf einem Single Node darauf aus. Das ist eine nützliche Zahl, an die man denken sollte, wenn jemand sagt, er brauche einen Cluster.

Aus engine_comparison_examples.ipynb:

from datafusion import SessionContext

ctx = SessionContext()
ctx.register_parquet("taxi", "yellow_tripdata_2024-01.parquet")

# SQL executed directly against Parquet — no intermediate copies
df = ctx.sql("""
    SELECT payment_type,
           COUNT(*)           AS trip_count,
           AVG(trip_distance) AS avg_distance,
           AVG(total_amount)  AS avg_fare
    FROM taxi
    WHERE trip_distance > 5.0 AND total_amount > 30.0
    GROUP BY payment_type
    ORDER BY trip_count DESC
""")
result = df.to_pandas()

Die Stärke von DataFusion ist seine Modularität. Die Erweiterungs-APIs decken benutzerdefinierte Catalogs, Table Providers, Optimizer Rules und Execution Plans ab. Deshalb taucht es immer dann auf, wenn jemand eine eigene Datenplattform baut oder eine Query Engine in ein eigenes Produkt einbettet.

Daft: Multimodale Datenverarbeitung

Daft ist die Engine, die multimodale Daten ernst nimmt. Bilder, Audio, Video, Embeddings — das sind echte Typen im DataFrame, keine opaken Bytes. Pandas behandelt ein Bild als Byte-Array, Spark serialisiert es durch die JVM, aber Daft hat native Rust-Expressions zum Dekodieren von Bildern, Abrufen von URLs und Erzeugen von Tensoren ohne die Python-Schleife in der Mitte.

Aus engine_comparison_examples.ipynb:

import daft

df = daft.read_parquet("yellow_tripdata_2024-01.parquet")

result = (
    df.where(
        (daft.col("trip_distance") > 5.0)
        & (daft.col("total_amount") > 30.0)
    )
    .groupby("payment_type")
    .agg(
        daft.col("total_amount").mean().alias("avg_fare"),
        daft.col("trip_distance").mean().alias("avg_distance"),
        daft.col("trip_distance").count().alias("trip_count"),
    )
    .collect()
)

Die API fühlt sich vertraut an, wenn du Pandas kennst, aber die Engine ist derselbe Rust- + Arrow-Stack wie in Polars und DataFusion. Daft zahlt sich dort aus, wo die „Zeilen“ in deinem DataFrame Bilder, PDFs oder Tensoren sind.

Benchmark: Single-Node-Performance

Ich habe alle vier Engines plus natives Rust über Polars-rs auf ~41 Mio. NYC yellow taxi trips für das gesamte Jahr 2024 benchmarked. Die vollständigen Ergebnisse stehen im Demo-Repo:

Benchmark-Ergebnisse: Single-Node-Performance

Bei dieser Größe (~660 MB Parquet) ist jede moderne Engine schnell. Die Schlagzeile „94x Polars vs. Pandas“ stammt aus PDS-H, einem synthetischen analytischen Benchmark — auf meinem Workload mit 41 Mio. Zeilen bekam ich eine reale, konsistente Verbesserung gegenüber Pandas, aber bei weitem keine 94x. Das interessantere Ergebnis ist, dass Polars, DataFusion, Daft und pures Rust über Polars-rs bei diesem Workload alle grob in derselben Größenordnung liegen. DataFusion liegt bei der Gesamtzeit knapp vorn. Polars ist am angenehmsten zu schreiben und ein solider Allrounder.


Teil 2: Umgang mit multimodalen Daten

Tabellarisches ETL verkleinert Daten meist: filtern, aggregieren, weniger schreiben als gelesen wurde. Multimodale KI-Pipelines machen das Gegenteil. Ein einzelner Dokumentpfad kann in Dutzende Text-Chunks und Embedding-Vektoren auffächern.

Spark behandelt Bilder und Audio als binäre Blobs. Sie in Python anzufassen bedeutet einen Sprung von der JVM nach Python über Py4J, Verarbeitung mit Pillow oder OpenCV und dann zurück eine Serialisierung. Dieser Roundtrip dominiert bei vielen realen Workloads die Wall-Clock-Zeit.

Pipeline-Ausführung und GPU-Auslastung

Spark parallelisiert Partitionen über Kerne hinweg, aber seine stufenbasierte (bulk-synchronous) Ausführung ist der Haken. Innerhalb eines einzelnen Tasks läuft jeder Schritt (Download, Dekodierung, Klassifikation) sequentiell auf einem Kern ohne asynchrone Überlappung. Und jeder Task in einer Stage muss fertig sein, bevor die nächste Stage beginnt. GPUs sind untätig, während CPUs preprocessen; CPUs sind untätig, während GPUs Inferenz ausführen. Jede Stage materialisiert ihren Output, bevor die nächste beginnt, was zusätzlich Speicherdruck erzeugt.

Pipeline-Ausführungsmodell

Pipelined execution ist die Alternative. Statt Stages nacheinander auszuführen, überlappt die Engine I/O, CPU-Arbeit und GPU-Inferenz, sodass zu jedem Zeitpunkt nur die langsamste Komponente wartet.

Benchmark: Bildverarbeitung

Ich habe Bildverarbeitung auf 500 realen Fotos aus dem Food-101-Datensatz benchmarked. Ergebnisse aus dem Demo-Repo:

Benchmark-Ergebnisse: Multimodale Performance

Polars und DataFusion tauchen hier nicht auf, weil sie keine nativen Bildoperationen haben — Bildarbeit würde auf sequenzielles Python zurückfallen und der Vergleich wäre unfair. Die relevanten Zahlen: Die Rust-nativen Bildoperationen von Daft sind 3,6x schneller als Pandas + Pillow, und pures Rust ist über die gesamte Pipeline hinweg 4,2x schneller als Pandas.


Teil 3: Verteilte Verarbeitung

Sobald eine einzelne Maschine nicht mehr reicht, musst du entscheiden, wie du die Arbeit verteilst. Hier werden die architektonischen Unterschiede zwischen den Engines tatsächlich relevant.

Apache Spark

Spark ist immer noch das, wozu ich bei tabellarischem ETL im Petabyte-Maßstab mit unübersichtlichen Shuffles greifen würde. Fehlertoleranz über RDD-Lineage funktioniert, das Ökosystem ist riesig (Databricks, EMR), und Multi-TB-Joins sind etabliertes Terrain. Bei KI-Workloads bricht es auseinander. Tasks werden auf JVM-Executors geplant, die nichts über GPUs wissen, sodass GPUs untätig auf CPU-Preprocessing warten.

Aus distributed_spark.ipynb:

from pyspark.sql import SparkSession
from pyspark.sql import functions as F

spark = SparkSession.builder \
    .appName("TaxiETL") \
    .config("spark.sql.adaptive.enabled", "true") \
    .getOrCreate()

orders = spark.read.parquet("s3a://lake/taxi/*.parquet")
zones = spark.read.csv("s3a://lake/taxi_zones.csv", header=True)

# Spark excels at this: joining massive tables with a shuffle
result = (
    orders
    .filter(F.col("trip_distance") > 5.0)
    .join(zones, orders.PULocationID == zones.LocationID, "inner")
    .groupBy("Borough")
    .agg(
        F.sum("total_amount").alias("total_revenue"),
        F.avg("total_amount").alias("avg_fare"),
    )
    .orderBy(F.desc("total_revenue"))
)

Ray Data: GPU-Auslastung

Ray Data wurde um KI-Workloads herum entworfen. Statt der Stage-Barrieren von Spark verwendet es ein Streaming-Modell, das GPUs beschäftigt hält. Das Feature, das seine Existenz rechtfertigt, ist gemischtes Ressourcen-Scheduling: Du kannst deklarieren, dass ein Actor „1 GPU, 4 CPUs“ will und ein anderer nur CPUs, und Ray erledigt den Rest.

Amazon berichtete von Einsparungen von über 120 Millionen US-Dollar pro Jahr, indem bestimmte Workloads von Spark auf Ray verlagert wurden. Die PoC-Zahlen lagen bei 91 % besserer Kosteneffizienz und 13x mehr Daten pro Stunde; in Produktion stabilisierte sich der Wert bei 82 % besserer Kosteneffizienz pro GiB S3-Input.

Aus distributed_ray.ipynb:

import ray

ray.init()

ds = ray.data.read_images("s3://my-bucket/food101/")

class ImageClassifier:
    def __init__(self):
        import torch
        from torchvision.models import resnet18, ResNet18_Weights
        self.model = resnet18(weights=ResNet18_Weights.DEFAULT).cuda()
        self.model.eval()
        self.preprocess = ResNet18_Weights.DEFAULT.transforms()

    def __call__(self, batch):
        import torch
        tensors = torch.stack([
            self.preprocess(img) for img in batch["image"]
        ]).cuda()
        with torch.no_grad():
            preds = self.model(tensors)
        return {
            "prediction": preds.argmax(dim=1).cpu().numpy(),
            "confidence": preds.max(dim=1).values.cpu().numpy(),
        }

# ActorPoolStrategy creates persistent GPU workers
predictions = ds.map_batches(
    ImageClassifier,
    compute=ray.data.ActorPoolStrategy(size=4),
    num_gpus=1,
    batch_size=64,
)
predictions.write_parquet("s3://output/predictions/")

Ein Detail, das man kennen sollte: Wenn du mehr CPUs pro GPU hinzufügst, skaliert Ray Data weiter, während andere Engines plateauieren. In Anyscales Benchmarks brachte der Wechsel von einem 4:1- zu einem 32:1-CPU-zu-GPU-Verhältnis einen 3x-Speedup bei Bildinferenz, weil die CPU-Feeder endlich mit der GPU Schritt hielten.

Daft (verteilt)

Daft skaliert über seine Flotilla-Engine: ein Swordfish-Worker pro Node, mit Flotilla darüber für clusterweites Scheduling. Swordfish übernimmt die lokale Rust-Ausführung und pipelinet I/O mit Compute über Small-Batch-Streaming, sodass jeder Node beschäftigt bleibt, ohne auf die nächste Stage zu warten.

Über vier multimodale Workloads hinweg lief Daft mit Flotilla 2–18x schneller als Spark. Beim schwersten davon (Video-Objekterkennung mit YOLO11n) brauchte Spark über 3,5 Stunden und Daft war in unter 12 Minuten fertig — 18x, hauptsächlich weil Spark diese Zeit mit Serialisierung und Stage-Barrieren verbringt.

Ein Vorbehalt: Anyscale, das Unternehmen hinter Ray, veröffentlichte eigene Benchmarks, in denen Ray Data den Abstand bei Instanzen mit vielen CPUs durch manuelles Tuning schließt oder umkehrt. Aktuell überholen sich beide quartalsweise gegenseitig.

Aus distributed_daft.ipynb:

import daft
from daft import col

# Daft's Flotilla engine distributes work across the cluster
df = daft.read_parquet("s3://data-lake/pdf_metadata/*.parquet")

# Download PDFs — Daft parallelizes downloads in Rust
df = df.with_column("pdf_bytes", col("pdf_url").url.download())

# Define a GPU UDF for embedding generation
@daft.udf(return_dtype=daft.DataType.list(daft.DataType.float32()))
class TextEmbedder:
    def __init__(self):
        from sentence_transformers import SentenceTransformer
        self.model = SentenceTransformer("all-MiniLM-L6-v2", device="cuda")

    def __call__(self, text_col):
        texts = text_col.to_pylist()
        embeddings = self.model.encode(texts, batch_size=32)
        return [emb.tolist() for emb in embeddings]

# Daft schedules CPU downloads and GPU embeddings simultaneously
df = df.with_column("embedding", TextEmbedder(col("text")))
df.write_parquet("s3://output/embeddings/")

Direktvergleich der verteilten Engines

Feature Spark Ray Data Daft (Flotilla)
Ausführungsmodell Task-pro-Core, partitionsbasiert Streaming-Tasks und Actors Swordfish-pro-Node, Streaming-Batches
Stärken Massives SQL/ETL, Fehlertoleranz Heterogene Compute-Ressourcen, GPU-Sättigung Multimodales Pipelining, begrenzter Speicher
GPU-Auslastung Schwach (stufenbasiert, keine CPU/GPU-Überlappung) Exzellent (explizite Ressourcenzuweisung) Exzellent (asynchrones I/O-Compute-Pipelining)
Tuning-Aufwand Hoch (Executor-Speicher, Kerne, Partitionen) Mittel (Batch-Größen, Object Store) Niedrig (Engine verwaltet Ressourcen)
Am besten geeignet für Tabellarisches ETL im Petabyte-Maßstab Training/Inferenz mit gemischten CPU+GPU Multimodales Datenladen für PyTorch

Teil 4: Die Rolle von Rust und Arrow

Unterhalb des Wettbewerbs ist die Konvergenz die interessantere Geschichte. Polars, Daft, DataFusion und Rays Core teilen alle dieselbe Grundlage: Apache Arrow als In-Memory-Format und Rust für die Nebenläufigkeit.

Konvergenz von Rust und Arrow

Das ist nicht nur architektonische Eleganz. Das Arrow PyCapsule Interface (__arrow_c_stream__-Protokoll) ermöglicht die Übergabe von Daten zwischen Engines ohne Serialisierung: in DataFusion rechnen, das Ergebnis ohne Kosten an Polars oder PyArrow übergeben; multimodales Preprocessing in Daft durchführen und Arrow-Batches direkt in einen PyTorch DataLoader einspeisen.

from datafusion import SessionContext
import polars as pl

# Compute in DataFusion (Rust-native execution)
ctx = SessionContext()
ctx.register_parquet("events", "events.parquet")
df = ctx.sql("""
    SELECT user_id, COUNT(*) as event_count
    FROM events WHERE event_type = 'purchase'
    GROUP BY user_id HAVING COUNT(*) > 5
""")

# Zero-copy conversion to Arrow, then to Polars
arrow_table = df.to_arrow_table()
df_polars = pl.from_arrow(arrow_table)

# Continue analysis in Polars
result = df_polars.with_columns(
    pl.col("event_count").rank().alias("rank")
).sort("rank")

Warum also Rust statt der JVM?

  1. Keine Garbage-Collection-Pausen. Ownership und Borrowing ersetzen GC, was keine Stop-the-World-Pausen und keine unvorhersehbaren Latenzspitzen bedeutet. Die OOM-Fehler, die in JVM-Systemen im großen Maßstab auftreten, verschwinden größtenteils.

  2. Keine Kosten durch Object Header. Die JVM fügt jedem Objekt 12–16 Byte Object Header hinzu. Über Milliarden kleiner Objekte hinweg ist das allein schon ein Speicherbudget. Rust zahlt diesen Preis nicht.

  3. Thread-Sicherheit zur Compile-Zeit. Rusts Typsystem lehnt Data Races ab, bevor das Binary gebaut wird, sodass Multi-Thread-Ausführung nicht mit der üblichen Klasse subtiler Concurrency-Bugs einhergeht.

In Kombination mit Arrows columnarem Speicherlayout verschwindet der Serialisierungsengpass, der in älteren JVM-Stacks bis zu 30 % der CPU-Zyklen frisst.


Teil 5: Mehrere Engines einsetzen

Die ehrliche Antwort ist: Du wählst nicht eine Engine. Du kombinierst sie.

Das Koexistenzmodell: Multi-Engine-Pipeline

In Produktion sieht das meist wie ein Staffellauf aus. Rohdaten in einem Lake werden von Spark (oder Dask, wenn dein Team nur Python nutzt) in Parquet- oder Delta-Tabellen bereinigt und gejoint. Diese Tabellen fließen dann in Ray Data oder Daft für die letzte Meile — GPU-Inferenz, Embedding-Generierung, multimodale Transformationen — für die Spark nie entworfen wurde. Für Ad-hoc-Analysen und lokale Entwicklung liefern Polars und DuckDB sofortiges Feedback, ohne einen Cluster hochzufahren.

Möglich wird diese Komposition durch offene Formate: Parquet, Delta, Iceberg, Arrow. Jede Engine im Stack liest und schreibt sie nativ, daher ist die Übergabe zwischen Stages nur ein Dateipfad.

Zusammenfassung der Einsatzfälle pro Engine

Engine-Auswahlmatrix

Aufgabe Empfohlenes Tool Trade-off
Ad-hoc-Analyse auf einem Laptop Polars / DuckDB Geschwindigkeit > Skalierung
Massives SQL ETL (Petabyte) Apache Spark Zuverlässigkeit > Geschwindigkeit
Python-native Skalierung Dask Einfachheit > Optimierung
Training von LLMs / Computer Vision Ray Data GPU-Auslastung > ETL-Features
Multimodales Datenladen Daft Developer Experience > Ökosystem
Eigene Query Engines bauen DataFusion Erweiterbarkeit > Out-of-the-box-Features

Diagonale Skalierung

Lange Zeit war die Wahl entweder Scale-up (eine größere Maschine) oder Scale-out (mehr Maschinen). Polars Cloud macht etwas dazwischen, das sie diagonale Skalierung nennen: horizontal skalieren, während aus Cloud Storage gelesen wird, um I/O maximal auszunutzen, und dann auf einen einzelnen großen Node zusammenfallen, sobald Filter und Aggregationen die Daten verkleinert haben, sodass der verteilte Shuffle komplett entfällt.

Der kontraintuitive Teil: Eine größere, teurere Instanz kann pro Job günstiger sein, weil sie die GPU-Auslastung so weit erhöht, dass die Gesamtausführungszeit schneller sinkt, als der Stundensatz steigt.


Probiere es selbst aus

Das begleitende Demo-Repository enthält alles, was du brauchst, um diese Benchmarks zu reproduzieren:

# Install dependencies
uv sync

# Run the tabular benchmark (~2.9M NYC taxi trips)
uv run python -m engine_comparison.benchmarks.tabular

# Run the multimodal benchmark (500 real food photos)
uv run python -m engine_comparison.benchmarks.multimodal

# Run native Rust benchmarks (Polars-rs + image crate)
cd rust_benchmark && cargo run --release && cd ..

Das Repo enthält außerdem Notebooks für Side-by-Side-API-Vergleiche und Docker-Compose-Setups für lokale verteilte Runs von Spark, Ray und Daft.


Zentrale Erkenntnisse

  1. Nicht verteilen, wenn es nicht nötig ist. Polars und DataFusion verarbeiten bis zu ~1 TB auf einer einzelnen Maschine, mit konsistenten Speedups gegenüber Pandas (und 94x bei schweren analytischen Benchmarks wie PDS-H). Greife erst dann zu einem Cluster, wenn du die Grenzen eines Single Nodes tatsächlich erreicht hast — nicht vorher.

  2. Die Grenzen von Pandas sind architektonisch. Eager Execution, Single-Threading, Zwischenkopien — das sind bewusste Designentscheidungen, die bei großen Datenmengen zu Engpässen werden. Lazy Execution mit Predicate Pushdown und Column Pruning ist der Unterschied.

  3. Pipeline-Ausführung schlägt Stage-Barrieren bei GPU-Workloads. Sparks Stages lassen GPUs untätig, während CPUs preprocessen. Ray Data und Daft überlappen I/O-, CPU- und GPU-Arbeit, sodass keine Ressource auf die nächste Stage wartet.

  4. Nutze jede Engine für das, worin sie gut ist. Spark für ETL im Petabyte-Maßstab, Polars und DuckDB für Ad-hoc-Analysen, Ray Data für GPU-Trainingspipelines, Daft für multimodales Datenladen, DataFusion für benutzerdefinierte Query Engines.

  5. Rust + Arrow ist die gemeinsame Grundlage. Es ist kein Zufall, dass Polars, DataFusion, Daft und Ray hier zusammenlaufen — damit verschwinden GC-Pausen, Serialisierungs-Overhead zwischen Prozessen und eine ganze Klasse von Concurrency-Bugs.

Wenn du nicht sicher bist, wo du anfangen sollst: Polars auf einer einzelnen Maschine, dann Daft oder Ray Data, sobald GPUs ins Spiel kommen. Spark nur dann, wenn du wirklich ein Petabyte hast.


Referenzen

Benchmarks und Performance-Daten

Engines und Frameworks

Architektur und Ökosystem

Demo-Repository

  • engine-comparison-demo - Begleitender Code zu diesem Artikel: Benchmarks, Notebooks und Docker Compose für Spark/Ray/Daft

Datensätze