Acesso a dados mais rápido com um conector Apache Arrow baseado em Rust
por Jack Yallop
À medida que organizações de setores como aeroespacial, manufatura, ciências da vida e outros setores intensivos em dados modernizam suas operações, elas trazem mais dados de sensores, telemetria e dados científicos para as plataformas de dados. No entanto, boa parte desses dados continua difícil de usar em escala porque está armazenada em formatos especializados ou proprietários que os principais mecanismos de análise não conseguem ler diretamente. Isso gera um desafio prático para o setor: as equipes precisam escolher entre criar transformações personalizadas lentas e que consomem muita memória, manter conectores especializados ou adicionar etapas extras de decodificação antes que os dados possam ser analisados.
Fontes de dados personalizadas do PySpark oferecem um caminho mais simples. Elas permitem que as equipes tragam formatos especializados para o Spark usando Python, sem precisar criar um conector separado do zero. Para a Eclipse Aerospace, uma fabricante e operadora de jatos muito leves, fazer parceria com o Databricks ofereceu uma maneira de tornar quase duas décadas de dados de voo proprietários mais acessíveis a centros de serviço e engenheiros, sem adicionar uma infraestrutura custosa e sempre ativa. O Databricks trouxe a plataforma de dados serverless e a integração com o Spark, enquanto a Eclipse Aerospace contribuiu com seus decodificadores de dados de voo existentes e sua experiência aeroespacial.
Juntas, a Eclipse Aerospace e o Databricks desenvolveram um caminho mais direto dos dados de voo para a análise, reduzindo a latência, a duplicação e a sobrecarga operacional do processo anterior de várias etapas, preservando ao mesmo tempo a flexibilidade do Databricks Serverless.
A Eclipse Aerospace conta com o Databricks para armazenar quase 20 anos de dados de voo, totalizando mais de 3TB. A empresa dá suporte aos seus centros de serviço e engenheiros com dados de mais de um milhão de horas de voo, obtidos e verificados em várias fontes, incluindo: telemetria de satélite quase em tempo real; grandes uploads em lote de históricos completos de voos; e fontes de dados externas. Toda a sua telemetria é armazenada em formatos de arquivo proprietários que exigem decodificadores personalizados, já desenvolvidos em várias linguagens, incluindo Rust e Scala.
Antes que as fontes de dados personalizadas do PySpark estivessem disponíveis no Databricks Runtime 15.4 LTS, a equipe da Eclipse Aerospace tinha três opções viáveis para decodificar e carregar seu tipo de arquivo proprietário no Databricks:
Para tipos de arquivos personalizados simples, as UDFs são uma opção natural. Elas trocam desempenho por legibilidade. No entanto, quando o formato da mensagem tem comprimento variável, as UDFs representam um risco real de falta de memória em mensagens longas e não puderam ser usadas. O decodificador Scala existente poderia ser usado para implementar um Data Source v1/v2 nativo, mas usá-lo no Databricks exigia computação dedicada. Os usuários esperavam poder carregar e visualizar dados rapidamente, o que exigiria manter essa computação sempre ativa, representando um custo maior do que as outras opções serverless. Isso deixou a equipe com a opção final de decodificar os arquivos em um formato de arquivo intermediário, neste caso, Avro, por meio de um decodificador baseado em Rust no AWS Lambda.
Essa solução era de baixo custo, mas apresentava os seguintes problemas:
O Databricks já era a plataforma da Eclipse Aerospace para armazenar e processar dados de voo. A vantagem decisiva foi a combinação de execução nativa do Spark, a fonte de dados personalizada do PySpark e o Databricks Serverless. O Databricks Serverless aloca e gerencia automaticamente a computação sob demanda, de modo que a Eclipse Aerospace não precisou provisionar nem manter uma infraestrutura dedicada em execução. A fonte de dados personalizada do PySpark tornou possível encapsular o decodificador Rust existente sem precisar criar um conector JVM. Como a decodificação agora é executada dentro do Spark, a mesma plataforma fornece a interface de usuário do Spark, métricas de jobs e fluxos de trabalho do Delta downstream, em vez de delegar a etapa crítica de ingestão a um pipeline Lambda separado.
A maior parte da engenharia de dados depende de formatos de dados comuns, mas alguns dos dados mais valiosos permanecem ocultos em formatos que o PySpark não consegue acessar diretamente, como HDF5 para dados científicos e padrões da indústria como ARINC 429 para dados aeroespaciais. O Databricks Serverless oferece às equipes uma maneira flexível de desbloquear esses dados em escala, enquanto as implementações respaldadas por Rust podem entregar um desempenho superior a uma abordagem puramente em Python.
As fontes de dados personalizadas do PySpark permitem definir um formato de arquivo legível/gravável em Python puro, utilizável como qualquer outra fonte de dados. Seu principal benefício é a simplicidade, permitindo que os usuários definam funções para leitura e escrita em Python puro, além de definir características importantes como paralelização. A equipe da Eclipse Aerospace foi além: ao criar um pacote Python respaldado por Rust, alcançou o dobro do desempenho avaliado em benchmark de uma fonte de dados puramente em Python baseada em PyArrow equivalente. Em comparação com uma implementação nativa em Python sem o Arrow, o desempenho foi quase 20 vezes superior.
O repositório de código de exemplo a seguir demonstra uma fonte de dados personalizada para ARINC 429. O código complementar inclui decisões de design que vão além do escopo deste artigo, juntamente com testes de benchmark que você mesmo pode executar.
O Apache Spark 4.0 lançou uma API de fontes de dados Python (mencionada na documentação do Databricks como fontes de dados personalizadas do PySpark) que permite registrar um formato personalizado para dados em lote ou em streaming, como:
Introduzida no Apache Spark 4.0 e disponível no Databricks, a API está geralmente disponível a partir do Databricks Runtime 15.4 LTS e também é compatível com o ambiente Serverless 2 e posterior. Como a API é implementada em Python, ela não exige um conector JVM ou arquivo JAR separado.
A API é uma interface simplificada para criar fontes de dados nativas, onde o usuário implementa algumas classes e as registra com:
Esses decodificadores vêm em dois tipos: Python nativo e formato Apache Arrow, sendo este último a escolha com muito mais desempenho. O Apache Arrow é um "formato de memória colunar independente de linguagem para dados planos e aninhados", tornando-se uma interface ideal entre diferentes linguagens que compartilham objetos de dados. A API tira proveito disso ao permitir que os usuários retornem objetos pyarrow.RecordBatch diretamente, e o Spark os consumirá como lotes em vez de materializar um InternalRow por linha.
Isso levantou a questão: o Python poderia servir como a camada de controle, com o Rust realizando o trabalho pesado de decodificação e mantendo o formato Apache Arrow do início ao fim?
A arquitetura por trás disso é um conector lógico único com um caminho de decodificação composto quase inteiramente por código compilado, e uma camada Python fina o suficiente para fazer alterações mínimas no SDK Rust existente da Eclipse Aerospace. Essa camada Python fina é construída com PyO3/Maturin como um pacote independente, importado para os pipelines e usado como qualquer outra fonte. Para garantir o melhor desempenho e a separação de responsabilidades, a equipe seguiu alguns princípios arquitetônicos.

