Rust 기반 Apache Arrow 커넥터를 통한 더 빠른 데이터 액세스
작성자: Jack Yallop
항공우주, 제조, 생명과학 및 기타 데이터 집약적 산업의 조직들이 운영을 현대화함에 따라 더 많은 센서, 텔레메트리 및 과학 데이터를 데이터 플랫폼으로 가져오고 있습니다. 그러나 이 데이터의 대부분은 주요 분석 엔진이 직접 읽을 수 없는 특수하거나 독점적인 형식으로 존재하기 때문에 대규모로 사용하기가 여전히 어렵습니다. 이로 인해 실무적인 산업 과제가 발생합니다. 팀은 느리고 메모리를 많이 사용하는 커스텀 변환 구축, 특수 커넥터 유지 관리, 또는 데이터를 분석하기 전에 추가 디코딩 단계 추가 중에서 선택해야 합니다.
PySpark 커스텀 데이터 소스는 더 간단한 해결책을 제공합니다. 이를 통해 팀은 별도의 커넥터를 처음부터 구축하지 않고도 Python을 사용하여 특수 형식을 Spark로 가져올 수 있습니다. 경량 제트기 제조 및 운영사인 Eclipse Aerospace는 Databricks와의 파트너십을 통해 비용이 많이 드는 상시 가동 인프라를 추가하지 않고도 거의 20년에 달하는 독점 비행 데이터를 서비스 센터와 엔지니어들이 더 쉽게 액세스할 수 있도록 만들 수 있었습니다. Databricks는 서버리스 데이터 플랫폼과 Spark 통합을 제공하고, Eclipse Aerospace는 기존 비행 데이터 디코더와 항공우주 전문 지식을 기여했습니다.
Eclipse Aerospace와 Databricks는 비행 데이터에서 분석까지 이어지는 보다 직접적인 경로를 함께 개발하여 Databricks Serverless의 유연성을 유지하면서 이전의 다단계 프로세스가 가졌던 지연 시 간, 중복 및 운영 오버헤드를 줄였습니다.
Eclipse Aerospace는 Databricks를 사용하여 총 3TB가 넘는 거의 20년 분량의 비행 데이터를 저장합니다. 여기에는 실시간에 가까운 위성 텔레메트리, 전체 비행 이력의 대규모 대량 업로드, 외부 데이터 소스를 비롯한 여러 소스에서 수집 및 검증된 100만 시간이 넘는 비행 시간의 데이터가 서비스 센터와 엔지니어들에게 제공됩니다. 모든 텔레메트리는 Rust와 Scala를 비롯한 여러 언어로 이미 개발된 커스텀 디코더가 필요한 독점 파일 형식으로 저장됩니다.
Databricks Runtime 15.4 LTS에서 Pyspark 커스텀 데이터 소스를 사용할 수 있게 되기 전에는 Eclipse Aerospace 팀이 독점 파일 형식을 디코딩하여 Databricks에 로드하기 위한 세 가지 실행 가능한 옵션을 가지고 있었습니다.
간단한 커스텀 파일 형식의 경우 UDF는 자연스러운 옵션입니다. 성능과 가독성을 맞바꾸는 방식입니다. 그러나 메시지 형식의 길이가 가변적인 경우 UDF는 긴 메시지에서 메모리 부족 현상이 발생할 위험이 크기 때문에 사용할 수 없었습니다. 기존 Scala 디코더를 사용하여 네이티브 Data Source v1/v2를 구현할 수도 있었지만, Databricks에서 이를 사용하려면 전용 컴퓨팅 이 필요했습니다. 사용자는 데이터를 빠르게 업로드하고 볼 수 있기를 기대했으므로 이 컴퓨팅을 항상 켜두어야 하며, 이는 다른 서버리스 옵션보다 더 큰 비용을 의미했습니다. 결국 팀에게 남은 마지막 옵션은 AWS Lambda에서 Rust 기반 디코더를 통해 파일을 중간 파일 형식(이 경우 Avro)으로 디코딩하는 것이었습니다.
이 솔루션은 비용이 저렴했지만 다음과 같은 문제가 있었습니다.
Databricks는 이미 Eclipse Aerospace가 비행 데이터를 저장하고 처리하는 데 사용하는 플랫폼이었습니다. 결정적인 장점은 Spark 네이티브 실행, Pyspark 커스텀 데이터 소스, 그리고 Databricks Serverless의 조합이었습니다. Databricks Serverless는 온디맨드 컴퓨팅을 자동으로 할당하고 관리하므로 Eclipse Aerospace는 전용 인프라를 프로비저닝하거나 계속 실행 상태로 유지할 필요가 없습니다. PySpark 커스텀 데이터 소스를 사용하면 JVM 커넥터를 구축하지 않고도 기존 Rust 디코더를 래핑할 수 있습니다. 이제 디코딩이 Spark 내부에서 실행되므로, 중요한 수집 단계를 별도의 Lambda 파이프라인에 넘기는 대신 동일한 플랫폼에서 Spark UI, 작업 메트릭 및 다운스트림 Delta 워크플로를 제공합니다.
대부분의 데이터 엔지니어링은 공통 데이터 형식을 기반으로 하지만, 가장 가치 있는 데이터 중 일부는 과학 데이터용 HDF5 및 항공우주 데이터용 ARINC 429와 같은 산업 표준과 같이 PySpark가 직접 액세스할 수 없는 형식으로 숨겨져 있습니다. Databricks Serverless는 팀이 대규모로 해당 데이터의 잠금을 해제할 수 있는 유연한 방법을 제공하며, Rust 기반 구현은 순수 Python 방식을 뛰어넘는 성능을 제공할 수 있습니다.
PySpark 커스텀 데이터 소스를 사용하면 다른 데이터 소스처럼 사용할 수 있는 읽기/쓰기 가능한 파일 형식을 순수 Python으로 정의할 수 있습니다. 주요 이점은 단순성으로, 사용자가 순수 Python으로 읽고 쓰는 함수를 정의할 수 있을 뿐만 아니라 병렬화와 같은 중요한 특성도 정의할 수 있습니다. Eclipse Aerospace 팀은 여기서 한 걸음 더 나아가 Rust 기반 Python 패키지를 구축하여 유사한 PyArrow 기반 순수 Python 데이터 소스의 벤치마크 성능의 두 배를 달성했습니다. Arrow가 없는 네이티브 Python 구현에 비해서는 거의 20배에 달하는 성능을 보여주었습니다.
다음 예제 코드 리포지토리에서는 ARINC 429용 커스텀 데이터 소스를 살펴봅니다. 첨부된 코드에는 이 문서의 범위를 벗어나는 설계 결정 사항과 직접 실행할 수 있는 벤치마크 테스트가 포함되어 있습니다.
Apache Spark 4.0에는 다음과 같이 일괄 처리 또는 스트리밍 데이터에 대한 커스텀 형식을 등록할 수 있는 Python Data Source API(Databricks 문서에서는 PySpark 커스텀 데이터 소스로 참조됨)가 탑재되어 있습니다.
Apache Spark 4.0에 도입되고 Databricks에서 사용할 수 있는 이 API는 Databricks Runtime 15.4 LTS부터 정식 버전(GA)으로 제공되며 Serverless 환경 2 이상에서도 지원됩니다. API가 Python으로 구현되어 있으므로 별도의 JVM 커넥터나 JAR이 필요하지 않습니다.
이 API는 네이티브 데이터 소스를 생성하기 위한 단순화된 인터페이스이며, 사용자는 몇 가지 클래스를 구현하고 다음과 같이 등록합니다.
이러한 디코더에는 네이티브 Python과 Apache Arrow 형식의 두 가지 종류가 있으며, 후자가 훨씬 더 성능이 뛰어난 선택입니다. Apache Arrow는 "플랫 및 중첩 데이터를 위한 언어 독립적 컬럼형 메모리 형식"으로, 데이터 객체를 공유하는 서로 다른 언어 간의 이상적인 인터페이스가 됩니다. API는 사용자가 pyarrow.RecordBatch 객체를 직접 반환할 수 있도록 허용함으로써 이를 활용하며, Spark는 행당 하나의 InternalRow를 구체화하는 대신 이를 배치로 소비합니다.
여기서 한 가지 의문이 생겼습니다. Python이 제어 레이어 역할을 하고, Rust가 Apache Arrow 형식을 일관되게 유지하면서 디코딩의 무거운 작업을 담당할 수 있을까요?
이 뒤의 아키텍처는 거의 전체가 컴파일된 코드로 이루어진 디코딩 경로를 가진 단일 논리적 커넥터와, Eclipse Aerospace의 기존 Rust SDK를 최소한으로만 변경할 수 있을 만큼 가벼운 Python 레이어로 구성됩니다. 이 가벼운 Python 레이어는 독립형 패키지로 PyO3/Maturin을 사용하여 구축되며, 파이프라인으로 가져와서 다른 소스처럼 사용할 수 있습니다. 최고의 성능과 관심사 분리를 보장하기 위해 팀은 몇 가지 아키텍처 원칙을 따랐습니다.

