[!NOTE] Traducción automática Este artículo se tradujo automáticamente a partir de la versión original en inglés.

Comparación de motores modernos de procesamiento de datos: Polars, DataFusion, Daft, Ray Data, Pandas y Spark

Durante años, Pandas se encargó del trabajo con tablas en memoria, mientras que Apache Spark gestionaba el tipo distribuido. Esa división funcionó bien cuando los datos estaban estructurados.

Los modelos modernos pipelines también son capaces de procesar imágenes, audio y vídeo. En estos tipos de trabajos, la decodificación de CPU puede ralentizar gravemente las operaciones de inferencia de GPU, mientras que la recolección de basura del JVM y el Global Interpreter Lock de Python limitan el rendimiento general. Los nuevos motores emplean Rust y Apache Arrow para reducir dichos costes.

Para comparar las opciones, realicé pruebas de rendimiento con Polars, DataFusion, Daft, Ray Data y Spark en dos casos reales datasets. Los viajes en taxi por Nueva York constituyen datos tabulares, mientras que las imágenes de Food-101 representan un pipeline multimodal.

El repositorio engine-comparison-demo Contiene el código. Ejécutelo en su propio hardware, ya que el rendimiento del motor depende de la máquina, dataset, y de la carga de trabajo.

TL;DR: Comience definiendo la estructura del trabajo, y no buscando un benchmark ganador. Polars es una opción sólida por defecto para los DataFrame locales pipelines; DataFusion resulta adecuado para los equipos embedding que necesitan un motor de consultas; Ray Data y Daft están diseñados para trabajos mixtos de CPU/GPU pipelines; mientras que Spark sigue siendo una opción madura para tareas de SQL distribuido y ETL. Las mejoras de rendimiento mostradas en esta guía son evidencias específicas de ciertos trabajos, y no promesas aplicables en cualquier contexto. Ejecute nuevamente el benchmarks correspondiente con sus propios datos y hardware antes de tomar una decisión.


Tipos de cargas de trabajo de procesamiento de datos

Dado que los motores están especializados, la primera pregunta a responder es qué tipo de carga de trabajo se tiene en realidad. La mayor distinción existe entre trabajos estructurados y multimodales.

Dos mundos de los datos: estructurados frente a multimodales

Datos estructurados o tabulares implican operaciones de filtrado, agregación y unión. Por lo general, están delimitados por CPU y suelen caber en la memoria. Según Polars, aproximadamente El 90 % de las consultas procesan menos de 1 TB., Así, muchos de ellos pueden ejecutarse en una sola máquina moderna en lugar de en un clúster.

Multimodal AI permite procesar imágenes, audio o vídeo. La inferencia puede ejecutarse en un GPU, mientras que la decodificación CPU, las lecturas remotas o los límites de agrupamiento restringen el pipeline. Esta relación varía en función de la instancia y de los operadores, por lo que es necesario medir el nivel de utilización a lo largo de todo el flujo, en lugar de dimensionar el GPU de forma aislada.

Los motores más recientes están orientados a distintas áreas de este campo. La ejecución nativa permite reducir la sobrecarga generada por Python, las interfaces compatibles con Arrow facilitan un intercambio de datos más eficiente, y la ejecución en flujo permite gestionar la memoria en sistemas datasets de tamaño superior al RAM. Ninguno de estos beneficios se aplica automáticamente para todos los operadores o procesos de conversión.


Parte 1: procesamiento en nodo único

Pandas prefiere un modelo de programación ágil y en memoria. Muchas operaciones comunes generan resultados intermedios, y la ejecución paralela no se coordina mediante un optimizador de consultas. Este compromiso permite que el código exploratorio sea sencillo, pero puede resultar costoso en análisis de grandes volúmenes de datos. En Polars’s PDS-H benchmark Con un factor de escala de 10, Pandas tardó aproximadamente 365 segundos en procesar el conjunto de datos, mientras que el motor de streaming de Polars necesitó solo 3,89 segundos. Este resultado de casi 94 veces menor refleja directamente las características y la configuración de benchmark; no constituye un factor de conversión genérico aplicable a todas las operaciones de transformación desde Pandas hacia Polars.

Execución ávida vs. ejecución perezosa

Polars para datos tabulares locales pipelines

Polars Se trata de una opción predeterminada práctica cuando un DataFrame local pipeline ha superado las limitaciones del modo de ejecución ágil y basado en un único proceso. Su modalidad perezosa API genera un plan de consulta antes de ejecutarla, lo que permite aplicar optimizaciones como el predicate pushdown y la reducción de datos mediante proyección. A continuación, el motor puede ejecutar los operadores de forma paralela y, cuando es posible, en lotes en tiempo real.