O decodificador não sabe que o Spark existe. O crate principal em Rust não tem dependência do PyO3, Arrow ou Spark e aceita apenas bytes, retornando registros estruturados. Isso permitiu que a Eclipse Aerospace migrasse a lógica de decodificação existente praticamente sem alterações, já que o SDK não precisava ser reescrito para isso, apenas encapsulado.
O Arrow é a única coisa que cruza o limite da FFI (foreign function interface), e ele faz isso sem cópias. A camada de integração em Rust cria um RecordBatch do Arrow usando arrow-rs, e o entrega ao Python como um pyarrow.RecordBatch por meio da interface de dados C do Arrow. Ao fazer isso, não há etapa de serialização, pois nenhum byte é recodificado e nenhum objeto Python por linha é criado. Os buffers alocados pelo Rust são os buffers lidos pelo Python.
A camada PySpark é propositalmente leve. Ela analisa as opções, decide como fazer o particionamento (um InputPartition por entrada no exemplo de referência) e define o schema. Em fontes de dados personalizadas do PySpark, DataSource, DataSourceReader e seus equivalentes de streaming precisam ser serializáveis para que o Spark possa serializar (pickle) a instância do leitor e enviá-la a um worker. Ao tornar a camada PySpark leve, a instância DataSourceReader que o Spark serializa e envia a um worker contém apenas estado Python simples (caminhos de arquivo, um int para o tamanho do lote). O objeto Rust apoiado por PyO3 é construído dentro de read(), após o leitor já ter chegado ao worker. Ele próprio nunca faz parte do que é serializado.
O código nativo é enviado como um wheel Python, construído com o Maturin. No Databricks, esse wheel é um artefato de build, anexado a um ambiente de job ou notebook como uma dependência Python normal. Como a implementação é compilada, a compatibilidade de versão e arquitetura exige cuidado, embora um processo de CI/CD adequado possa gerenciar isso conforme demonstrado no repositório de exemplo.
No Databricks Serverless, o caminho de execução é simples:
read() e passa os bytes do arquivo para o decodificador compilado.Esta é a principal diferença em relação ao design anterior: o arquivo original é decodificado como parte da leitura do Spark, em vez de ser copiado para uma zona de pouso Avro intermediária primeiro.