디코더는 Spark의 존재를 모릅니다. 핵심 Rust 크레이트는 PyO3, Arrow 또는 Spark에 대한 종속성이 없으며 바이트만 입력받아 구조화된 레코드를 반환합니다. 이를 통해 Eclipse Aerospace는 SDK를 다시 작성할 필요 없이 래핑만 하면 되었으므로 기존 디코딩 로직을 크게 변경하지 않고 포팅할 수 있었습니다.
Arrow는 FFI(Foreign Function Interface) 경계를 넘는 유일한 항목이며, 복사 없이 경계를 넘습니다. Rust 글루 레이어는 arrow-rs를 사용하여 Arrow RecordBatch를 빌드한 다음, Arrow C 데이터 인터페이스를 통해 pyarrow.RecordBatch로 Python에 전달합니다. 이 과정에서 바이트가 다시 인코딩되거나 행별 Python 객체가 구축되지 않으므로 직렬화 단계가 없습니다. Rust가 할당한 버퍼가 바로 Python 이 읽는 버퍼입니다.
PySpark 계층은 의도적으로 가볍게 설계되었습니다. 옵션을 파싱하고, 파티셔닝 방식을 결정하며(참조 예제에서는 입력당 InputPartition개), 스키마를 정의합니다. PySpark 커스텀 데이터 소스에서는 DataSource, DataSourceReader 및 이와 상응하는 스트리밍 작업 모두 직렬화 가능해야 하므로, Spark는 리더 인스턴스를 피클(pickle)하여 워커로 전송할 수 있습니다. PySpark 계층을 가볍게 유지함으로써, Spark가 피클하여 워커로 전송하는 DataSourceReader 인스턴스는 순수 Python 상태(파일 경로, 배치 크기를 나타내는 int)만 유지하게 됩니다. PyO3 기반 Rust 객체는 리더가 워커에 도착한 후 read() 내부에서 생성됩니다. 객체 자체가 피클 대상에 포함되지는 않습니다.
네이티브 코드는 Maturin으로 빌드된 Python wheel 형태로 전송됩니다. Databricks에서는 이 wheel이 빌드 아티팩트이며, 일반적인 Python 종속성으로 작업 또는 노트북 환경에 연결됩니다. 구현 내용이 컴파일되기 때문에 버전 및 아키텍처 호환성에 주의를 기울여야 하지만, 예제 저장소에 나타난 것처럼 적절한 CI/CD 프로세스를 통해 이를 관리할 수 있습니다.
Databricks Serverless에서는 런타임 경로가 매우 단순합니다.
read() 내부에서 Rust 디코더를 생성하고 파일 바이트를 컴파일된 디코더로 전달합니다.이것이 이전 설계와의 핵심적인 차이점입니다. 원본 파일을 먼저 중간 Avro 랜딩 존으로 복사하는 대신, Spark 읽기의 일부로 디코딩합니다.

