Weiter zum Inhalt

DuckDB für Data Engineers: Beschleunige deine Datenpipelines um das 10‑Fache und mehr

DuckDB ist eine leistungsstarke Analyse-Engine, die auf deinem Laptop läuft. Damit beschleunigst du das Einlesen und Verarbeiten von Daten und reduzierst Laufzeiten in Pipelines von Minuten auf Sekunden. Folge dieser praxisnahen Anleitung.
Aktualisiert 18. Sept. 2026  · 13 Min. lesen

Mit KI erkunden

ChatGPTClaudePerplexity

Wie oft hat dich ein Manager gefragt, wie lange deine Pipeline läuft, und dir war die Antwort peinlich?

Damit bist du nicht allein. Den Komfort von Python bezahlt man irgendwo. Meistens mit der Laufzeitgeschwindigkeit. Pipelines sind schnell geschrieben, aber schwer zu skalieren und brauchen mit wachsender fachlicher Komplexität immer mehr Zeit.

Vielleicht ist aber nicht dein Code der Engpass. Vielleicht holst du aus pandas keine zusätzliche Performance mehr heraus. Vielleicht kannst du die Laufzeit durch den Wechsel auf eine andere Datenverarbeitungs-Engine von Stunden auf Minuten drücken.

Genau hier kommt DuckDB ins Spiel.

In diesem Artikel erkläre ich dir, was DuckDB ist und warum es für Data Engineers wichtig ist. Du lernst DuckDB an praktischen Beispielen kennen und siehst, wie viel schneller es im Vergleich zu den beliebtesten Python-Bibliotheken für die Datenverarbeitung ist. 

Was ist DuckDB, und ist es der nächste große Wurf für Data Engineers?

DuckDB ist ein Open-Source, eingebettetes, In-Process, relationales OLAP-DBMS.

In einfacher Sprache: eine analytische spaltenorientierte Datenbank, die im Speicher läuft. Als analytische Datenbank ist sie auf SELECT-Statements optimiert, nicht auf INSERT und UPDATE. Quasi wie SQLite – nur andersherum.

DuckDB gibt es seit rund 5 Jahren, aber die erste „stabile“ Version wurde im Juni 2024 angekündigt – zum Zeitpunkt des Schreibens vor 3 Monaten. Lass dich davon nicht täuschen: DuckDB wurde breit erprobt und gilt fast immer als Empfehlung, wenn Geschwindigkeit zählt.

Warum DuckDB für deine Datenpipelines wählen

Wenn du Data Engineer bist, sprechen ein paar ganz konkrete Vorteile für DuckDB in deinen Pipelines:

  • Es ist schnell: Denk in Größenordnungen schneller als Pythons Standard-Data-Frame-Bibliotheken. In den meisten Fällen sogar schneller als auf Performance getrimmte Libraries (z. B. polars).
  • Es ist Open Source: Der komplette Quellcode liegt in einem öffentlichen GitHub-Repo. Du kannst es nutzen, anpassen und sogar zum Projekt beitragen.
  • Es verschiebt den Cloud‑Zeitpunkt nach hinten: Cloud Computing kann teuer werden, oft aber gerechtfertigt, wenn Processing‑Speed geschäftskritisch ist. Mit DuckDB analysierst du Hunderte Millionen Zeilen auf deinem Laptop.
  • Es ist leicht zu lernen: Der Einstieg in DuckDB ist einfach. In bestehenden Pipelines musst du meist nur minimal etwas ändern, da DuckDB sich gut in Pythons Datenbibliotheken integriert.
  • Es integriert sich nahtlos mit Cloud‑Speichern: Du kannst z. B. eine DuckDB‑SQL‑Abfrage schreiben, die Daten direkt aus AWS S3 liest. Ein vorheriger Download ist nicht nötig.

Aus diesen Gründen (und weiteren weiter unten) ist DuckDB für mich der nächste große Schritt für Data Engineers.

Werde Dateningenieur

Baue Python-Kenntnisse auf, um ein professioneller Dateningenieur zu werden.
Jetzt Kostenlos Loslegen

So startest du mit DuckDB

Wie gesagt, DuckDB läuft auf deinem Laptop. Der erste Schritt ist also die Installation. 

Ich zeige dir die Schritte für macOS, aber die Anleitungen für Windows und Linux sind ebenfalls klar und leicht zu befolgen.