La brecha se amplía a medida que aumentan los datos. En factor de escala 100 (~100 GB), El motor de procesamiento en tiempo real de Polars completó la tarea en 23,94 segundos, frente a los 152,27 segundos que necesitaba su propio motor en memoria; es decir, aproximadamente 6 veces más rápido, incluso con datos de tamaño superior al de la RAM.

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

La diferencia clave radica en el modelo de ejecución. En la consulta perezosa presentada anteriormente, Polars permite trasladar el filtrado y la selección de columnas al proceso de escaneo de Parquet. Esto permite omitir grupos de filas y evitar la lectura de columnas no utilizadas. Un caso típico de uso ansioso con Pandas pipeline no dispone de un plan de consulta completo que se pueda optimizar, aunque un uso cuidadoso de los filtros de Parquet, las columnas seleccionadas y otros backends alternativos puede ayudar a reducir en parte esta desventaja.

DataFusion para motores de consulta embebidos

Donde Polars es una biblioteca que se utiliza directamente, DataFusion es el motor sobre el que se construyen otros motores. Es el que proporciona la potencia necesaria. InfluxDB 3.0, GreptimeDB, y los de Apple Acelerador Comet Spark.

En noviembre de 2024 Ejecución de ClickBench publicada por el proyecto DataFusion, DataFusion lideró las configuraciones de Parquet de un solo nodo que se probaron. Más tarde, el equipo de Embucket publicó una ejecución de la escala TPC-H con factor 1000 en un único nodo de gran tamaño. Estos resultados muestran el nivel de escalabilidad que se puede lograr bajo condiciones específicas; no demuestran que toda carga de trabajo de 1 TB deba evitarse en un clúster.

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

La fortaleza de DataFusion radica en su modularidad. La extensión APIs abarca catálogos personalizados, proveedores de tablas, reglas de optimizador y planes de ejecución; por ello aparece cada vez que alguien está desarrollando una plataforma de datos personalizada o embedding un motor de consultas para su propio producto.

Inadecuado para datos multimodales

Insensato Incorpora imágenes, audio, vídeo y embeddings dentro del flujo de trabajo del DataFrame. Ofrece expresiones nativas para operaciones como la decodificación de imágenes y la descarga desde URLs, por lo que los procesos habituales de preprocesamiento no necesitan recurrir a bucles en Python por fila. Dicha integración es la razón principal por la cual se elige esta herramienta en lugar de un motor tabular genérico.

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

El API resultará familiar si ya conoces Pandas, pero el motor es la misma pila formada por Rust y Arrow que se utiliza en Polars y DataFusion. Daft demuestra su ventaja cuando las “filas” de tu DataFrame son imágenes, PDF o tensores.

Nodo único benchmark

Ejecuté los cuatro motores, además del Rust nativo a través de Polars-rs, sobre aproximadamente 41 millones. Trayectos de taxis amarillos en Nueva York para todo el año 2024. Los resultados completos se encuentran en el repositorio de demostración:

Benchmark Resultados: Rendimiento en nodo único

Con este tamaño (~660 MB en formato Parquet), los cuatro motores más recientes completan la tarea rápidamente. La cifra de 94 veces mayor que indica la velocidad de conversión de Polars a Pandas proviene de PDS-H; mi conjunto de datos de taxis con 41 millones de filas arrojó una diferencia menor. Polars, DataFusion, Daft y Polars-rs se situaron dentro del mismo rango aproximado, siendo DataFusion el que registró el tiempo total más bajo en esta prueba. Se trata de un resultado útil para esta consulta, pero no constituye una clasificación fiable: API la adecuación del modelo y las mediciones repetidas con datos representativos deberían ser los criterios para decidir entre motores tan similares entre sí.


Parte 2: datos multimodales

El ETL tabular suele reducir el volumen de datos: se aplican filtros y operaciones de agregación para escribir menos información de la que se lee. En cambio, los procesos multimodales AI pipelines hacen lo contrario. Una única ruta de documento puede dividirse en decenas de fragmentos de texto y vectores embedding.

En un PySpark convencional pipeline, las imágenes y el audio suelen introducirse como datos binarios y pasar a las bibliotecas de Python para su decodificación o transformación. Cruzar la frontera entre JVM y Python y serializar los resultados puede representar una gran parte del tiempo total de ejecución. Spark permite utilizar rutas basadas en Arrow, UDFs vectorizados e integraciones con aceleradores, pero dichas opciones requieren un diseño y mediciones explícitos.

