Vai al contenuto principale

DuckDB per Data Engineer: accelera le tue pipeline dati di 10 volte (e oltre)

DuckDB è un potente motore analitico che vive sul tuo laptop. Puoi usarlo per velocizzare lettura ed elaborazione dei dati e ridurre i tempi di esecuzione delle pipeline da minuti a secondi. Segui questa guida pratica per imparare come.
Aggiornato 29 set 2026  · 13 min leggi

Scopri con l'IA

ChatGPTClaudePerplexity

Quante volte un manager ti ha chiesto quanto impiega la tua pipeline a terminare e ti sei sentito in imbarazzo a rispondere?

Non sei il solo. La comodità di Python ha un prezzo. Di solito lo si paga in termini di velocità di esecuzione. Le pipeline sono veloci da scrivere ma difficili da scalare e spesso richiedono sempre più tempo man mano che crescono i requisiti del progetto.

Ma forse non è il tuo codice il collo di bottiglia. Forse non c’è altra performance da spremere da pandas. Forse passare a un motore di elaborazione dati diverso può ridurre i tempi di esecuzione da ore a minuti.

Ed è qui che entra in gioco DuckDB.

In questo articolo ti spiegherò esattamente cos’è DuckDB e perché è importante per i data engineer. Imparerai a usarlo con esempi pratici e vedrai quanto è più veloce rispetto alle librerie di elaborazione dati più popolari di Python. 

Cos’è DuckDB e perché potrebbe essere il prossimo grande strumento per i data engineer?

DuckDB è un DBMS OLAP relazionale open-source, embedded e in-process.

In parole semplici, pensalo come un database analitico colonnare che gira in memoria. Essendo un database analitico, è ottimizzato per le istruzioni SELECT piuttosto che per INSERT e UPDATE. È come SQLite, ma al contrario.

DuckDB esiste da circa 5 anni, ma la prima release “stabile” è stata annunciata a giugno 2024 - 3 mesi fa al momento della scrittura. Ma non farti ingannare dalla novità: DuckDB è stato ampiamente provato e testato, e viene quasi sempre consigliato quando la velocità è fondamentale.

Perché scegliere DuckDB per le tue pipeline dati

Se sei un data engineer, ecco alcuni vantaggi concreti nell’usare DuckDB nelle tue pipeline:

  • È veloce: pensa a ordini di grandezza più veloce delle librerie dataframe predefinite di Python. Nella maggior parte dei casi è persino più rapido di librerie ottimizzate per la velocità (ad es., polars).
  • È open-source: l’intero codice sorgente è disponibile in un repo pubblico su GitHub. Puoi usarlo, modificarlo e persino contribuire al progetto.
  • Rimanda il momento di passare al cloud: il cloud può essere costoso, ma il prezzo spesso è giustificato quando la velocità di elaborazione è critica. Con DuckDB puoi analizzare centinaia di milioni di righe sul tuo laptop.
  • È facile da imparare: iniziare con DuckDB è semplice. Probabilmente dovrai modificare al minimo le pipeline esistenti, poiché DuckDB si integra bene con le librerie di elaborazione dati di Python.
  • Si integra bene con le piattaforme di storage cloud: ad esempio, puoi scrivere una query SQL DuckDB che legge i dati direttamente da AWS S3. Non serve scaricare i dati prima.

Per queste ragioni (e molte altre che vedremo), penso che DuckDB sia il prossimo grande salto per i data engineer.

Come iniziare con DuckDB

Come dicevo, DuckDB gira sul tuo laptop. La prima cosa da fare è installarlo. 

Ti mostrerò i passaggi d’installazione per macOS, ma le istruzioni per Windows e Linux sono ben spiegate e facili da seguire.

Passo 1: Installa DuckDB

Se hai un Mac, il modo più semplice per installare DuckDB è con Homebrew. Esegui il seguente comando da shell:

brew install duckdb

In pochi secondi vedrai un output simile al seguente:

Installazione di DuckDB con Homebrew

Installazione di DuckDB con Homebrew

Una volta installato, puoi entrare nella shell di DuckDB con il comando seguente:

duckdb

Shell DuckDB

Shell DuckDB

Da qui, il mondo è tuo!

Puoi scrivere ed eseguire query SQL direttamente dalla console. Per esempio, ho scaricato i primi 6 mesi di NYC Taxi Data per il 2024. Se hai fatto lo stesso, usa il seguente snippet per mostrare il numero di righe di un singolo file Parquet:

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