Schritt 1: DuckDB installieren

Auf dem Mac installierst du DuckDB am einfachsten mit Homebrew. Führe folgenden Shell‑Befehl aus:

brew install duckdb

Nach wenigen Sekunden solltest du diese Ausgabe sehen:

Installing DuckDB with Homebrew

Installing DuckDB with Homebrew

Nach der Installation öffnest du die DuckDB‑Shell mit:

duckdb

DuckDB shell

DuckDB shell

Ab hier stehen dir alle Türen offen!

Du kannst SQL‑Abfragen direkt in der Konsole schreiben und ausführen. Ich habe beispielsweise die ersten 6 Monate der NYC Taxi Data für 2024 heruntergeladen. Wenn du dasselbe getan hast, zeigt dir dieses Snippet die Zeilenanzahl einer einzelnen Parquet‑Datei:

SELECT COUNT(*)
  FROM PARQUET_SCAN("path-to-data.parquet");

Single Parquet file row counts

Single Parquet file row counts

Ja – fast 20 Millionen Zeilen in nur einer Datei. Ich habe 24 davon geladen.

Vermutlich willst du DuckDB nicht dauerhaft im Terminal nutzen. Als Nächstes zeige ich dir daher, wie du DuckDB mit Python verbindest.

Schritt 2: DuckDB in deinen Python‑Workflow einbinden

Ziel dieses Abschnitts ist zu zeigen, wie du DuckDB in der beliebtesten Sprache des Data Engineerings konfigurierst und nutzt: Python.

Tatsächlich bietet DataCamp einen kompletten Karriere‑Lernpfad für Data Engineering mit Python an.

Für Python gibt es eine eigene DuckDB‑Bibliothek, die du zuerst installieren musst. Führe Folgendes im Terminal aus, am besten in einer virtuellen Umgebung:

pip install duckdb

Erstelle nun eine neue Python‑Datei und füge diesen Code ein:

import duckdb
# Function to get the count from a single file
def get_row_count_from_file(conn: duckdb.DuckDBPyConnection, file_path: str) -> int:
    query = f"""
        SELECT COUNT(*)
        FROM PARQUET_SCAN("{file_path}")
    """
    return conn.sql(query).fetchone()[0]
if __name__ == "__main__":
    # In-memory database connection
    conn = duckdb.connect()
    # Path to a parquet file
    file_path = "fhvhv_tripdata_2024-01.parquet"
    # Get the row count
    row_count = get_row_count_from_file(conn=conn, file_path=file_path)
    print(row_count)

Kurz gesagt: Die Funktion ermittelt mit einer gültigen DuckDB‑Verbindung und einem Dateipfad die Zeilenanzahl einer Datei.

Nach dem Ausführen des Skripts erhältst du das Ergebnis nahezu instantan:

DuckDB and Python connection

DuckDB and Python connection

Das war’s! 

Als Nächstes zeige ich dir, was du mit Python und DuckDB in Lichtgeschwindigkeit anstellen kannst!

DuckDB in Aktion: So beschleunigst du Datenpipelines

Zur Einordnung: Ich führe den Code auf dem 2024‑Ausschnitt (Januar–Juni) des NYC Taxi Datasets aus – konkret für High‑Volume For‑Hire‑Vehicles. Das sind 6 Parquet‑Dateien mit ca. 3 GB Speicherbedarf. Als Hardware nutze ich ein M3 Pro MacBook Pro 16" mit 12 CPU‑Kernen und 36 GB Unified Memory.

Deine Zahlen können abweichen, sollten aber in ähnlichen Bereichen liegen.

Gigantische Datensätze im Handumdrehen einlesen

Ich habe die 6 Dateien zusätzlich in CSV und JSON konvertiert. So viel Speicher brauchen die Formate jeweils:

Dataset size comparison

Dataset size comparison

Gesamt: 2,96 GB für Parquet, 19,31 GB für CSV und 66,04 GB für JSON.

Ein gewaltiger Unterschied! Wenn du aus diesem Artikel nur eines mitnimmst, dann: Nutze bei großen Dateien immer das Parquet‑Format. Das spart Rechenzeit und Speicherplatz.

DuckDB bietet bequeme Funktionen, um mehrere Dateien desselben Formats auf einmal (per Glob‑Pattern) zu lesen. Ich nutze das, um alle sechs einzulesen, die Zeilen zu zählen und die Laufzeit zu vergleichen:

import duckdb
import pandas as pd
from datetime import datetime
def get_row_count_and_measure_time(file_format: str) -> str:
    # Construct a DuckDB query based on the file_format
    match file_format:
        case "csv":
            query = """
            SELECT COUNT(*)
            FROM READ_CSV("nyc-taxi-data-csv/*.csv")
            """
        case "json":
            query = """
            SELECT COUNT(*)
            FROM READ_JSON("nyc-taxi-data-json/*.json")
            """
        case "parquet":
            query = """
                SELECT COUNT(*)
                FROM READ_PARQUET("nyc-taxi-data/*.parquet")
            """
        case _:
            raise KeyError("Param file_format must be in [csv, json, parquet]")
    # Open the database connection and measure the start time
    time_start = datetime.now()
    conn = duckdb.connect()
    # Get the row count
    row_count = conn.sql(query).fetchone()[0]
    # Close the database connection and measure the finish time
    conn.close()
    time_end = datetime.now()
    return {
        "file_format": file_format,
        "duration_seconds": (time_end - time_start).seconds + ((time_end - time_start).microseconds) / 1000000,
        "row_count": row_count
    }
# Run the function
file_format_results = []
for file_format in ["csv", "json", "parquet"]:
    file_format_results.append(get_row_count_and_measure_time(file_format=file_format))
pd.DataFrame(file_format_results)

Die Ergebnisse sind eindeutig – es gibt nur einen klaren Sieger:

File reading runtime comparison

File reading runtime comparison

Dank DuckDB sind alle drei schnell und landen am selben Ziel, aber Parquet gewinnt klar mit einem Faktor 600 gegenüber CSV und einem Faktor 1200 gegenüber JSON.

Deshalb nutze ich im weiteren Verlauf ausschließlich Parquet.

Daten mit SQL abfragen

Mit DuckDB aggregierst du Daten per SQL.

Ich führe die Idee hier weiter aus. Konkret zeige ich dir, wie du Monatsstatistiken berechnest – Anzahl Fahrten, Fahrtzeit, Strecke, Kosten und Fahrerlohn – und das auf Basis von 120 Millionen Zeilen.

Das Beste daran: Es dauert nur zwei Sekunden!

Das ist der Code im Python‑Skript, der aggregiert und die Ergebnisse ausgibt:

conn = duckdb.connect()
query = """
    SELECT
        ride_year || '-' || ride_month AS ride_period,
        COUNT(*) AS num_rides,
        ROUND(SUM(trip_time) / 86400, 2) AS ride_time_in_days,
        ROUND(SUM(trip_miles), 2) AS total_miles,
        ROUND(SUM(base_passenger_fare + tolls + bcf + sales_tax + congestion_surcharge + airport_fee + tips), 2) AS total_ride_cost,
        ROUND(SUM(driver_pay), 2) AS total_rider_pay
    FROM (
        SELECT
            DATE_PART('year', pickup_datetime) AS ride_year,
            DATE_PART('month', pickup_datetime) AS ride_month,
            trip_time,
            trip_miles,
            base_passenger_fare,
            tolls,
            bcf,
            sales_tax,
            congestion_surcharge,
            airport_fee,
            tips,
            driver_pay
        FROM PARQUET_SCAN("nyc-taxi-data/*.parquet")
        WHERE 
            ride_year = 2024
        AND ride_month >= 1 
        AND ride_month <= 6
    )
    GROUP BY ride_period
    ORDER BY ride_period
"""
# Aggregate data, print it, and show the data type
results = conn.sql(query)
results.show()
print(type(results))

Die Ausgabe zeigt die Aggregationsergebnisse, den Variablentyp von results und die Gesamtlaufzeit:

Monthly summary statistics aggregation

Monthly summary statistics aggregation

Lass dir das auf der Zunge zergehen: 3 GB Daten mit über 120 Millionen Zeilen, in 2 Sekunden auf einem Laptop aggregiert!

Einzig der Variablentyp ist langfristig unpraktisch. Zum Glück ist das leicht zu beheben.

Integration mit pandas und DataFrames

Der Vorteil von DuckDB ist nicht nur die Geschwindigkeit, sondern auch die gute Integration mit deiner Lieblings‑Data‑Frames‑Library: pandas.

Wenn du ein temporäres Resultset wie oben hast, reicht ein Aufruf der Methode .df(), um es in einen pandas‑DataFrame zu konvertieren:

# Convert to Pandas DataFrame
results_df = results.df()
# Print the type and contents
print(type(results_df))
results_df

DuckDB to pandas conversion

DuckDB to pandas conversion

Ebenso kannst du DuckDB nutzen, um Berechnungen auf pandas‑DataFrames durchzuführen, die bereits im Speicher liegen.

Der Trick: Verweise im DuckDB‑SQL‑Query nach dem FROM‑Keyword einfach auf den Variablennamen. Hier ein Beispiel:

# Pandas DataFrame
pandas_df = pd.read_parquet("nyc-taxi-data/fhvhv_tripdata_2024-01.parquet")
# Run SQL queries through DuckDB
duckdb_res = duckdb.sql("""
    SELECT
        pickup_datetime,
        dropoff_datetime,
        trip_miles,
        trip_time,
        driver_pay
    FROM pandas_df
    WHERE trip_miles >= 300
""").df()
duckdb_res

DuckDB query on an existing Pandas DataFrame

DuckDB query on an existing pandas DataFrame

Unterm Strich kannst du pandas komplett umgehen – oder Aggregationen auf bestehenden DataFrames deutlich schneller ausführen.

Als Nächstes zeige ich dir ein paar fortgeschrittene DuckDB‑Features.

Advanced DuckDB für Data Engineers

DuckDB kann mehr, als man auf den ersten Blick sieht.

In diesem Abschnitt führe ich dich durch einige erweiterte Funktionen, die für Data Engineers essenziell sind.

Erweiterungen

DuckDB lässt sich über Core‑ und Community‑Erweiterungen funktional erweitern. Ich nutze Extensions regelmäßig, um Cloud‑Speicher und Datenbanken anzubinden und zusätzliche Dateiformate zu verarbeiten.

Installieren kannst du Erweiterungen sowohl in Python als auch in der DuckDB‑Konsole.

Im folgenden Snippet siehst du, wie du die httpfs-Erweiterung installierst, die für die Anbindung an AWS und das Lesen von S3‑Daten benötigt wird:

import duckdb
conn = duckdb.connect()
conn.execute("""
    INSTALL httpfs;
    LOAD httpfs;
""")

Ohne angehängte .df()‑Methode an conn.execute() siehst du keine Ausgabe. Mit .df() bekommst du eine „Success“‑ oder „Error“‑Meldung.

Abfragen von Cloud‑Speicher

In der Praxis bekommst du meist Zugriff auf Daten, die du in deine Pipelines integrieren sollst. Diese liegen üblicherweise auf skalierbaren Cloud‑Plattformen wie AWS S3.

DuckDB kann S3 (und andere Plattformen) direkt anbinden.

Wie du die Erweiterung (httpfs) installierst, habe ich gezeigt. Jetzt musst du sie nur noch konfigurieren. Für AWS brauchst du Region, Access Key und Secret Access Key.

Wenn du alles parat hast, führe folgenden Befehl über Python aus:

conn.execute("""    CREATE SECRET aws_s3_secret (
        TYPE S3,
        KEY_ID '<your-access-key>',
        SECRET '<your-secret-key>',
        REGION '<your-region>'
    );
""")

Mein S3‑Bucket enthält zwei Dateien aus dem New‑York‑Taxi‑Datensatz:

S3 bucket contents

S3 bucket contents

Du musst die Dateien nicht herunterladen – du kannst sie direkt aus S3 scannen:

import duckdb
conn = duckdb.connect()
aws_count = conn.execute("""
    SELECT COUNT(*)
    FROM PARQUET_SCAN('s3://<your-bucket-name>/*.parquet');
""").df()
aws_count

DuckDB S3 fetch results

DuckDB S3 fetch results

Du liest richtig: Es brauchte nur 4 Sekunden für knapp 900 MB Daten in einem S3‑Bucket.

Parallele Verarbeitung

Parallelisierung ist in DuckDB standardmäßig aktiviert und basiert auf Row Groups – horizontalen Datenpartitionen, wie man sie typischerweise in Parquet findet. Eine Row Group hat maximal 122.880 Zeilen. 

Parallelität in DuckDB startet also ab mehr als 122.880 Zeilen.

DuckDB startet dann automatisch weitere Threads. Standardmäßig entspricht deren Anzahl der Zahl der CPU‑Kerne. Du kannst aber auch manuell mit der Thread‑Zahl spielen.