Ejecución en tuberías y utilización de GPU

Spark programa los trabajos en fases separadas por límites de shuffle. Una implementación sencilla podría organizar las operaciones de descarga, decodificación e inferencia en una secuencia que deja sin utilizar capacidad ya sea para CPU o GPU. Spark cuenta con mecanismos de programación de recursos y plugins basados en GPU, por lo que el tiempo de inactividad no es algo inevitable; lo importante es que la superposición de tareas y la contrapresión no son propiedades automáticas de un trabajo ordinario con DataFrame de PySpark.

Modelo de ejecución en tubería

Ejecución en tubería es la alternativa. En lugar de ejecutar las fases una tras otra, el motor superpone las operaciones de E/S, CPU y la GPU inferencia, de modo que el único componente que permanece en espera en un momento dado es el más lento.

Procesamiento de imágenes benchmark

Realicé pruebas de rendimiento en el procesamiento de imágenes con 500 fotografías reales del Introducción a los alimentos dataset. Los resultados obtenidos de repositorio de demostración:

Benchmark Resultados: Rendimiento multimodal

Polars y DataFusion no se incluyen porque esta prueba se centra en operaciones sobre imágenes nativas y no en expresiones tabulares. En esta ejecución con 500 imágenes, la ruta de archivo utilizada por Daft fue 3,6 veces más rápida que el estándar basado en Pandas + Pillow, mientras que la implementación en Rust lo fue 4,2 veces más. La muestra resulta útil para mostrar la sobrecarga asociada a la orquestación, pero es demasiado pequeña como para poder predecir por sí sola el rendimiento en entornos de producción.


Parte 3: procesamiento distribuido

Cuando una sola máquina ya no es suficiente, es necesario decidir cómo distribuir el trabajo. Es en este punto donde las diferencias arquitectónicas entre los motores comienzan a ser realmente relevantes.

Apache Spark

Spark constituye una opción madura para procesos ETL a gran escala que involucran grandes volúmenes de datos tabulares y uniones con mucho movimiento de datos, así como para aquellas organizaciones que ya utilizan su ecosistema. Su capacidad de recuperación basada en el historial de operaciones y su amplio soporte en distintas plataformas resultan cruciales cuando la fiabilidad y la familiaridad operativa son más importantes que la velocidad local. Los entornos híbridos de CPU/GPU pipelines exigen una configuración más cuidadosa de los recursos y un diseño más detallado de pipeline en comparación con el enfoque basado en SQL, área en la que Ray Data y Daft ofrecen una abstracción más enfocada.

Desde 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 para cómputo heterogéneo

Datos de Ray fue diseñado para trabajar con cargas de trabajo AI. En lugar de las barreras de etapa de Spark, utiliza un modelo de streaming Eso mantiene alimentado a GPUs. La característica que hace posible su funcionamiento es la programación mixta de recursos: se puede especificar que un agente desea “1 GPU, 4 CPUs”, mientras que otro solo necesita CPUs, y Ray se encarga de calcular el resto.

Amazon informó Más de 120 millones de dólares en ahorros anuales.

Desde 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/")

Un detalle importante a tener en cuenta: a medida que se añaden más CPUs por GPU, Ray Data muestra un crecimiento escalable, mientras que otros motores alcanzan un límite de rendimiento. En El benchmarks de Anyscale, Al pasar de una relación de 4:1 a una de 32:1 CPU-a-GPU, se logró una aceleración de 3 veces en la inferencia de imágenes, ya que los alimentadores de datos CPU finalmente pudieron mantener el mismo ritmo que los procesadores GPU.

Daft distribuido

Daft escala horizontalmente a través de su Motor de flotilla: Un trabajador Swordfish por nodo, con Flotilla encargada de la planificación a nivel de clúster. Swordfish gestiona la ejecución local en Rust y las operaciones de E/S pipelines mediante flujos de datos por lotes pequeños, de modo que cada nodo permanece ocupado sin tener que esperar a la siguiente etapa.

In benchmarks publicado por Daft, Flotilla funcionó de 2 a 18 veces más rápido que las implementaciones de Spark probadas en cuatro cargas de trabajo multimodales. La mayor diferencia se observó en un caso de detección de objetos en vídeo. Se debe considerar este resultado como evidencia de que el diseño de la ejecución es crucial para estos pipelines; a continuación, hay que compararlo con los resultados obtenidos por Anyscale y con pruebas realizadas localmente.