Conteggio righe di un singolo file Parquet

Conteggio righe di un singolo file Parquet

Già — sono quasi 20 milioni di righe da un singolo file. Ne ho scaricati 24.

Probabilmente non vuoi usare DuckDB dal terminale, quindi ora ti mostro come collegarlo a Python.

Passo 2: Collega DuckDB al tuo workflow in Python

L’obiettivo di questa sezione è mostrarti come configurare ed eseguire DuckDB nel linguaggio più usato nel data engineering: Python.

Infatti, DataCamp offre un career track completo in data engineering in Python.

Python ha una libreria specifica per DuckDB che devi installare per prima. Esegui il seguente comando dal terminale, preferibilmente in un ambiente virtuale:

pip install duckdb

Ora crea un nuovo file Python e incolla il seguente codice:

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)

In breve, lo snippet contiene una funzione che recupera il numero di righe da un singolo file, dato una connessione valida a DuckDB e un percorso file.

Otterrai il seguente risultato quasi istantaneamente dopo l’esecuzione dello script:

Connessione DuckDB e Python

Connessione DuckDB e Python

Tutto qui! 

A breve ti mostro come fare cose incredibili con Python e DuckDB, a velocità fulminea!

DuckDB in azione: come accelerare le pipeline dati

Per riferimento, eseguirò il codice su una porzione 2024 (gennaio - giugno) del dataset NYC Taxi — specificamente per i veicoli a noleggio ad alto volume. Sono 6 file Parquet, per circa 3GB di spazio su disco. Come hardware, uso un MacBook Pro 16” M3 Pro con 12 core CPU e 36GB di memoria unificata.

I tuoi risultati possono variare, ma dovresti ottenere numeri simili.

Leggi dataset enormi in pochissimo tempo

Ho convertito questi 6 file in due formati aggiuntivi: CSV e JSON. Puoi vedere quanto spazio su disco occupa ciascuno:

Confronto dimensione dataset

Confronto dimensione dataset

Totale: 2,96GB per Parquet, 19,31GB per CSV e 66,04GB per JSON.

Differenza enorme! Se non ricordi altro da questo articolo, ricorda almeno di usare sempre il formato Parquet quando lavori con file molto grandi. Ti farà risparmiare tempo di elaborazione e spazio su disco.

DuckDB ha funzioni comode per leggere più file dello stesso formato in una volta sola (usando un pattern glob). Le userò per leggere tutti e sei i file, ottenere il conteggio righe e confrontare i tempi di esecuzione:

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)

I risultati parlano chiaro: c’è un solo vincitore netto:

Confronto tempi di lettura file

Confronto tempi di lettura file

Grazie a DuckDB, tutti e tre sono veloci e arrivano allo stesso risultato, ma è evidente che Parquet vince per un fattore 600 rispetto a CSV e per un fattore 1200 rispetto a JSON.

Per questo motivo, per il resto dell’articolo userò solo Parquet.

Interroga i dati con SQL

DuckDB ti permette di aggregare i dati tramite SQL.

L’obiettivo di questa sezione è approfondire l’idea. Più precisamente, ti mostrerò come calcolare statistiche riepilogative a livello mensile che includono numero di corse, tempo di corsa, distanza, costo e compenso del driver — tutto da 120 milioni di righe.

La parte migliore: ci vorranno solo due secondi!

Ecco il codice che ho usato in uno script Python per aggregare i dati e stamparne i risultati:

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))

L’output contiene i risultati dell’aggregazione, il tipo della variabile results e il tempo totale di esecuzione:

Aggregazione statistiche mensili

Aggregazione statistiche mensili

Pensaci: sono 3GB di dati per oltre 120 milioni di righe, tutto aggregato in 2 secondi su un laptop!

L’unico problema è che il tipo di variabile è poco utilizzabile nel lungo periodo. Per fortuna c’è una soluzione semplice.

Integrazione con pandas e DataFrame

Il vantaggio di usare DuckDB non è solo la velocità: si integra anche molto bene con la tua libreria di dataframe preferita, pandas.

Se hai un result set temporaneo come quello mostrato sopra, ti basta chiamare il metodo .df() per convertirlo in un DataFrame pandas:

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

Conversione da DuckDB a pandas

Conversione da DuckDB a pandas