Ich zeige dir gleich, wie das geht und welchen Effekt unterschiedliche Thread‑Zahlen auf eine identische Aufgabe haben.

Die aktuelle Thread‑Zahl siehst du mit:

SELECT current_setting('threads') AS threads;

Current number of threads used by DuckDB

The current number of threads used by DuckDB

Dasselbe kannst du auch aus Python heraus abfragen.

Zum Ändern der Thread‑Zahl nutzt du den Befehl SET threads = N

Im folgenden Code implementiere ich eine Python‑Funktion, die eine DuckDB‑Abfrage mit einer vorgegebenen Thread‑Zahl ausführt, in einen pandas‑DataFrame konvertiert und die Laufzeit (u. a.) zurückgibt. Der Code darunter führt sie für einen Bereich von 1 bis 12 Threads aus:

def thread_test(n_threads: int) -> dict:
    # Open the database connection and measure the start time
    time_start = datetime.now()
    conn = duckdb.connect()
    # Set the number of threads
    conn.execute(f"SET threads = {n_threads};")
    query = """
        SELECT
            ride_year || '-' || ride_month AS ride_period,
            COUNT(*) AS num_rides,
            ROUND(SUM(trip_time) / 86400, 2) AS ride_time_in_days,
            ROUND(SUM(trip_miles), 2) AS total_miles,
            ROUND(SUM(base_passenger_fare + tolls + bcf + sales_tax + congestion_surcharge + airport_fee + tips), 2) AS total_ride_cost,
            ROUND(SUM(driver_pay), 2) AS total_rider_pay
        FROM (
            SELECT
                DATE_PART('year', pickup_datetime) AS ride_year,
                DATE_PART('month', pickup_datetime) AS ride_month,
                trip_time,
                trip_miles,
                base_passenger_fare,
                tolls,
                bcf,
                sales_tax,
                congestion_surcharge,
                airport_fee,
                tips,
                driver_pay
            FROM PARQUET_SCAN("nyc-taxi-data/*.parquet")
            WHERE 
                ride_year = 2024
            AND ride_month >= 1 
            AND ride_month <= 6
        )
        GROUP BY ride_period
        ORDER BY ride_period
    """
    # Convert to DataFrame
    res = conn.sql(query).df()
    # Close the database connection and measure the finish time
    conn.close()
    time_end = datetime.now()
    return {
        "num_threads": n_threads,
        "num_rows": len(res),
        "duration_seconds": (time_end - time_start).seconds + ((time_end - time_start).microseconds) / 1000000
    }
thread_results = []
for n_threads in range(1, 13):
    thread_results.append(thread_test(n_threads=n_threads))
pd.DataFrame(thread_results)

Du solltest eine ähnliche Ausgabe sehen wie ich:

DuckDB runtime for different number of threads

DuckDB runtime for different number of threads

Grundsätzlich gilt: Je mehr Threads du einer Berechnung gibst, desto schneller ist sie fertig. Ab einem Punkt kann der Overhead neuer Threads die Gesamtlaufzeit wieder erhöhen – in diesem Bereich war das aber nicht der Fall.

Performance‑Vergleich: DuckDB vs. traditionelle Ansätze

Ich zeige dir jetzt, wie du eine Datenpipeline von Grund auf schreibst. Sie ist bewusst einfach: Daten von der Platte lesen, aggregieren und Ergebnisse schreiben. Mein Ziel ist zu zeigen, wie viel Performance du durch den Wechsel von pandas zu DuckDB gewinnen kannst – und dass dein Code nebenbei sauberer wird.

Das ist kein vollständiger Einstieg in ETL/ELT‑Prozesse, sondern ein Überblick.

Ziele der Datenpipeline

Die Pipeline, die ich dir gleich zeige, leistet Folgendes:

  • Extract: Liest mehrere Parquet‑Dateien von der Platte (rund 120 Millionen Zeilen).
  • Transform: Berechnet monatliche Kennzahlen wie Umsatz des Taxiunternehmens, Marge (Differenz aus Fahrtkosten und Fahrerlohn) und durchschnittlichen Umsatz pro Fahrt. Außerdem siehst du die zuvor besprochenen Standardstatistiken.
  • Load: Speichert die Monatsstatistiken lokal als CSV.

