{"schemaVersion":1,"id":"https://lucioduran.com/es/blog/arrow-datafusion-query-engine-rust","url":"https://lucioduran.com/es/blog/arrow-datafusion-query-engine-rust","title":"Apache Arrow DataFusion: Construyendo Query Engines Personalizados en Rust","description":"Una guía práctica de Apache Arrow DataFusion, construyendo query engines personalizados en Rust con ejecución columnar, modelos push-based vs pull-based, custom table providers, funciones definidas por usuario, y tuning real de performance para workloads analíticos.","language":"es","datePublished":"2026-01-05","dateModified":null,"contentFormat":"text/markdown","content":"Data Engineering\n\n# Apache Arrow DataFusion: Construyendo Query Engines Personalizados en Rust\n\n2026-01-053 min lectura\n\nLucio Durán\nEngineering Manager & AI Solutions Architect\n\nTambién disponible en: [English](<https://lucioduran.com/blog/arrow-datafusion-query-engine-rust>)\n\n## Descripción General y Arquitectura de DataFusion\n\n[Apache Arrow DataFusion](<https://datafusion.apache.org/>) es un query engine, no una base de datos. Proporciona:\n\n- Un parser y planner de SQL\n- Un optimizador de queries basado en costo (con 60+ reglas de optimización)\n- Un motor de ejecución paralelo usando Arrow RecordBatches\n- Una API de extensión para customizar todo\n\nLo que NO te da: gestión de almacenamiento, transacciones, replicación, gestión de usuarios. Es una librería, no un servidor.\n\n```rust\nuse datafusion::prelude::*;\n\n#[tokio::main]\nasync fn main() -> datafusion::error::Result<()> {\n let ctx = SessionContext::new();\n\n // Registrar un archivo Parquet como tabla\n ctx.register_parquet(\n \"events\",\n \"s3://mybucket/events/*.parquet\",\n ParquetReadOptions::default(),\n ).await?;\n\n // SQL simplemente funciona\n let df = ctx.sql(\"\n SELECT date_trunc('hour', timestamp) as hour,\n count(*) as event_count,\n approx_percentile_cont(latency_ms, 0.99) as p99\n FROM events\n WHERE timestamp > '2026-02-01'\n GROUP BY 1\n ORDER BY 1\n \").await?;\n\n df.show().await?;\n Ok(())\n}\n```\n\nEso es una query completamente paralela con predicate pushdown a row groups de Parquet, ejecución columnar sobre arrays Arrow, y computación de percentil aproximado. En unas 15 líneas.\n\n## Custom Table Providers: Tus Datos, Tus Reglas\n\nAcá es donde DataFusion resulta particularmente relevante. El trait ` TableProvider ` te permite envolver cualquier fuente de datos:\n\n```rust\n#[async_trait]\nimpl TableProvider for MetricsTableProvider {\n fn as_any(&self) -> &dyn Any { self }\n\n fn schema(&self) -> SchemaRef {\n self.schema.clone()\n }\n\n fn table_type(&self) -> TableType {\n TableType::Base\n }\n\n async fn scan(\n &self,\n state: &SessionState,\n projection: Option<&Vec<usize>>,\n filters: &[Expr],\n limit: Option<usize>,\n ) -> Result<Arc<dyn ExecutionPlan>> {\n // Extraer rango temporal de los filtros para pushdown\n let time_range = extract_time_range(filters);\n let projected_schema = project_schema(&self.schema, projection);\n\n Ok(Arc::new(MetricsScanExec {\n schema: projected_schema,\n client: self.metrics_client.clone(),\n time_range,\n limit,\n }))\n }\n\n fn supports_filters_pushdown(\n &self,\n filters: &[&Expr],\n ) -> Result<Vec<TableProviderFilterPushDown>> {\n filters.iter().map(|f| {\n if is_time_filter(f) {\n Ok(TableProviderFilterPushDown::Exact)\n } else {\n Ok(TableProviderFilterPushDown::Unsupported)\n }\n }).collect()\n }\n}\n```\n\nEl método ` supports_filters_pushdown ` le dice al optimizador qué predicados tu provider puede manejar nativamente. Devolver ` Exact ` significa que el optimizador no va a aplicar ese filtro de nuevo después del scan, tu provider lo manejó completamente. Esto elimina filtrado redundante y puede cortar queries de métricas de ~800ms a ~200ms.\n\n## Funciones Definidas por Usuario\n\nDataFusion soporta UDFs escalares, de agregación, y de ventana:\n\n```rust\n// UDF escalar: distancia haversine custom\nfn haversine_distance(args: &[ArrayRef]) -> Result<ArrayRef> {\n let lat1 = args[0].as_any().downcast_ref::<Float64Array>().unwrap();\n let lon1 = args[1].as_any().downcast_ref::<Float64Array>().unwrap();\n let lat2 = args[2].as_any().downcast_ref::<Float64Array>().unwrap();\n let lon2 = args[3].as_any().downcast_ref::<Float64Array>().unwrap();\n\n let result: Float64Array = lat1.iter().zip(lon1.iter())\n .zip(lat2.iter().zip(lon2.iter()))\n .map(|((la1, lo1), (la2, lo2))| {\n match (la1, lo1, la2, lo2) {\n (Some(la1), Some(lo1), Some(la2), Some(lo2)) => {\n Some(haversine(la1, lo1, la2, lo2))\n }\n _ => None,\n }\n })\n .collect();\n\n Ok(Arc::new(result))\n}\n\nctx.register_udf(create_udf(\n \"haversine\",\n vec![DataType::Float64; 4],\n Arc::new(DataType::Float64),\n Volatility::Immutable,\n Arc::new(haversine_distance),\n));\n\n// Ahora usalo en SQL\nlet df = ctx.sql(\"\n SELECT city_name,\n haversine(lat, lon, -34.6037, -58.3816) as distancia_a_bsas\n FROM cities\n ORDER BY distancia_a_bsas\n LIMIT 10\n\").await?;\n```\n\n## Resultados de Benchmark\n\nEn una máquina de 32 cores, consultando 50 GB de datos Parquet (1.2 mil millones de filas):\n\n```\nQuery: SELECT date_trunc('hour', ts), count(*), avg(value)\n FROM events WHERE ts > '2026-02-01' GROUP BY 1\n\nDuckDB (in-process): 1,8s\nDataFusion (default): 2,4s\nDataFusion (tuneado): 1,1s\nSpark (local[32]): 14,2s\n```\n\nEl DataFusion tuneado fue en realidad más rápido que DuckDB en este workload específico porque el reader Parquet custom solapaba I/O con computación, mientras DuckDB bloqueaba en lecturas, DataFusion estaba procesando el batch anterior.\n\nPara analytics sobre datos heterogéneos sin un servidor de base de datos administrado, DataFusion es una de las opciones más sólidas disponibles. El ecosistema Rust alrededor de Arrow está madurando rápido, y la comunidad es muy activa. Las APIs de extensión son poderosas pero no triviales, conocimiento sólido de Rust es un prerrequisito.\n\n## Preguntas frecuentes\n\n### ¿Qué es Apache Arrow DataFusion?\n\nDataFusion es un query engine extensible escrito en Rust, construido sobre el formato de memoria columnar de Apache Arrow. Provee una API de SQL y DataFrame, un optimizador de queries basado en costo, y un motor de ejecución paralelo que es posible embeber en tus propias aplicaciones o extender con fuentes de datos custom, funciones y estrategias de ejecución.\n\n### ¿Cuándo debería usar DataFusion en vez de una base de datos existente?\n\nDataFusion sobresale cuando se necesita analytics embebidos sin un servidor de base de datos separado, fuentes de datos custom que no encajan en bases de datos tradicionales, o cuando se desea construir query engines específicos de dominio. Si se necesita transacciones, concurrencia multi-usuario, o garantías ACID completas, una base de datos tradicional es mejor.\n\n### ¿Cuál es la diferencia entre ejecución push-based y pull-based?\n\nEn ejecución pull-based (Volcano), cada operador tira batches de sus hijos llamando next(). En ejecución push-based, los datos fluyen downstream sin que los operadores necesiten solicitarlos. DataFusion usa un modelo de streaming pull-based con RecordBatch como unidad de trabajo, que soporta backpressure naturalmente y se integra bien con el modelo async de Rust.\n\n## Tips\n\n1. **Tip:** El trait ` TableProvider ` de DataFusion es engañosamente poderoso. Puede envolver literalmente cualquier fuente de datos, archivos Parquet en S3, APIs REST, hasta streams gRPC, detrás de él, y el optimizador de queries simplemente funciona. Un provider custom para un store interno de métricas se puede armar en unas 200 líneas de Rust.\n2. **Tip:** Si tus queries filtran por una columna específica el 90% del tiempo, implementá ` supports_filters_pushdown ` en tu ` TableProvider `. El optimizador va a pushear predicados hasta tu scan, y es posible saltear row groups enteros. Solo esto puede cortar la latencia de queries 4x.\n3. **Tip:** No escribas funciones de agregación custom desde cero. Utilizar el trait ` Accumulator ` de DataFusion, maneja particionamiento y merge entre particiones automáticamente. Armar un HyperLogLog paralelo custom desde cero es innecesario cuando el trait maneja todas las partes difíciles automáticamente.\n\n- arrow\n- datafusion\n- rust\n- query-engine\n- columnar\n- data-engineering\n- analytics\n- performance\n\n[Volver al Blog](<https://lucioduran.com/es/blog>)","headings":[{"level":1,"text":"Apache Arrow DataFusion: Construyendo Query Engines Personalizados en Rust","url":"https://lucioduran.com/es/blog/arrow-datafusion-query-engine-rust#title"},{"level":2,"text":"Descripción General y Arquitectura de DataFusion","url":"https://lucioduran.com/es/blog/arrow-datafusion-query-engine-rust#descripcin-general-y-arquitectura-de-datafusion"},{"level":2,"text":"Custom Table Providers: Tus Datos, Tus Reglas","url":"https://lucioduran.com/es/blog/arrow-datafusion-query-engine-rust#custom-table-providers-tus-datos-tus-reglas"},{"level":2,"text":"Funciones Definidas por Usuario","url":"https://lucioduran.com/es/blog/arrow-datafusion-query-engine-rust#funciones-definidas-por-usuario"},{"level":2,"text":"Resultados de Benchmark","url":"https://lucioduran.com/es/blog/arrow-datafusion-query-engine-rust#resultados-de-benchmark"},{"level":2,"text":"Preguntas frecuentes","url":"https://lucioduran.com/es/blog/arrow-datafusion-query-engine-rust#questions"},{"level":3,"text":"¿Qué es Apache Arrow DataFusion?","url":"https://lucioduran.com/es/blog/arrow-datafusion-query-engine-rust#question-1"},{"level":3,"text":"¿Cuándo debería usar DataFusion en vez de una base de datos existente?","url":"https://lucioduran.com/es/blog/arrow-datafusion-query-engine-rust#question-2"},{"level":3,"text":"¿Cuál es la diferencia entre ejecución push-based y pull-based?","url":"https://lucioduran.com/es/blog/arrow-datafusion-query-engine-rust#question-3"},{"level":2,"text":"Tips","url":"https://lucioduran.com/es/blog/arrow-datafusion-query-engine-rust#article-tips"}],"sections":[{"heading":"Apache Arrow DataFusion: Construyendo Query Engines Personalizados en Rust","level":1,"url":"https://lucioduran.com/es/blog/arrow-datafusion-query-engine-rust","content":"Data Engineering"},{"heading":"Apache Arrow DataFusion: Construyendo Query Engines Personalizados en Rust","level":1,"url":"https://lucioduran.com/es/blog/arrow-datafusion-query-engine-rust#title","content":"2026-01-053 min lectura\n\nLucio Durán\nEngineering Manager & AI Solutions Architect\n\nTambién disponible en: [English](<https://lucioduran.com/blog/arrow-datafusion-query-engine-rust>)"},{"heading":"Descripción General y Arquitectura de DataFusion","level":2,"url":"https://lucioduran.com/es/blog/arrow-datafusion-query-engine-rust#descripcin-general-y-arquitectura-de-datafusion","content":"[Apache Arrow DataFusion](<https://datafusion.apache.org/>) es un query engine, no una base de datos. Proporciona:\n\n- Un parser y planner de SQL\n- Un optimizador de queries basado en costo (con 60+ reglas de optimización)\n- Un motor de ejecución paralelo usando Arrow RecordBatches\n- Una API de extensión para customizar todo\n\nLo que NO te da: gestión de almacenamiento, transacciones, replicación, gestión de usuarios. Es una librería, no un servidor.\n\n```rust\nuse datafusion::prelude::*;\n\n#[tokio::main]\nasync fn main() -> datafusion::error::Result<()> {\n let ctx = SessionContext::new();\n\n // Registrar un archivo Parquet como tabla\n ctx.register_parquet(\n \"events\",\n \"s3://mybucket/events/*.parquet\",\n ParquetReadOptions::default(),\n ).await?;\n\n // SQL simplemente funciona\n let df = ctx.sql(\"\n SELECT date_trunc('hour', timestamp) as hour,\n count(*) as event_count,\n approx_percentile_cont(latency_ms, 0.99) as p99\n FROM events\n WHERE timestamp > '2026-02-01'\n GROUP BY 1\n ORDER BY 1\n \").await?;\n\n df.show().await?;\n Ok(())\n}\n```\n\nEso es una query completamente paralela con predicate pushdown a row groups de Parquet, ejecución columnar sobre arrays Arrow, y computación de percentil aproximado. En unas 15 líneas."},{"heading":"Custom Table Providers: Tus Datos, Tus Reglas","level":2,"url":"https://lucioduran.com/es/blog/arrow-datafusion-query-engine-rust#custom-table-providers-tus-datos-tus-reglas","content":"Acá es donde DataFusion resulta particularmente relevante. El trait ` TableProvider ` te permite envolver cualquier fuente de datos:\n\n```rust\n#[async_trait]\nimpl TableProvider for MetricsTableProvider {\n fn as_any(&self) -> &dyn Any { self }\n\n fn schema(&self) -> SchemaRef {\n self.schema.clone()\n }\n\n fn table_type(&self) -> TableType {\n TableType::Base\n }\n\n async fn scan(\n &self,\n state: &SessionState,\n projection: Option<&Vec<usize>>,\n filters: &[Expr],\n limit: Option<usize>,\n ) -> Result<Arc<dyn ExecutionPlan>> {\n // Extraer rango temporal de los filtros para pushdown\n let time_range = extract_time_range(filters);\n let projected_schema = project_schema(&self.schema, projection);\n\n Ok(Arc::new(MetricsScanExec {\n schema: projected_schema,\n client: self.metrics_client.clone(),\n time_range,\n limit,\n }))\n }\n\n fn supports_filters_pushdown(\n &self,\n filters: &[&Expr],\n ) -> Result<Vec<TableProviderFilterPushDown>> {\n filters.iter().map(|f| {\n if is_time_filter(f) {\n Ok(TableProviderFilterPushDown::Exact)\n } else {\n Ok(TableProviderFilterPushDown::Unsupported)\n }\n }).collect()\n }\n}\n```\n\nEl método ` supports_filters_pushdown ` le dice al optimizador qué predicados tu provider puede manejar nativamente. Devolver ` Exact ` significa que el optimizador no va a aplicar ese filtro de nuevo después del scan, tu provider lo manejó completamente. Esto elimina filtrado redundante y puede cortar queries de métricas de ~800ms a ~200ms."},{"heading":"Funciones Definidas por Usuario","level":2,"url":"https://lucioduran.com/es/blog/arrow-datafusion-query-engine-rust#funciones-definidas-por-usuario","content":"DataFusion soporta UDFs escalares, de agregación, y de ventana:\n\n```rust\n// UDF escalar: distancia haversine custom\nfn haversine_distance(args: &[ArrayRef]) -> Result<ArrayRef> {\n let lat1 = args[0].as_any().downcast_ref::<Float64Array>().unwrap();\n let lon1 = args[1].as_any().downcast_ref::<Float64Array>().unwrap();\n let lat2 = args[2].as_any().downcast_ref::<Float64Array>().unwrap();\n let lon2 = args[3].as_any().downcast_ref::<Float64Array>().unwrap();\n\n let result: Float64Array = lat1.iter().zip(lon1.iter())\n .zip(lat2.iter().zip(lon2.iter()))\n .map(|((la1, lo1), (la2, lo2))| {\n match (la1, lo1, la2, lo2) {\n (Some(la1), Some(lo1), Some(la2), Some(lo2)) => {\n Some(haversine(la1, lo1, la2, lo2))\n }\n _ => None,\n }\n })\n .collect();\n\n Ok(Arc::new(result))\n}\n\nctx.register_udf(create_udf(\n \"haversine\",\n vec![DataType::Float64; 4],\n Arc::new(DataType::Float64),\n Volatility::Immutable,\n Arc::new(haversine_distance),\n));\n\n// Ahora usalo en SQL\nlet df = ctx.sql(\"\n SELECT city_name,\n haversine(lat, lon, -34.6037, -58.3816) as distancia_a_bsas\n FROM cities\n ORDER BY distancia_a_bsas\n LIMIT 10\n\").await?;\n```"},{"heading":"Resultados de Benchmark","level":2,"url":"https://lucioduran.com/es/blog/arrow-datafusion-query-engine-rust#resultados-de-benchmark","content":"En una máquina de 32 cores, consultando 50 GB de datos Parquet (1.2 mil millones de filas):\n\n```\nQuery: SELECT date_trunc('hour', ts), count(*), avg(value)\n FROM events WHERE ts > '2026-02-01' GROUP BY 1\n\nDuckDB (in-process): 1,8s\nDataFusion (default): 2,4s\nDataFusion (tuneado): 1,1s\nSpark (local[32]): 14,2s\n```\n\nEl DataFusion tuneado fue en realidad más rápido que DuckDB en este workload específico porque el reader Parquet custom solapaba I/O con computación, mientras DuckDB bloqueaba en lecturas, DataFusion estaba procesando el batch anterior.\n\nPara analytics sobre datos heterogéneos sin un servidor de base de datos administrado, DataFusion es una de las opciones más sólidas disponibles. El ecosistema Rust alrededor de Arrow está madurando rápido, y la comunidad es muy activa. Las APIs de extensión son poderosas pero no triviales, conocimiento sólido de Rust es un prerrequisito."},{"heading":"Preguntas frecuentes","level":2,"url":"https://lucioduran.com/es/blog/arrow-datafusion-query-engine-rust#questions","content":""},{"heading":"¿Qué es Apache Arrow DataFusion?","level":3,"url":"https://lucioduran.com/es/blog/arrow-datafusion-query-engine-rust#question-1","content":"DataFusion es un query engine extensible escrito en Rust, construido sobre el formato de memoria columnar de Apache Arrow. Provee una API de SQL y DataFrame, un optimizador de queries basado en costo, y un motor de ejecución paralelo que es posible embeber en tus propias aplicaciones o extender con fuentes de datos custom, funciones y estrategias de ejecución."},{"heading":"¿Cuándo debería usar DataFusion en vez de una base de datos existente?","level":3,"url":"https://lucioduran.com/es/blog/arrow-datafusion-query-engine-rust#question-2","content":"DataFusion sobresale cuando se necesita analytics embebidos sin un servidor de base de datos separado, fuentes de datos custom que no encajan en bases de datos tradicionales, o cuando se desea construir query engines específicos de dominio. Si se necesita transacciones, concurrencia multi-usuario, o garantías ACID completas, una base de datos tradicional es mejor."},{"heading":"¿Cuál es la diferencia entre ejecución push-based y pull-based?","level":3,"url":"https://lucioduran.com/es/blog/arrow-datafusion-query-engine-rust#question-3","content":"En ejecución pull-based (Volcano), cada operador tira batches de sus hijos llamando next(). En ejecución push-based, los datos fluyen downstream sin que los operadores necesiten solicitarlos. DataFusion usa un modelo de streaming pull-based con RecordBatch como unidad de trabajo, que soporta backpressure naturalmente y se integra bien con el modelo async de Rust."},{"heading":"Tips","level":2,"url":"https://lucioduran.com/es/blog/arrow-datafusion-query-engine-rust#article-tips","content":"1. **Tip:** El trait ` TableProvider ` de DataFusion es engañosamente poderoso. Puede envolver literalmente cualquier fuente de datos, archivos Parquet en S3, APIs REST, hasta streams gRPC, detrás de él, y el optimizador de queries simplemente funciona. Un provider custom para un store interno de métricas se puede armar en unas 200 líneas de Rust.\n2. **Tip:** Si tus queries filtran por una columna específica el 90% del tiempo, implementá ` supports_filters_pushdown ` en tu ` TableProvider `. El optimizador va a pushear predicados hasta tu scan, y es posible saltear row groups enteros. Solo esto puede cortar la latencia de queries 4x.\n3. **Tip:** No escribas funciones de agregación custom desde cero. Utilizar el trait ` Accumulator ` de DataFusion, maneja particionamiento y merge entre particiones automáticamente. Armar un HyperLogLog paralelo custom desde cero es innecesario cuando el trait maneja todas las partes difíciles automáticamente.\n\n- arrow\n- datafusion\n- rust\n- query-engine\n- columnar\n- data-engineering\n- analytics\n- performance\n\n[Volver al Blog](<https://lucioduran.com/es/blog>)"}],"links":[{"text":"English","url":"https://lucioduran.com/blog/arrow-datafusion-query-engine-rust"},{"text":"Apache Arrow DataFusion","url":"https://datafusion.apache.org/"},{"text":"Volver al Blog","url":"https://lucioduran.com/es/blog"}],"structuredData":[{"@context":"https://schema.org","@graph":[{"@type":"BlogPosting","@id":"https://lucioduran.com/es/blog/arrow-datafusion-query-engine-rust#article","mainEntityOfPage":{"@type":"WebPage","@id":"https://lucioduran.com/es/blog/arrow-datafusion-query-engine-rust"},"headline":"Apache Arrow DataFusion: Construyendo Query Engines Personalizados en Rust","description":"Una guía práctica de Apache Arrow DataFusion, construyendo query engines personalizados en Rust con ejecución columnar, modelos push-based vs pull-based, custom table providers, funciones definidas por usuario, y tuning real de performance para workloads analíticos.","datePublished":"2026-01-05","author":{"@type":"Person","@id":"https://lucioduran.com/#person","name":"Lucio Durán","url":"https://lucioduran.com"},"publisher":{"@type":"Person","@id":"https://lucioduran.com/#person","name":"Lucio Durán","url":"https://lucioduran.com"},"image":"https://lucioduran.com/favicons/og-default.png","url":"https://lucioduran.com/es/blog/arrow-datafusion-query-engine-rust","inLanguage":"es","isPartOf":{"@id":"https://lucioduran.com/blog#blog"},"articleSection":"Data Engineering","keywords":"arrow, datafusion, rust, query-engine, columnar, data-engineering, analytics, performance","wordCount":575,"timeRequired":"PT3M","workTranslation":[{"@type":"BlogPosting","inLanguage":"en","url":"https://lucioduran.com/blog/arrow-datafusion-query-engine-rust"}]},{"@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Inicio","item":"https://lucioduran.com/"},{"@type":"ListItem","position":2,"name":"Blog","item":"https://lucioduran.com/es/blog"},{"@type":"ListItem","position":3,"name":"Apache Arrow DataFusion: Construyendo Query Engines Personalizados en Rust","item":"https://lucioduran.com/es/blog/arrow-datafusion-query-engine-rust"}]},{"@type":"FAQPage","mainEntity":[{"@type":"Question","name":"¿Qué es Apache Arrow DataFusion?","acceptedAnswer":{"@type":"Answer","text":"DataFusion es un query engine extensible escrito en Rust, construido sobre el formato de memoria columnar de Apache Arrow. Provee una API de SQL y DataFrame, un optimizador de queries basado en costo, y un motor de ejecución paralelo que es posible embeber en tus propias aplicaciones o extender con fuentes de datos custom, funciones y estrategias de ejecución."}},{"@type":"Question","name":"¿Cuándo debería usar DataFusion en vez de una base de datos existente?","acceptedAnswer":{"@type":"Answer","text":"DataFusion sobresale cuando se necesita analytics embebidos sin un servidor de base de datos separado, fuentes de datos custom que no encajan en bases de datos tradicionales, o cuando se desea construir query engines específicos de dominio. Si se necesita transacciones, concurrencia multi-usuario, o garantías ACID completas, una base de datos tradicional es mejor."}},{"@type":"Question","name":"¿Cuál es la diferencia entre ejecución push-based y pull-based?","acceptedAnswer":{"@type":"Answer","text":"En ejecución pull-based (Volcano), cada operador tira batches de sus hijos llamando next(). En ejecución push-based, los datos fluyen downstream sin que los operadores necesiten solicitarlos. DataFusion usa un modelo de streaming pull-based con RecordBatch como unidad de trabajo, que soporta backpressure naturalmente y se integra bien con el modelo async de Rust."}}]}]}],"contentHash":"11bcc109957c7249fd4893ed1eecf44cb61ffaac925f742d58a312210a31ba09","provenance":{"method":"Deterministic serialization of the server-rendered main element","source":"https://lucioduran.com/es/blog/arrow-datafusion-query-engine-rust","hashAlgorithm":"sha256","hashScope":"JSON document fields before contentHash and provenance","sourceHTMLBytes":50295,"contentBytes":7601}}
