이 저장소가 대체 무엇인가.
Sail은 Apache Spark의 Spark Connect 프로토콜을 완전 구현하여, 기존 PySpark 코드를 한 줄도 고치지 않고 그대로 연결할 수 있는 분산 컴퓨트 엔진이다. JVM(자바 가상머신) 대신 Rust로 엔진 전체를 재작성했고, 내부적으로 Apache DataFusion과 Arrow를 사용해 컬럼 기반 벡터화 실행을 제공한다.
핵심 가치는 세 가지: 호환성(코드 변경 없음), 성능(4× 빠름, 메모리 60% 절약), 단순함(워커가 수 MB 메모리로 즉시 기동). 배치 ETL뿐 아니라 스트리밍과 AI/ML 워크로드까지 하나의 엔진으로 처리하는 것을 목표로 한다.
트렌딩 이유 · 경쟁 대비 장점.
JVM 탈출 + 드롭인 호환 + 압도적 벤치마크 세 조합이 데이터 엔지니어 커뮤니티를 흔들었다. Spark를 대체하고 싶어도 코드 마이그레이션 비용이 무서웠던 팀에게, "코드 건드리지 않아도 된다"는 약속은 큰 유인이다.
| 지표 | Apache Spark | Sail |
|---|---|---|
| 전체 쿼리 실행 시간 | 387초 | 102초 (3.8×) |
| 쿼리별 속도 향상 | 기준 | 43% ~ 727% |
| 피크 메모리 | 54 GB | 22 GB (60% 절약) |
| 디스크 셔플 스필 | 110 GB 이상 | 0 GB |
| 엔진 | 강점 | Sail 대비 한계 |
|---|---|---|
| Apache Spark (JVM) | 15년 생태계, 광범위한 커넥터 | JVM GC 일시정지, 높은 메모리 사용, 느린 기동, JVM 튜닝 복잡 |
| Polars | 단일 노드 Rust DataFrame, 매우 빠름 | Spark API 없음, 분산 처리 미지원(단일 노드 전용) |
| DuckDB | 단일 노드 OLAP, 임베디드 사용 탁월 | 분산 미지원, Spark API 미호환, 대규모 클러스터 부적합 |
| Ray | Python 네이티브 분산, AI/ML 강점 | Spark SQL·DataFrame API 없음, SQL 쿼리 최적화 약함 |
| Databricks (Spark 기반) | 매니지드 Spark + Delta Lake | 벤더 종속, 높은 비용, 여전히 JVM 기반 |
Polars·DuckDB가 빠르지만 Spark API를 지원하지 않아 마이그레이션 비용이 크다. Sail은 Spark Connect 프로토콜을 그대로 구현해, 연결 주소만 바꾸면(sc://localhost:50051) 기존 PySpark 코드가 돌아간다.
v0.6.4 기준 "Beta" 단계로, 모든 Spark 함수·기능을 지원하지 않는다. 공식 호환성 체크 스크립트(python -m pysail.examples.spark.compatibility_check)로 기존 코드베이스를 먼저 스캔해 지원 여부를 확인해야 한다. SQL 문자열 내 함수는 스크립트가 감지하지 못하니 실제 테스트도 필수.
Rust 크레이트 + Python 바인딩 + 인프라 계층 전체.
| 크레이트 / 라이브러리 | 역할 |
|---|---|
sail-spark-connect | Spark Connect gRPC 프로토콜 서버 구현. PySpark 클라이언트 요청 수신·세션 관리 |
sail-sql-parser | Spark SQL 방언 전용 파서(chumsky 파서 콤비네이터 사용). Rust 절차형 매크로로 SQL 문법 코드 생성 |
sail-logical-plan / sail-logical-optimizer | Spark 관계(Relation)를 DataFusion 논리 계획으로 변환·최적화 |
sail-physical-plan / sail-physical-optimizer | 논리 계획 → 물리 실행 계획 변환. 분산 셔플·병렬 파티션 결정 |
sail-execution | 실제 쿼리 실행 엔진 조율. 드라이버-워커 통신, 태스크 분배 |
sail-python-udf | Python/Pandas/Arrow UDF 실행. Arrow 포인터 공유로 직렬화 없음 |
sail-catalog-* (6개) | 카탈로그 연동: AWS Glue, Iceberg REST, Unity Catalog, Hive Metastore, OneLake, 인메모리 |
sail-delta-lake / sail-iceberg | Delta Lake·Apache Iceberg 테이블 포맷 지원 |
sail-object-store | 스토리지 추상화: S3·Azure·GCS·HDFS·HuggingFace·HTTP·인메모리 |
sail-flight | Apache Arrow Flight(gRPC 기반) 데이터 전송 서비스 |
sail-telemetry | OpenTelemetry 트레이싱·메트릭 수집 |
| DataFusion 54.0 | Rust 네이티브 쿼리 실행 엔진. SQL 파싱·벡터화 실행·Parquet I/O 담당 |
| Apache Arrow 58.3 | 컬럼 기반 인메모리 포맷. 모든 데이터가 Arrow 배열로 표현됨 |
| Tokio 1.52 | Rust 비동기 런타임. 고성능 비동기 I/O와 작업 스케줄링 |
| Tonic 0.14 (gRPC) | gRPC 서버/클라이언트. Spark Connect 프로토콜 통신에 사용 |
| 구성요소 | 역할 |
|---|---|
pysail PyPI 패키지 | pip으로 설치하는 사용자 진입점. Python 3.10~3.14 지원 |
sail-python 크레이트 (PyO3) | Rust-Python 바인딩. Rust 함수를 Python 모듈로 노출 |
SparkConnectServer Python API | Rust 서버를 Python에서 직접 기동하는 클래스 |
| Maturin 빌드 백엔드 | Rust 코드를 Python 휠(.whl)로 컴파일 |
pyspark-client 연동 | 기존 PySpark DataFrame/SQL API를 그대로 사용. Spark Connect 프로토콜로 Sail 서버에 연결 |
| 도구 | 역할 |
|---|---|
| Docker / devcontainer | 개발 환경: Python 3.11 + Rust + Zig + Maturin 빌드 환경 |
| Docker Compose | 로컬 테스트용 MinIO(S3 호환)·Azurite(Azure Blob)·HDFS·OpenTelemetry·Grafana 스택 |
| Kubernetes (sail.yaml) | 프로덕션 클러스터 배포. 드라이버 Pod가 워커 Pod를 동적 생성(kube API 사용) |
| kind (로컬 K8s) | k8s/kind-config.yaml로 로컬에서 클러스터 테스트 |
| OpenTelemetry + Tempo + Prometheus + Grafana | 분산 트레이싱·메트릭 수집·대시보드 |
Spark Connect 호환부터 DataFusion 실행까지 한 줄기로.
Spark Connect는 Spark 3.4부터 도입된 클라이언트-서버 분리 아키텍처다. PySpark 클라이언트가 gRPC로 계획(Plan)을 서버에 보내면 서버가 실행한다. Sail은 이 서버 역할을 Rust로 구현했다. PySpark는 자신이 Sail과 대화하는지 모른다 — 프로토콜만 맞으면 된다.
전통적인 Spark(JVM)는 행(Row) 단위로 처리하고 Java 객체 오버헤드가 크다. Sail은 Apache Arrow의 컬럼 기반 배열로 데이터를 들고, DataFusion이 SIMD(CPU의 단일 명령 다중 데이터) 명령어로 묶음 연산한다. 예를 들어 100만 행의 `age + 1` 연산을 CPU가 한 번에 32개씩 처리할 수 있다.
Spark에서 Python UDF는 Pickle 직렬화로 JVM ↔ Python 프로세스 간 데이터를 복사했다. Sail은 Arrow 배열 포인터를 Rust와 Python이 직접 공유한다. 데이터를 복사하지 않고 메모리 주소만 전달하므로 Python UDF 오버헤드가 드라마틱하게 낮다.
로컬에서 sail spark server를 실행하면 단일 프로세스 모드로 DataFusion이 직접 실행한다. Kubernetes에 배포하면 드라이버 Pod가 동적으로 워커 Pod를 생성하고 Arrow Flight로 데이터를 셔플한다. 코드는 같고 SAIL_MODE=kubernetes-cluster 환경변수 하나로 모드가 바뀐다.
JVM Spark는 자바 언어로 된 대형 호텔 주방이다. 주방장(JVM)이 중간에서 모든 걸 조율하는데, 주방장이 가끔 "잠깐만요, 청소 중" (GC)을 외치며 멈춘다. 재료(데이터)를 그릇(Java 객체)에 담아 나르니 그릇도 많이 필요하다.
Sail은 Rust로 지은 주방이다. 주방장 없이 요리사들(워커 스레드)이 직접 효율적으로 움직이고, 멈추는 일(GC)이 없다. 재료를 벨트(Arrow 컬럼 배열)에 줄 세워 한꺼번에(SIMD) 칼질하니 훨씬 빠르다. 손님(PySpark)은 주방이 바뀐 줄 모른다 — 같은 메뉴판(API)으로 주문할 뿐이다.
37개 Rust 크레이트 + Python 패키지 — 각 폴더의 역할.
이 레포에서 무엇을 배울 수 있는가.
Cargo 워크스페이스의 의존성 설계를 실전에서 배울 수 있다. 공통 의존성 버전을 Cargo.toml 최상단에서 관리하고, 각 크레이트가 { workspace = true }로 참조하는 패턴이다. Clippy 규칙도 워크스페이스 단위로 강제(unwrap_used = "deny", panic = "deny")한다.
볼 파일: 루트 Cargo.toml의 [workspace.dependencies], [workspace.lints.clippy]
Apache Arrow는 "메모리 안에서 데이터를 어떻게 표현하는가"의 표준이다. 행 단위 대신 컬럼 단위로 저장해 CPU 캐시 효율을 극적으로 높인다. DataFusion은 Arrow 위에서 SQL 쿼리를 실행하는 Rust 쿼리 엔진이다. Sail은 DataFusion을 실행 계획의 최종 실행자로 사용한다.
볼 파일: crates/sail-common-datafusion/, DataFusion 관련 크레이트 사용 패턴
Spark Connect는 클라이언트-서버가 gRPC(HTTP/2)로 통신하며, 계획(Plan)·표현식(Expression)·데이터 타입 등을 Protobuf로 직렬화한다. sail-spark-connect/proto/에 .proto 파일이 있고, build.rs가 빌드 시 Rust 코드를 생성한다. Tonic으로 gRPC 서버를 구현하는 전체 흐름을 볼 수 있다.
볼 파일: crates/sail-spark-connect/proto/, crates/sail-spark-connect/src/server.rs
파서 콤비네이터(parser combinator)는 작은 파서 함수들을 조합해 복잡한 문법을 파싱하는 기법이다. Sail은 chumsky 라이브러리로 Spark SQL 방언(HiveQL·스파크 함수·LATERAL VIEW 등)을 파싱한다. 보통 Yacc/Flex 같은 파서 생성기 대신 Rust 코드로 문법을 표현하는 현대적 접근이다.
볼 파일: crates/sail-sql-parser/src/
PyO3는 Rust 코드를 Python 모듈(.pyd/.so)로 컴파일하는 라이브러리다. Maturin은 이 과정을 pip 패키지 빌드·배포까지 자동화해준다. pysail이 pip install pysail로 설치되는 과정 전체가 이 두 도구의 조합이다. AI/데이터 도구(polars, pydantic 등)의 표준 배포 방식이다.
볼 파일: pyproject.toml의 build-system, crates/sail-python/src/lib.rs
SQL 엔진 내부의 4단계를 Sail 크레이트 구조와 대응하며 배울 수 있다. ① SQL 문자열 → AST (sail-sql-parser), ② AST → 논리 계획 (sail-logical-plan), ③ 논리 최적화 (sail-logical-optimizer: 조건 푸시다운·컬럼 가지치기), ④ 물리 계획 (sail-physical-plan: 셔플 방식·조인 알고리즘 결정) → DataFusion 실행. 데이터베이스 내부 원리를 실제 코드로 볼 수 있는 드문 기회.
단일 노드 개발부터 프로덕션 클러스터까지.
| 목적 | 최소 사양 / 방법 |
|---|---|
| 빠른 시작 (pip 설치) | Python 3.10+ · pip install pysail pyspark-client · 수 초 내 서버 기동 가능 |
| 소스 빌드 | Rust 1.95+, Python 3.10+, protobuf-compiler, Maturin. devcontainer 사용 시 자동 설정 |
| 단일 노드 개발/테스트 | 일반 개발 노트북으로 충분. 벤치마크 기준 Spark 대비 22GB vs 54GB 피크 메모리 (60% 절약) |
| 프로덕션 클러스터 | Kubernetes 환경 필수. 워커 Pod는 기동 시 수 MB만 사용, 태스크 수신 시 확장. 공식 벤치마크는 AWS r8g.4xlarge (32 vCPU, 128GB RAM) |
| 스토리지 | S3·GCS·Azure Blob·HDFS·로컬 파일시스템 지원. 로컬 개발 시 Docker Compose로 MinIO(S3 호환) 제공 |
| OS / 아키텍처 | Linux/macOS/Windows. Apple Silicon(M1/M2/M3) 네이티브 빌드 지원 |
| 포트 | 기본 50051 (Spark Connect gRPC). 설정으로 변경 가능 |
Spark 워커는 JVM 기동에 수 초 ~ 수십 초가 걸리고, 메모리도 수백 MB를 기본으로 잡아먹는다. Sail 워커는 Kubernetes Pod로 배포되며 수 MB 메모리에 수십 밀리초 안에 뜬다. 수요에 따라 워커를 빠르게 추가/제거할 수 있어 클라우드 비용을 극적으로 줄인다.
난이도별로 손에 익히는 단계.
pip으로 설치하고 Sail 서버를 기동한 뒤, 기존 PySpark 코드로 연결한다. SELECT 1 + 1부터 spark.range(1000000).filter(...).groupBy(...).count()까지 실행해보며 응답 속도를 체감한다.
# 설치
pip install pysail "pyspark-client"
# 서버 기동
sail spark server --port 50051
# 다른 터미널에서 PySpark로 연결
from pyspark.sql import SparkSession
spark = SparkSession.builder.remote("sc://localhost:50051").getOrCreate()
spark.sql("SELECT 1 + 1").show()
df = spark.range(1_000_000)
df.filter("id % 2 == 0").groupBy().count().show()
CLI 대신 Python 코드 안에서 Sail 서버를 기동하고 종료하는 패턴을 익힌다. 테스트 코드나 Jupyter Notebook에서 Sail을 프로그래밍적으로 제어하는 방식이다.
from pysail.spark import SparkConnectServer
from pyspark.sql import SparkSession
# 서버를 백그라운드로 기동
server = SparkConnectServer(port=50051)
server.start(background=True)
spark = SparkSession.builder.remote("sc://localhost:50051").getOrCreate()
# Parquet 파일 읽고 SQL 쿼리
df = spark.read.parquet("/path/to/data.parquet")
df.createOrReplaceTempView("my_table")
spark.sql("SELECT COUNT(*), AVG(value) FROM my_table GROUP BY category").show()
server.stop()
이미 PySpark 코드베이스가 있다면 호환성 체크 스크립트로 어떤 함수가 지원되는지 스캔한다. 지원 안 되는 함수를 파악하고 대안을 찾거나, Sail 이슈 트래커에 기여 가능성을 검토한다.
# 기존 PySpark 코드 디렉토리를 스캔
python -m pysail.examples.spark.compatibility_check ./my_spark_project
# 결과 예시:
# [OK] pyspark.sql.functions.col
# [OK] pyspark.sql.functions.sum
# [WARN] pyspark.sql.functions.some_experimental_func → 미구현
# 주의: SQL 문자열 내 함수는 감지 안 됨 → 실제 실행 테스트 필수
Docker Compose의 storage 프로파일로 MinIO(로컬 S3 호환 스토리지)를 기동하고, Sail에서 S3 프로토콜로 Parquet 파일을 읽고 쓰는 실습이다. 로컬에서 클라우드 스토리지 연동을 시뮬레이션하는 방법을 익힌다.
# MinIO 기동
docker compose --profile storage up minio -d
# MinIO 콘솔: http://localhost:19001 (sail/password)
# Python에서 S3 스타일로 Parquet 읽기
spark = SparkSession.builder.remote("sc://localhost:50051") \
.config("spark.hadoop.fs.s3a.endpoint", "http://localhost:19000") \
.config("spark.hadoop.fs.s3a.access.key", "sail") \
.config("spark.hadoop.fs.s3a.secret.key", "password") \
.getOrCreate()
df = spark.read.parquet("s3a://my-bucket/data/")
df.show()
kind(로컬 K8s)로 Sail을 클러스터 모드로 배포하고, 워커 Pod가 동적으로 생성되는 과정을 kubectl로 관찰한다. 단일 노드와 클러스터 모드의 실행 방식 차이를 직접 확인한다.
# kind 클러스터 생성
kind create cluster --config k8s/kind-config.yaml
# Sail 이미지 빌드 후 배포
docker build -t sail:latest .
kind load docker-image sail:latest
kubectl apply -f k8s/sail.yaml
kubectl -n sail port-forward service/sail-spark-server 50051:50051
# 쿼리 실행 중 워커 Pod 확인
kubectl -n sail get pods -w
한 주씩 따라가는 "Rust 데이터 엔지니어" 코스.
| 주차 | 주제 | 학습 자료 / 할 일 |
|---|---|---|
| 1주차 | Apache Spark 기초 | PySpark DataFrame API, SQL, RDD 개념. pyspark 튜토리얼 공식 문서. Sail 실습 1 완료 |
| 2주차 | Spark Connect 프로토콜 | Spark Connect 아키텍처 문서. gRPC·Protobuf 기본 개념. Sail의 sail-spark-connect/proto/ 파일 읽기 |
| 3주차 | Apache Arrow 이해 | Arrow 컬럼 포맷 공식 문서. pyarrow로 배열 생성·변환 실습. "왜 행보다 열이 빠른가" 개념 정리 |
| 4주차 | Apache DataFusion 탐구 | DataFusion 깃허브 예제. 커스텀 UDF 등록, LogicalPlan 직접 조작 실습. Sail과의 관계 이해 |
| 5주차 | Rust 기초 + 비동기 | The Rust Book. Tokio 비동기 런타임 기초. async/await, Future 개념 |
| 6주차 | PyO3 + Maturin | PyO3 가이드북. Maturin 튜토리얼. 간단한 Rust 함수를 Python 모듈로 배포 실습 |
| 7주차 | 쿼리 최적화 이론 | 카네기 멜런 15-445 DB 강의(무료). 조건 푸시다운·컬럼 가지치기·조인 순서 최적화 개념 |
| 8주차 | 분산 컴퓨트 + Kubernetes | Kubernetes 공식 튜토리얼. Sail 클러스터 모드(실습 5). Arrow Flight 셔플 동작 원리 분석 |
본문에 나온 용어 빠른 참조.
| 용어 | 의미 |
|---|---|
| Apache Spark | 2014년 출시된 JVM 기반 분산 컴퓨트 엔진. Hadoop MapReduce를 대체. SQL·DataFrame·스트리밍·ML 통합 API 제공. 업계 표준이지만 JVM 오버헤드가 약점. |
| Spark Connect | Spark 3.4부터 도입된 클라이언트-서버 분리 아키텍처. 클라이언트(PySpark)와 서버(실행 엔진)가 gRPC로 통신. Sail이 이 서버 역할을 대체한다. |
| Apache Arrow | 컬럼 기반 인메모리 데이터 포맷 표준. 행 대신 열로 데이터를 저장해 CPU 캐시 효율을 높이고, 언어 간 제로 복사 공유를 가능하게 한다. |
| Apache DataFusion | Rust로 만든 쿼리 실행 엔진 라이브러리. Arrow 위에서 SQL 파싱·최적화·실행을 담당. 벡터화 실행과 SIMD를 활용해 빠르다. Sail의 핵심 실행 계층. |
| 벡터화 실행 | 데이터를 한 행씩이 아니라 컬럼 배열 단위(벡터)로 한꺼번에 처리하는 방식. SIMD CPU 명령어를 활용해 처리량을 수십 배 높인다. |
| SIMD | Single Instruction Multiple Data. CPU 하나의 명령으로 여러 데이터를 동시 처리. 예: 32비트 정수 8개를 한 번에 더하기. 벡터화 실행의 하드웨어 기반. |
| JVM (Java Virtual Machine) | Java·Scala·Kotlin 코드가 돌아가는 런타임. GC(가비지 컬렉션) 일시정지, 높은 메모리 사용, 느린 기동이 Spark 성능의 병목이었다. |
| 가비지 컬렉션 (GC) | JVM이 사용 안 하는 메모리를 자동으로 회수하는 과정. 이 동안 프로그램 실행이 잠깐 멈추는 "GC 일시정지(Stop-the-World)"가 지연을 유발한다. |
| gRPC | HTTP/2 기반의 고성능 원격 프로시저 호출(RPC) 프레임워크. Spark Connect가 클라이언트-서버 통신에 사용. Protobuf로 데이터를 직렬화해 효율적이다. |
| Protobuf | Protocol Buffers. 구글이 만든 이진 직렬화 포맷. JSON보다 작고 빠르며, .proto 파일에서 타입을 정의하면 여러 언어용 코드를 자동 생성한다. |
| 논리 계획 (Logical Plan) | 쿼리의 "무엇을 할 것인가"를 추상적으로 표현한 트리. "테이블 A에서 조건 B로 필터하고 C 컬럼으로 집계" 같은 수준. 어떻게 실행할지는 결정 안 된 상태. |
| 물리 계획 (Physical Plan) | 논리 계획을 "어떻게 실행할 것인가"로 구체화한 것. 어떤 조인 알고리즘(해시 조인 vs 정렬 병합 조인)을 쓸지, 파티션을 어떻게 나눌지 결정된 상태. |
| 셔플 (Shuffle) | 분산 처리에서 데이터를 재분배하는 과정. 예: GROUP BY 시 같은 키를 가진 행들을 같은 워커로 모아야 한다. 네트워크 I/O가 많아 대용량 처리의 병목이 된다. |
| Arrow Flight | Arrow 데이터를 gRPC로 고속 전송하는 프로토콜. Sail 클러스터 모드에서 워커 간 Arrow 배열 셔플에 사용된다. |
| PyO3 | Rust 코드를 Python 확장 모듈(.pyd/.so)로 컴파일해주는 라이브러리. Rust의 성능을 Python에서 쓸 수 있게 해준다. Polars·pydantic 등도 같은 방식. |
| Maturin | PyO3 기반 Rust 프로젝트를 pip 패키지(휠)로 빌드·배포하는 도구. pyproject.toml의 빌드 백엔드로 설정하면 pip install이 자동으로 Rust를 컴파일한다. |
| Delta Lake / Iceberg | 오픈 테이블 포맷. Parquet 파일 위에 트랜잭션·스키마 진화·타임 트래블 등을 추가한다. Sail이 두 포맷 모두 네이티브 지원한다. |
| 파서 콤비네이터 | 작은 파서를 조합해 복잡한 문법을 처리하는 함수형 파싱 기법. Sail의 SQL 파서(chumsky)가 이 방식을 사용한다. Yacc/Flex 대신 Rust 타입 시스템으로 문법을 표현한다. |