Nach dem Ausführen solltest du bei beiden Implementierungen identische Aggregationsergebnisse sehen (Rundungsdifferenzen ausgenommen):

Pipeline results for DuckDB and Pandas

Pipeline results for DuckDB and pandas

Zuerst gehen wir die Implementierung mit pandas durch.

Code: Python und pandas

Egal was ich versucht habe: Alle 6 Parquet‑Dateien auf einmal ließen sich nicht verarbeiten. Systemwarnungen wie diese erschienen nach wenigen Sekunden:

System memory error

System memory error

Offenbar reichen 36 GB RAM nicht, um 120 Millionen Zeilen auf einmal zu verarbeiten. Das Python‑Skript wurde beendet:

Killed Python script due to insufficient memory

Killed Python script due to insufficient memory

Zur Abhilfe musste ich die Parquet‑Dateien nacheinander verarbeiten. Hier ist der Code für die Pipeline:

import os
import pandas as pd
def calculate_monthly_stats_per_file(file_path: str) -> pd.DataFrame:
    # Read a single Parquet file
    df = pd.read_parquet(file_path)
    # Extract ride year and month
    df["ride_year"] = df["pickup_datetime"].dt.year
    df["ride_month"] = df["pickup_datetime"].dt.month
    # Remove data points that don"t fit in the time period
    df = df[(df["ride_year"] == 2024) & (df["ride_month"] >= 1) & (df["ride_month"] <= 6)]
    # Combine ride year and month
    df["ride_period"] = df["ride_year"].astype(str) + "-" + df["ride_month"].astype(str)
    # Calculate total ride cost
    df["total_ride_cost"] = (
        df["base_passenger_fare"] + df["tolls"] + df["bcf"] +
        df["sales_tax"] + df["congestion_surcharge"] + df["airport_fee"] + df["tips"]
    )
    # Aggregations
    summary = df.groupby("ride_period").agg(
        num_rides=("pickup_datetime", "count"),
        ride_time_in_days=("trip_time", lambda x: round(x.sum() / 86400, 2)),
        total_miles=("trip_miles", "sum"),
        total_ride_cost=("total_ride_cost", "sum"),
        total_rider_pay=("driver_pay", "sum")
    ).reset_index()
    # Additional attributes
    summary["total_miles_in_mil"] = summary["total_miles"] / 1000000
    summary["company_revenue"] = round(summary["total_ride_cost"] - summary["total_rider_pay"], 2)
    summary["company_margin"] = round((1 - (summary["total_rider_pay"] / summary["total_ride_cost"])) * 100, 2).astype(str) + "%"
    summary["avg_company_revenue_per_ride"] = round(summary["company_revenue"] / summary["num_rides"], 2)
    # Remove columns that aren't needed anymore
    summary.drop(["total_miles"], axis=1, inplace=True)
    return summary
def calculate_monthly_stats(file_dir: str) -> pd.DataFrame:
    # Read data from multiple Parquet files
    files = [os.path.join(file_dir, f) for f in os.listdir(file_dir) if f.endswith(".parquet")]
    
    df = pd.DataFrame()
    for file in files:
        print(file)
        file_stats = calculate_monthly_stats_per_file(file_path=file)
        # Check if df is empty
        if df.empty:
            df = file_stats
        else:
            # Concat row-wise
            df = pd.concat([df, file_stats], axis=0)
    # Sort the dataset
    df = df.sort_values(by="ride_period")
    # Change column order
    cols = ["ride_period", "num_rides", "ride_time_in_days", "total_miles_in_mil", "total_ride_cost",
            "total_rider_pay", "company_revenue", "company_margin", "avg_company_revenue_per_ride"]
    return df[cols]
if __name__ == "__main__":
    data_dir = "nyc-taxi-data"
    output_dir = "pipeline_results"
    output_file_name = "results_pandas.csv"
    # Run the pipeline
    monthly_stats = calculate_monthly_stats(file_dir=data_dir)
    # Save to CSV
    monthly_stats.to_csv(f"{output_dir}/{output_file_name}", index=False)

Die Implementierung mit DuckDB sollte ohne Memory‑Probleme durchlaufen.

Code: DuckDB

Mit dem Großteil des DuckDB‑Codes bist du bereits vertraut.

Neu ist nur das oberste SELECT, das ein paar zusätzliche Kennzahlen berechnet. Der Rest bleibt unverändert:

import duckdb
import pandas as pd
def calculate_monthly_stats(file_dir: str) -> pd.DataFrame:
    query = f"""
        SELECT
            ride_period,
            num_rides,
            ride_time_in_days,
            total_miles / 1000000 AS total_miles_in_mil,
            total_ride_cost,
            total_rider_pay,
            ROUND(total_ride_cost - total_rider_pay, 2) AS company_revenue,
            ROUND((1 - total_rider_pay / total_ride_cost) * 100, 2) || '%' AS company_margin,
            ROUND((total_ride_cost - total_rider_pay) / num_rides, 2) AS avg_company_revenue_per_ride
        FROM (
            SELECT
                ride_year || '-' || ride_month AS ride_period,
                COUNT(*) AS num_rides,
                ROUND(SUM(trip_time) / 86400, 2) AS ride_time_in_days,
                ROUND(SUM(trip_miles), 2) AS total_miles,
                ROUND(SUM(base_passenger_fare + tolls + bcf + sales_tax + congestion_surcharge + airport_fee + tips), 2) AS total_ride_cost,
                ROUND(SUM(driver_pay), 2) AS total_rider_pay
            FROM (
                SELECT
                    DATE_PART('year', pickup_datetime) AS ride_year,
                    DATE_PART('month', pickup_datetime) AS ride_month,
                    trip_time,
                    trip_miles,
                    base_passenger_fare,
                    tolls,
                    bcf,
                    sales_tax,
                    congestion_surcharge,
                    airport_fee,
                    tips,
                    driver_pay
                FROM PARQUET_SCAN("{file_dir}/*.parquet")
                WHERE 
                    ride_year = 2024
                AND ride_month >= 1 
                AND ride_month <= 6
            )
            GROUP BY ride_period
            ORDER BY ride_period
        )
    """
    conn = duckdb.connect()
    df = conn.sql(query).df()
    conn.close()
    return df
if __name__ == "__main__":
    data_dir = "nyc-taxi-data"
    output_dir = "pipeline_results"
    output_file_name = "results_duckdb.csv"
    # Run the pipeline
    monthly_stats = calculate_monthly_stats(file_dir=data_dir)
    # Save to CSV
    monthly_stats.to_csv(f"{output_dir}/{output_file_name}", index=False)

Als Nächstes zeige ich dir die Laufzeitunterschiede.

Ergebnisse des Performance‑Vergleichs

Nach je 5 Durchläufen beider Pipelines und gemittelten Werten ergaben sich diese Laufzeiten:

DuckDB vs pandas runtime comparison chart

DuckDB vs. pandas runtime comparison chart

Im Schnitt war pandas 24‑mal langsamer als DuckDB beim Laden und Verarbeiten von rund 120 Millionen Zeilen (~3 GB) über 6 Parquet‑Dateien.

Der Vergleich ist nicht ganz fair, da ich mit pandas nicht alle Parquet‑Dateien auf einmal verarbeiten konnte. Dennoch ist das Ergebnis relevant – solche Hürden wirst du im Job ebenfalls sehen. 

Wenn eine Bibliothek nicht liefert, probiere eine andere. Und die, die in den meisten Fällen am schnellsten fertig wird, ist DuckDB.

Best Practices für DuckDB in Datenpipelines

Bevor du DuckDB weiter auf eigene Faust erkundest, hier einige allgemeine und data‑engineering‑bezogene Best Practices:

  • Nutze zuerst die eingebauten Funktionen von DuckDB: Du kannst deine Python‑Funktionen per User‑Defined Functions in DuckDB einbinden und erhältst so enorme Flexibilität. Eine Grundregel der Programmierung: das Rad nicht neu erfinden. Implementiere nichts selbst, was es bereits gibt.
  • Optimiere zuerst die Dateiformate: Auch wenn DuckDB out of the box viel Performance bringt, solltest du nicht bei der Optimierung sparen. Du hast gesehen, wie viel langsamer DuckDB beim Lesen von CSV (bis zu 600‑fach) gegenüber Parquet ist. Parquet ist schneller und braucht weniger Speicher. Doppelt gewonnen. 
  • Nutze DuckDB nicht für Transaktionen: DuckDB ist eine OLAP‑Datenbank (Online Analytical Processing), also für SELECT optimiert. Vermeide Workflows mit häufigen INSERT‑ und UPDATE‑Statements. In solchen Fällen ist SQLite die bessere Wahl.
  • Nutze die native DuckDB‑Unterstützung in Jupyter Notebooks: Jupyter‑Users können DuckDB‑Queries direkt ausführen – ohne spezielle Python‑Funktionen. Das beschleunigt die Exploration und hält Notebooks aufgeräumt.
  • Behalte das Concurrency‑Modell von DuckDB im Kopf: Du kannst DuckDB so konfigurieren, dass ein Prozess lesen und schreiben kann – oder dass mehrere Prozesse lesen, aber keiner schreibt. Ersteres erlaubt Caching von Daten im RAM für schnellere Analysen. Mehrere Prozesse können theoretisch auch schreiben, dafür müsstest du jedoch Cross‑Process‑Mutex‑Locks und das Öffnen/Schließen der Datenbank selbst implementieren.
  • Nutze DuckDB auch in der Cloud: Das Projekt MotherDuck ist ein kollaboratives Data Warehouse, das die Stärken von DuckDB in die Cloud bringt und erweitert – für Data Engineers definitiv einen Blick wert.