Allo stesso modo, puoi usare DuckDB per eseguire calcoli su DataFrame pandas che sono già caricati in memoria.

Il trucco è referenziare il nome della variabile dopo la keyword FROM in una query SQL DuckDB. Ecco un esempio:

# 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

Query DuckDB su un DataFrame Pandas esistente

Query DuckDB su un DataFrame pandas esistente

In conclusione, puoi evitare del tutto pandas o eseguire aggregazioni più velocemente su DataFrame pandas esistenti.

A seguire, ti mostro un paio di funzionalità avanzate di DuckDB.

DuckDB avanzato per data engineer

DuckDB offre molto più di quanto sembri a prima vista.

In questa sezione ti guiderò attraverso alcune funzionalità avanzate di DuckDB essenziali per i data engineer.

Estensioni

DuckDB ti consente di estenderne le funzionalità native tramite estensioni core e community extensions. Uso spesso le estensioni per connettermi a sistemi di storage e database nel cloud e per lavorare con formati di file aggiuntivi.

Puoi installare le estensioni sia dalla console Python che da quella di DuckDB.

Nel seguente snippet ti mostro come installare l’estensione httpfs necessaria per connettersi ad AWS e leggere dati da S3:

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

Non vedrai alcun output se non concateni il metodo .df() a conn.execute(). Se lo fai, vedrai un messaggio di “Success” o “Error”.

Query su storage cloud

Spesso la tua azienda ti concederà l’accesso ai dati da includere nelle pipeline. Di solito sono archiviati su piattaforme cloud scalabili, come AWS S3.

DuckDB ti consente di connetterti direttamente a S3 (e ad altre piattaforme).

Ti ho già mostrato come installare l’estensione (httpfs); ora resta solo da configurarla. AWS richiede informazioni su regione, access key e secret access key.

Supponendo che tu abbia tutto, esegui il seguente comando tramite Python:

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

Il mio bucket S3 contiene due file del dataset New York Taxi:

Contenuti del bucket S3

Contenuti del bucket S3

Non serve scaricare i file, perché puoi scansionarli direttamente da S3:

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

Risultati fetch S3 con DuckDB

Risultati fetch S3 con DuckDB

Hai letto bene: ci sono voluti solo 4 secondi per passare in rassegna ~900MB di dati in un bucket S3.

Elaborazione parallela

Per quanto riguarda la parallelizzazione, DuckDB la implementa di default basandosi sui row group: partizioni orizzontali tipiche dei file Parquet. Un singolo row group può avere al massimo 122.880 righe. 

Il parallelismo in DuckDB parte quando lavori con più di 122.880 righe.

DuckDB avvierà automaticamente nuovi thread quando ciò accade. Per impostazione predefinita, il numero di thread è uguale al numero di core CPU. Ma nulla ti impedisce di regolare manualmente i thread.

In questa sezione ti mostro come fare e l’impatto concreto di un numero diverso di thread sullo stesso carico di lavoro.

Per visualizzare il numero attuale di thread, esegui quanto segue:

SELECT current_setting('threads') AS threads;

Numero corrente di thread usati da DuckDB

Numero corrente di thread usati da DuckDB

Puoi ottenere lo stesso risultato anche tramite Python.

Per cambiare il numero di thread, usa il comando SET threads = N. 

Nel seguente snippet, ti mostro come implementare una funzione Python che esegue una query DuckDB con un certo numero di thread, la converte in un DataFrame pandas e ritorna il tempo di esecuzione (tra le altre cose). Il codice sotto la funzione la esegue con un intervallo di thread [1, 12]:

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)

Dovresti vedere un output simile al mio:

Tempi di DuckDB con numero di thread diversi

Tempi di DuckDB con numero di thread diversi

In generale, più thread assegni a un’elaborazione, più velocemente terminerà. Potrebbe esistere un punto in cui il overhead di creare un nuovo thread porta ad aumentare il tempo totale, ma non l’ho riscontrato in questo intervallo.

Confronto prestazionale: DuckDB vs approcci tradizionali

In questa sezione ti mostro come scrivere una pipeline dati da zero. Sarà relativamente semplice: lettura dei dati da disco, aggregazioni e scrittura dei risultati. Il mio obiettivo è mostrarti quante prestazioni puoi guadagnare passando da pandas a DuckDB. E non solo: il tuo codice risulterà anche più pulito.