Ao substituir o fluxo de trabalho Lambda e Avro de vários estágios por uma única leitura nativa do Spark, a Eclipse Aerospace disponibilizou dados de voo mais rapidamente, reduzindo a infraestrutura, o armazenamento e a sobrecarga operacional. O novo conector de fontes de dados personalizadas do PySpark nativo para Arrow também traz a decodificação para o Spark, melhorando a visibilidade do desempenho do pipeline e permitindo que a lógica de schema e qualidade de dados seja gerenciada em um só lugar.
A implementação da fonte de dados personalizada simplificou significativamente a infraestrutura da Eclipse Aerospace, e os efeitos aparecem em quatro áreas distintas: latência, custo, observabilidade e tempo de engenharia.
Latência. Reduzir um pipeline de vários saltos (upload, Lambda, decodificação, gravação em Avro, leitura de Avro, carregamento no Delta) a uma única leitura nativa do Spark remove vários saltos de rede e vários pontos em que um arquivo fica em uma fila aguardando o próximo estágio buscá-lo. O novo conector pode processar registros em até um minuto após o upload do arquivo, incluindo o tempo de inicialização, e normalmente pode processar lotes grandes de vários gigabytes em paralelo em menos de dois minutos devido ao paralelismo.
Custo. A remoção do salto Lambda e do intermediário Avro eliminou toda uma camada de infraestrutura que a Eclipse Aerospace precisava executar, monitorar e pagar. Eles não pagam mais para armazenar duas cópias de cada voo (o binário original e sua conversão Avro), o que, em quase duas décadas de histórico de voos mantido, representa uma quantidade significativa de armazenamento duplicado. Isso significa que a maioria dos arquivos permanece no armazenamento Glacier na AWS, com decodificações facilmente repetíveis caso os dados originais sejam necessários novamente.
Observabilidade. Como a etapa de decodificação agora ocorre dentro do próprio Spark, ela aparece na interface do Spark e nas métricas de job padrão, como qualquer outro estágio de um job. A Eclipse Aerospace usa o parâmetro personalizado "badRecordsPath", que imita o carregador CSV/JSON, para capturar dados mal formatados e mantém amplos pipelines de qualidade de dados que capturam estatísticas diretamente do decodificador.
Tempo de engenharia. A lógica de schema costumava ser definida duas vezes: uma no decodificador Lambda que produzia Avro e novamente nos jobs do Spark a jusante que o consumiam. Qualquer alteração no formato da mensagem significava atualizar ambos, em duas bases de código diferentes, com o risco de as duas definições se desviarem. Com um único conector versionado, o schema é definido uma vez, junto com a lógica de decodificação. Isso também significa que o mesmo padrão arquitetônico (um DataSource Python leve encapsulando um decodificador Rust) agora é reutilizável para os outros formatos proprietários suportados pela Eclipse Aerospace, em vez de ser algo que precisa ser reinventado para cada formato.
A experiência da Eclipse Aerospace mostra como formatos de dados especializados podem ser trazidos para o Databricks sem criar uma plataforma de ingestão separada ao redor deles. Ao manter o decodificador em Rust, usar o Python como camada de integração e passar registros por meio do Apache Arrow, a fonte de dados personalizada lê os arquivos originais diretamente no Spark e permanece compatível com a flexibilidade do Databricks Serverless.
Para equipes que trabalham com formatos proprietários ou específicos do setor, o padrão reutilizável é simples. Os engenheiros podem manter a lógica de decodificação específica de domínio próxima ao formato de origem, expô-la por meio de uma fonte de dados personalizada do PySpark e deixar que o Spark gerencie o particionamento, a execução, a observabilidade e o processamento posterior.
Quer começar? Dê uma olhada nos seguintes recursos:
Uma fonte de dados personalizada do PySpark é uma fonte de dados do Spark definida pelo usuário e implementada em Python. Ela permite que as equipes leiam formatos de arquivo especializados como parte de um fluxo de trabalho padrão do Spark sem precisar criar um conector JVM separado.
O conector de fonte de dados personalizada do PySpark nativo para Arrow ingere telemetria de voo proprietária no Databricks sem depender de um pipeline de vários estágios, computação dedicada sempre ativa ou cópias Avro duplicadas dos arquivos originais.
O Databricks Serverless é um serviço de computação totalmente gerenciado que permite executar cargas de trabalho sem provisionar ou gerenciar nenhuma infraestrutura em sua conta de nuvem. O Databricks aloca, dimensiona e otimiza automaticamente os recursos de computação para você.
O conector usa as fontes de dados personalizadas do PySpark para executar a lógica de leitura de arquivos dentro do Spark na computação serverless. O Python gerencia o fluxo de controle, enquanto o Rust e o Apache Arrow cuidam da decodificação de alto desempenho e da transferência de dados. Isso permite que arquivos proprietários sejam lidos diretamente, sem computação dedicada sempre ativa ou um pipeline Avro intermediário. As fontes de dados personalizadas do PySpark estão disponíveis no Databricks Runtime 15.4 LTS e superior, ou no ambiente serverless versão 2.
(Esta publicação no blog foi traduzida utilizando ferramentas baseadas em inteligência artificial) Publicação original
Assine nosso blog e receba os posts mais recentes diretamente na sua caixa de entrada.