Fazit

Mein Fazit: Wenn du als Data Engineer Datenpipelines baust und optimierst, solltest du DuckDB ausprobieren.

Du hast nichts zu verlieren und alles zu gewinnen. Das Tool ist nahezu garantiert schneller als alles, was Python von Haus aus bietet. Es läuft blitzschnell auf deinem Laptop und verarbeitet Datensätze, die sonst zu Memory‑Errors führen würden. Und es verbindet sich mit Cloud‑Speichern, wenn du Daten nicht lokal ablegen willst oder darfst.

Dennoch gilt: DuckDB ist nicht die einzige Optimierung, die du angehen solltest. Optimiere immer zuerst die Eingangsdaten. CSV zugunsten von Parquet zu streichen spart Rechenzeit und Speicher. Außerdem lohnt es sich, die Prinzipien einer modernen Datenarchitektur in deine Workflows und Pipelines zu integrieren.

DuckDB ist nur ein Werkzeug. Aber ein sehr mächtiges.

Lass dich für deine Traumrolle als Data Engineer zertifizieren

Unsere Zertifizierungsprogramme helfen dir, dich von anderen abzuheben und potenziellen Arbeitgebern zu beweisen, dass deine Fähigkeiten für den Job geeignet sind.

Hol Dir Deine Zertifizierung
Timeline mobile.png

FAQs

Ist DuckDB kostenlos nutzbar?

Ja, DuckDB ist ein Open‑Source‑Projekt. Du kannst es herunterladen, anpassen und sogar auf GitHub dazu beitragen.

Worin unterscheidet sich DuckDB von SQLite?

DuckDB ist für analytische Workloads mit komplexen SQL‑Abfragen optimiert (sprich SELECT), während SQLite sich besser für transaktionale Verarbeitung eignet (sprich INSERT und UPDATE).

Ist DuckDB eine NoSQL‑Datenbank?

Nein, DuckDB ist ein In‑Process SQL‑OLAP‑Datenbankmanagementsystem (Online Analytical Processing).

Ist DuckDB schneller als pandas?

In nahezu allen Szenarien ist DuckDB schneller als pandas – häufig um Größenordnungen. Außerdem kannst du mit Datensätzen arbeiten, bei denen pandas Speicherfehler wirft.

Verwendet DuckDB einen speziellen SQL‑Dialekt?

Nein, mit grundlegenden SQL‑Kenntnissen fühlst du dich sofort zuhause. DuckDB orientiert sich stark am PostgreSQL‑Dialekt, aber selbst wenn du andere Datenbanken gewohnt bist, gibt es praktisch keine Lernkurve.


Dario Radečić's photo
Author
Dario Radečić
LinkedIn
Senior Data Scientist mit Sitz in Kroatien. Top Tech Writer mit über 700 veröffentlichten Artikeln, die mehr als 10 Millionen Mal aufgerufen wurden. Buchautor von Machine Learning Automation with TPOT.
Themen
Datentechnik
Python

Vertiefe dein Data‑Engineering‑Wissen mit diesen Kursen!

Kurs

Grundlagen von Data Engineering

2 Std.
372.4K
Hier lernst du, wie Data Engineers die Grundlagen für Data Science schaffen – ganz ohne Programmieren.
Details anzeigenRight Arrow
Kurs Starten
Mehr anzeigenRight Arrow