Questa non è un’introduzione esaustiva ai processi ETL/ELT, ma una panoramica ad alto livello.

Obiettivi della pipeline dati

La pipeline che sto per mostrarti realizza quanto segue:

  • Extract: legge più file Parquet da disco (circa 120 milioni di righe).
  • Transform: calcola statistiche mensili riepilogative, come i ricavi della compagnia di taxi, il margine di ricavo (differenza tra costo corsa e compenso driver) e il ricavo medio per corsa. Vedrai anche statistiche più comuni discusse in precedenza.
  • Load: salva localmente le statistiche mensili in un file CSV.

Dopo aver eseguito il codice, dovresti ottenere esattamente gli stessi risultati di aggregazione per entrambe le implementazioni della pipeline (a parte qualche differenza di arrotondamento):

Risultati della pipeline per DuckDB e Pandas

Risultati della pipeline per DuckDB e pandas

Per prima cosa, vediamo l’implementazione della pipeline in pandas.

Codice: Python e pandas

Qualunque cosa abbia provato, non sono riuscito a processare tutti e 6 i file Parquet insieme. In pochi secondi comparivano avvisi di sistema come questo:

Errore di memoria di sistema

Errore di memoria di sistema

Sembra che 36GB di memoria non bastino per processare 120 milioni di righe in una volta sola. Di conseguenza, lo script Python è stato terminato:

Script Python terminato per memoria insufficiente

Script Python terminato per memoria insufficiente

Per rimediare, ho dovuto processare i file Parquet in sequenza. Ecco il codice che ho usato per la 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)

L’implementazione della pipeline in DuckDB dovrebbe terminare senza problemi di memoria.

Codice: DuckDB

Gran parte del codice DuckDB ti è già familiare.

L’unica novità è il SELECT più esterno, che calcola alcune statistiche aggiuntive. Il resto è invariato:

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)

A seguire, i differenziali di tempo di esecuzione.

Risultati del confronto prestazionale

Dopo aver eseguito entrambe le pipeline 5 volte e fatto la media, questi sono i tempi di esecuzione che ho ottenuto:

Grafico di confronto tempi tra DuckDB e pandas

Grafico di confronto tempi: DuckDB vs. pandas

In media, pandas è risultato 24 volte più lento di DuckDB durante il caricamento e l’elaborazione di circa 120 milioni di righe (~3GB), distribuite su 6 file Parquet.

Il confronto non è del tutto equo, dato che non sono riuscito a processare tutti i file Parquet in una volta con pandas. Ciononostante, i risultati restano rilevanti, perché incontrerai le stesse difficoltà anche al lavoro. 

Se una libreria non fa ciò di cui hai bisogno, provale un’altra. E quella che probabilmente finirà per prima, nella maggior parte dei casi, è DuckDB.

Best practice per usare DuckDB nelle pipeline dati

Prima di lasciarti esplorare DuckDB in autonomia, voglio condividere un paio di best practice generali e legate al data engineering:

  • Prova sempre prima le funzioni integrate di DuckDB: puoi portare le tue funzioni Python in DuckDB con le funzioni definite dall’utente, che portano la flessibilità di DuckDB a un livello del tutto diverso. Una best practice generale nella programmazione è non reinventare la ruota. In altre parole, non vuoi implementare da zero una funzionalità che esiste già.
  • Ottimizza prima i formati dei file: anche se DuckDB offre grandi vantaggi prestazionali out of the box, non significa che tu debba fermarti lì con le ottimizzazioni. Hai visto quanto più lento è DuckDB nella lettura di file CSV (fino a 600 volte) rispetto ai Parquet. Questi ultimi saranno sempre più veloci da leggere e occuperanno meno spazio su disco. Doppio vantaggio. 
  • Non usare DuckDB per scopi transazionali: DuckDB è un database OLAP (Online Analytical Processing), il che significa che è ottimizzato per le SELECT. Evita di usarlo in workflow che si basano su INSERT e UPDATE frequenti. In quel caso, usa SQLite al posto.
  • Sfrutta il supporto nativo di DuckDB in Jupyter Notebook: gli utenti di Jupyter Lab/Notebook puòno eseguire query DuckDB direttamente, senza la necessità di usare funzioni Python specifiche. È un ottimo modo per esplorare i dati più velocemente e mantenere i notebook ordinati.
  • Ricorda sempre come DuckDB gestisce la concorrenza: puoi configurare DuckDB in modo che un processo possa leggere e scrivere nel database, oppure in modo che più processi possano leggerlo ma nessuno scriverci. Il primo consente la messa in cache dei dati in RAM per query analitiche più veloci. In teoria più processi possono scrivere nello stesso file di database, ma per farlo dovresti implementare manualmente la logica per i lock tra processi e l’apertura/chiusura del database.
  • Puoi usare DuckDB nel cloud: il progetto MotherDuck è un data warehouse collaborativo che porta e amplia la potenza di DuckDB nel cloud. Potrebbe valere la pena esplorarlo per i data engineer.