Anyscale, la empresa responsable de Ray, publicó un competidor benchmark en el cual Ray Data redujo o eliminó la brecha en las instancias de alto rendimiento CPU tras realizar ajustes. En conjunto, estos dos estudios realizados por proveedores constituyen una razón para probar operadores representativos y los ratios CPU a GPU, y no para establecer una clasificación definitiva y permanente.

Desde 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/")

Comparación distribuida

CaracterísticaSparkDatos de RayDaft (Flotilla)
Modelo de ejecuciónTarea por núcleo, basado en particionesTareas de streaming y actoresSwordfish por nodo, lotes en flujo continuo
VentajasProcesamiento masivo de SQL/ETL y tolerancia a fallosComputación heterogénea, saturación de GPUEnrutamiento multimodal con memoria limitada
GPU de controlProgramación de recursos junto con complementos del ecosistemaRecursos explícitos de tarea y actorProgramación integrada de CPU/GPU pipeline
Ajuste típicoEjecutores, memoria, particiones, aceleradoresLotes, actores, almacén de objetosLotes, recursos, concurrencia de E/S
Caso de evaluación adecuadoTareas de gran escala basadas en SQL/ETL y con alto uso de operaciones de mezcla de datosEntrenamiento o inferencia con recursos mixtosIngestión y transformación multimodal

Parte 4: el papel de Rust y Arrow

Debajo de la competencia por el mercado, la convergencia entre estas tecnologías es la historia más interesante. Polars, Daft y DataFusion utilizan ampliamente Rust, mientras que Ray incorpora componentes nativos junto con sus implementaciones en Python APIs. Todas ellas pueden intercambiar datos a través de partes de Apache Arrow ecosistema.

Convergencia de Rust y Arrow