다단계 Lambda 및 Avro 워크플로를 단일 Spark 네이티브 읽기로 교체함으로써, Eclipse Aerospace는 인프라, 스토리지 및 운영 오버헤드를 줄이면서 비행 데이터를 더 빠르게 활용할 수 있게 되었습니다. 또한 새로운 Arrow 네이티브 PySpark 커스텀 데이터 소스 커넥터를 통해 디코딩 기능이 Spark 내부로 통합되어 파이프라인 성능 가시성이 향상되었고, 스키마 및 데이터 품 질 로직을 한 곳에서 관리할 수 있게 되었습니다.
커스텀 데이터 소스 구현으로 Eclipse Aerospace의 인프라가 대폭 단순화되었으며, 그 효과는 지연 시간, 비용, 관측 가능성, 엔지니어링 시간의 4가지 영역에서 명확하게 나타납니다.
지연 시간. 다중 홉 파이프라인(업로드, Lambda, 디코딩, Avro 쓰기, Avro 읽기, Delta 로드)을 단일 Spark 네이티브 읽기로 통합함으로써 여러 네트워크 홉과 파일이 다음 단계를 기다리며 대기열에 머무르는 지점들이 제거되었습니다. 새로운 커넥터는 시작 시간을 포함하여 파일 업로드 후 1분 이내에 레코드를 처리할 수 있으며, 병렬 처리 덕분에 일반적으로 수기가바이트에 달하는 대규모 배치를 2분 이내에 처리할 수 있습니다.
비용. Lambda 홉과 Avro 중간 단계를 제거함으로써 Eclipse Aerospace가 실행, 모니터링 및 유지 비용을 지불해야 했던 인프라 계층 전체가 사라졌습니다. 거의 20년에 달하는 비행 기록 분량에 대해 원본 바이너리와 Avro 변환본이라는 두 가지 복사본을 각각 저장할 필요가 없어지면서 상당한 양의 중복 스토리지를 절감하게 되었습니다. 결과적으로 대부분의 파일은 AWS의 Glacier 스토리지에 유지되며, 원본 데이터가 다시 필요한 경우 간편하게 반복 디코딩을 수행할 수 있습니다.
관측 가능성. 디코딩 단계가 이제 Spark 내부에서 직접 수행되므로, 작업의 다른 단계와 마찬가지로 Spark UI 및 표준 작업 메트릭에 표시됩니다. Eclipse Aerospace는 CSV/JSON 로더를 모방한 커스텀 "badRecordsPath"를 사용하여 형식이 잘못된 데이터를 캡처하고, 디코더에서 직접 통계를 수집하는 광범위한 데이터 품질 파이프라인을 유지 관리합니다.
엔지니어링 시간. 과거에는 스키마 로직이 Avro를 생성하는 Lambda 디코더와 이를 소비하는 다운스트림 Spark 작업에 각각 두 번 정의되어야 했습니다. 메시지 포맷이 변경되면 두 개의 서로 다른 코드베이스를 모두 업데이트해야 했으며, 두 정의가 서로 일치하지 않게 될 위험이 있었습니다. 단일 버전 관리 커넥터를 사용하면 디코딩 로직 바로 옆에 스키마가 한 번만 정의됩니다. 즉, 얇은 Python DataSource가 Rust 디코더를 감싸는 동일한 아키텍처 패턴을 포맷마다 새로 만들 필요 없이 Eclipse Aerospace가 지원하는 다른 독점 포맷에도 재사용할 수 있습니다.
Eclipse Aerospace의 경험은 별도의 수집 플랫폼을 구축하지 않고도 특화된 데이터 포맷을 Databricks로 가져올 수 있음을 보여줍니다. 디코더를 Rust로 유지하고, Python을 통합 계층으로 사용하며, Apache Arrow를 통해 레코드를 전달함으로써 커스텀 데이터 소스는 Spark에서 원본 파일을 직접 읽고 Databricks Serverless의 유연성과 호환성을 유지합니다.
독점 포맷이나산업별 포맷을 다루는 팀을 위해 재사용 가능한 패턴은 간단합니다. 엔지니어는 도메인별 디코딩 로직을 소스 포맷 가까이에 유지하고, PySpark 커스텀 데이터 소스를 통해 노출하며, Spark가 파티셔닝, 실행, 관측 가능성 및 다운스트림 처리를 담당하도록 할 수 있습니다.
시작할 준비가 되셨나요? 다음 내용을 확인해 보세요.
PySpark 커스텀 데이터 소스는 Python으로 구현된 사용자 정의 Spark 데이터 소스입니다. 팀은 별도의 JVM 커넥터를 구축하지 않고도 표준 Spark 워크플로의 일부로 특화된 파일 포맷을 읽을 수 있습니다.
Arrow 네이티브 PySpark 커스텀 데이터 소스 커넥터는 다단계 파이프라인, 전용 상시 가동 컴퓨팅, 또는 원본 파일의 중복 Avro 복사본에 의존하지 않고도 독점 비행 텔레메트리를 Databricks로 수집합니다.
Databricks Serverless는 클라우드 계정에서 인프라를 프로비저닝하거나 관리할 필요 없이 워크플로를 실행할 수 있는 완전 관리형 컴퓨팅 서비스입니다. Databricks가 컴퓨팅 리소스를 자동으로 할당, 확장 및 최적화해 줍니다.
이 커넥터는 PySpark 커스텀 데이터 소스를 사용하여 서버리스 컴퓨팅상의 Spark 내부에서 파일 읽기 로직을 실행합니다. Python이 제어 흐름을 처리하고, Rust와 Apache Arrow가 고성능 디코딩 및 데이터 전송을 처리합니다. 이를 통해 전용 상시 가동 컴퓨팅이나 중간 Avro 파이프라인 없이도 독점 파일을 직접 읽을 수 있습니다. PySpark 커스텀 데이터 소스는 Databricks Runtime 15.4 LTS 이상 또는 서버리스 환경 버전 2에서 사용할 수 있습니다.
(이 글은 AI의 도움을 받아 번역되었습니다. 원문이 궁금하시다면 여기를 클릭해 주세요)
블로그를 구독하고 최신 게시물을 이메일로 받아보세요.