Conclusioni

In conclusione, se sei un data engineer che si occupa di costruire e ottimizzare pipeline dati, penso che dovresti provare DuckDB.

Non hai nulla da perdere e tutto da guadagnare. Questo strumento è quasi garantito più veloce di qualsiasi cosa offra Python. È fulmineo sul tuo laptop e ti permette di gestire dataset che altrimenti genererebbero errori di memoria. Può persino connettersi a piattaforme di storage cloud quando non vuoi o non puoi archiviare dati in locale.

Detto ciò, DuckDB non dovrebbe essere l’unica ottimizzazione che porti sul tavolo. Dovresti sempre puntare a ottimizzare i dati in ingresso nel sistema. Per esempio, abbandonare i CSV a favore dei Parquet ti farà risparmiare tempo di calcolo e storage. Un’altra ottimizzazione da considerare è implementare i principi della moderna data architecture nei tuoi workflow e pipeline.

DuckDB è solo uno strumento. Ma uno molto potente.

FAQs

DuckDB è gratuito?

Sì, DuckDB è un progetto open-source. Puoi scaricarlo, modificarlo e persino contribuire tramite GitHub.

In cosa DuckDB è diverso da SQLite?

DuckDB è ottimizzato per carichi analitici tramite query SQL complesse (pensa alle istruzioni SELECT), mentre SQLite è più adatto all’elaborazione transazionale (pensa a INSERT e UPDATE).

DuckDB è un database NoSQL?

No, DuckDB è un sistema di gestione di database SQL OLAP (Online Analytical Processing) in-process.

DuckDB è più veloce di pandas?

Nella quasi totalità degli scenari, DuckDB sarà più veloce di pandas, spesso di un ordine di grandezza (almeno). Ti permetterà anche di lavorare con dataset che in pandas genererebbero errori di memoria.

DuckDB usa un dialetto SQL particolare?

No, ti troverai subito a tuo agio se hai conoscenze base di SQL. DuckDB segue da vicino le convenzioni del dialetto PostgreSQL, ma anche se sei abituato a un altro vendor, la curva di apprendimento è praticamente nulla.


Dario Radečić's photo
Author
Dario Radečić
LinkedIn
Senior Data Scientist con base in Croazia. Top Tech Writer con oltre 700 articoli pubblicati, per più di 10 milioni di visualizzazioni. Autore del libro Machine Learning Automation with TPOT.
Argomenti
Data Engineering
Python

Approfondisci il data engineering con questi corsi!

Corso

Capire il Data Engineering

2 ore
377.9K
Scopri come i data engineer pongono le basi per rendere possibile la data science, senza necessità di programmare.
Vedi i dettagliRight Arrow
Inizia Il Corso
Mostra altroRight Arrow
Correlato

blog

Che cos'è Snowflake? Guida per principianti alla piattaforma dati cloud

Esplora le basi di Snowflake, la piattaforma dati cloud. Scopri la sua architettura, le sue funzionalità e come integrarla nelle tue pipeline di dati.
Tim Lu's photo

Tim Lu

12 min

blog

I 15 migliori server MCP remoti che ogni AI builder dovrebbe conoscere nel 2026

Scopri i 15 migliori server MCP remoti che stanno trasformando lo sviluppo AI nel 2026. Scopri come migliorano automazione, ragionamento, sicurezza e velocità dei workflow.
Abid Ali Awan's photo

Abid Ali Awan

15 min

blog

Tokenizzazione nel NLP: come funziona, sfide e casi d'uso

Guida al preprocessing NLP nel machine learning. Copriamo spaCy, i transformer di Hugging Face e come funziona la tokenizzazione in casi d'uso reali.
Abid Ali Awan's photo

Abid Ali Awan

10 min

Mostra AltroMostra Altro