El Interfaz Arrow PyCapsule (__arrow_c_stream__ Este protocolo proporciona a las bibliotecas compatibles una forma estandarizada para intercambiar flujos Arrow. Las transferencias pueden evitar la serialización por filas y reutilizar buffers cuando los esquemas y las distribuciones de memoria son compatibles. No obstante, la materialización, el reagrupamiento, la conversión de tipos o la transferencia entre dispositivos siguen requiriendo la copia de datos; por lo tanto, es necesario verificar dicho proceso mediante análisis de rendimiento en lugar de dar por sentado que es gratuita.

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

# Convert through Arrow; compatible buffers may be reused
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")

¿Por qué estos motores más recientes utilizan Rust para la ejecución nativa?

  1. Colector de basura sin seguimiento en código Rust. El sistema de propiedad permite a los operadores nativos un control más directo sobre la asignación y liberación de memoria. Esto puede reducir la latencia asociada al colector de basura, pero no evita la presión en la memoria ni los fallos por falta de espacio en memoria.

  2. Representaciones nativas compactas. Las estructuras de Rust no incluyen encabezados de objetos en Java. La ventaja práctica depende del diseño del motor: los sistemas JVM columnares también evitan representar cada valor como un objeto independiente.

  3. Verificaciones de concurrencia más estrictas. Las reglas de Rust seguras eliminan numerosas carreras de datos en tiempo de compilación. El código del motor puede seguir conteniendo bloques inseguros y errores de concurrencia lógica, pero el lenguaje reduce la superficie de posibles fallos.

Junto con el diseño de memoria columnar de Arrow, estas opciones permiten reducir la sobrecarga asociada a la asignación de memoria y a la serialización. No logran eliminarla en todos los puntos de transición; los objetos de Python, el transporte por red, los esquemas incompatibles y los movimientos del dispositivo siguen siendo factores relevantes.


Parte 5: adopción de múltiples motores

No es necesario forzar toda la plataforma a pasar por un único motor. La composición resulta útil cuando su implementación es más económica que crear un sistema único capaz de gestionar todas las cargas de trabajo.

El Modelo de Coexistencia: Múltiples Motores Pipeline

Una de las posibles soluciones de retransmisión consiste en utilizar Spark para realizar uniones en entornos lakehouse ya consolidados, escribir los resultados en un formato de tabla o archivo abierto, y posteriormente pasar dichos datos a Ray Data o Daft con el fin de ejecutar inferencias mediante GPU o aplicar transformaciones multimodales. Polars o DuckDB pueden servir para realizar análisis locales sobre los mismos archivos. Un equipo más reducido podría necesitar únicamente uno de estos motores; se añade otro cuando un cuello de botella medido justifique la expansión operativa.

Lo que hace posible esta composición son los formatos abiertos: Parquet, Delta, Iceberg y Arrow. Cada motor de la pila los lee y escribe de forma nativa, por lo que el paso de datos entre etapas se reduce a un simple camino de archivo.

Selección del motor según el caso de evaluación

Caso de evaluaciónLista de candidatos seleccionadosValidar
DataFrames analíticos localesPolars / DuckDBAPI combinación de ajuste, memoria y consultas
SQL/ETL distribuido existenteApache SparkComportamiento de mezcla, operaciones y costo
Trabajo paralelo nativo en PythonCoste de sobrecarga del planificador, modelo de fallos
Entrenamiento o inferencia mixto de CPU/GPURay Data / DaftUtilización del acelerador, contrapresión
Ingestión multimodalDaft / Ray DataOperadores nativos, comportamiento de reintentos
Motor de consultas incrustadoDataFusionExtensión APIs, cobertura SQL

Escalado diagonal

Durante mucho tiempo, la opción disponible era escalar verticalmente (con una máquina más grande) o escalar horizontalmente (con más máquinas). Polars Cloud Está realizando una operación intermedia a la que denominan diagonal scaling: escala horizontalmente mientras lee desde el almacenamiento en la nube para maximizar las operaciones de E/S, y luego se reduce a un único nodo grande una vez que los filtros y las agregaciones han reducido los datos, omitiendo por completo el proceso de mezcla distribuida.

La lección más importante es comparar el costo por tarea completada con éxito, y no el precio por hora de la instancia. Un nodo de mayor tamaño puede resultar más económico cuando la reducción generada por runtime supera su mayor tarifa, pero el resultado depende de las operaciones de E/S, de la presión en la memoria y de la cantidad de trabajo que se elimina antes de realizar un reordenamiento.


Reproducir la comparación

El repositorio de demostración complementaria Incluye los scripts, cuadernos de notas y el entorno utilizados para las comparaciones. La orden rápida que se muestra a continuación emplea datos de taxis correspondientes a un mes; siga las instrucciones de dataset del repositorio para reproducir la ejecución completa de todo el año que se presentó anteriormente.

# 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 ..

El repositorio también incluye cuadernos de notas para realizar comparaciones lado a lado API, así como configuraciones de Docker Compose para ejecutar localmente de forma distribuida Spark, Ray y Daft.


Conclusiones principales

  1. Demuestre que una sola máquina es insuficiente. Los resultados obtenidos al escalar la arquitectura indican que ciertas exploraciones analíticas de gran volumen caben en un único nodo. Antes de asumir los costes adicionales derivados del uso de un clúster, debe medir las necesidades de memoria, E/S, volcado de datos y tiempo de recuperación.

  2. Los modelos de ejecución son más importantes que la sintaxis habitual. La planificación perezosa, el empuje hacia abajo, el flujo en tiempo real y los operadores paralelos explican gran parte de la diferencia entre un script basado en DataFrame de tipo “eager” y un motor analítico.

  3. Mida todo el acelerador pipeline. La decodificación, las operaciones de E/S de red, el agrupamiento de solicitudes, la serialización y la contrapresión determinan si un GPU permanece ocupado. Ray Data y Daft convierten esta interacción en una abstracción fundamental; Spark puede ofrecer soporte para ello mediante diseños y herramientas adicionales.

  4. Seleccionar una lista corta según la carga de trabajo y, a continuación, benchmark. Se debe aprovechar la compatibilidad con el ecosistema para reducir el abanico de opciones, y elegir un caso de uso representativo de tipo end-to-end. Los resultados obtenidos mediante benchmark de los proveedores constituyen meras hipótesis, y no garantías.

  5. Los formatos abiertos permiten que la elección sea reversible. Las interfaces compatibles con flechas, Parquet y los formatos de tabla abiertos pueden reducir los costes de transferencia. Hay que comprobar si una conversión concreta reutiliza los búferes o los copia.

Si necesita un punto de partida, pruebe Polars para trabajar con datos tabulares locales pipeline, y Daft o Ray Data para manejar conjuntos de datos mixtos de tipo CPU/GPU pipeline. Elija Spark cuando su capacidad de ejecución distribuida, su ecosistema o su infraestructura operativa existente satisfagan una necesidad específica, y no solo cuando el tamaño de los datos dataset supere un umbral arbitrario.


Referencias

Benchmarks y datos de rendimiento

Motores y frameworks

Arquitectura y ecosistema

Repositorio de demostración

Datasets