LLM WikiAccess-protected knowledge portal
← 스터디 홈
165편 · 약 13분

Apache Arrow IPC와 Flight SQL: 데이터 시스템을 제로카피로 잇는 컬럼형 교환 표준

요약

데이터 파이프라인에서 시스템 간 직렬화·역직렬화 비용은 실제 쿼리 시간보다 오래 걸리는 경우가 많다. Apache Arrow는 이 문제를 컬럼형 인메모리 포맷과 공유 메모리 기반 제로카피(zero-copy) 전송으로 해결한다. Arrow IPC는 Arrow 배치를 프로세스 경계 너머로 보내는 직렬화 표준이고, Arrow Flight는 gRPC 위에서 수십 GB/s 규모의 컬럼형 데이터를 이동시키는 전송 프로토콜이다. Arrow Flight SQL은 여기에 SQL 의미론을 얹어 JDBC/ODBC를 대체하는 고성능 데이터베이스 접속 표준을 제시한다. DuckDB, Polars, Apache DataFusion, BigQuery Storage API, Snowflake Python Connector가 모두 Arrow를 기반으로 동작하는 지금, Arrow를 모르면 현대 데이터 스택의 절반을 모르는 셈이다.


1. 왜 행 지향 직렬화가 느린가

전통적인 JDBC 결과집합은 행 단위로 직렬화된다. 10개 컬럼, 10만 행의 테이블을 가져올 때 벌어지는 일을 추적해보자.

  • CPU 오버헤드: Java 객체(ResultSet) 생성, 타입 변환, 박싱(boxing) 비용이 행마다 반복된다.
  • 메모리 단편화: 행 지향 버퍼는 캐시 라인(64 bytes)을 낭비한다. 분석 쿼리가 특정 컬럼만 스캔해도 전체 행을 읽어야 한다.
  • 직렬화·역직렬화(SerDes): JDBC는 사실상 텍스트 프로토콜(ResultSet.getString)로 작동하거나, 벤더마다 다른 바이너리 포맷을 쓴다. 프로세스 간 공유가 불가능하다.
JDBC / 행 지향
DB 서버: 행 버퍼 직렬화
Obj 생성, 타입 변환, 박싱
↓ 네트워크/소켓
클라이언트: 역직렬화
행 → Java 객체 매핑
↓ 컬럼 추출
DataFrame / Pandas
다시 컬럼 재배열
3단계 복사 + SerDes
vs
Arrow IPC / 컬럼형
DB 서버: Arrow RecordBatch
컬럼 버퍼 포인터 배열
↓ IPC 스트림 / Flight
클라이언트: 버퍼 공유
포인터만 넘김, 복사 없음
↓ 바로 사용 가능
DuckDB / Polars / Pandas
제로카피 스캔
0–1회 복사 + 타입 재해석
JDBC 행 지향 직렬화 vs Arrow 컬럼형 제로카피

2. Arrow 컬럼형 메모리 포맷

Arrow의 핵심은 언어 중립 메모리 레이아웃 명세다. 같은 버퍼를 C, Python, Rust, Java가 동일한 방식으로 해석할 수 있어 언어 경계를 넘어도 복사가 필요 없다.

버퍼 구조

모든 Arrow 배열은 세 가지 버퍼로 이루어진다:

버퍼역할필수 여부
Validity bitmap각 행의 null/non-null 표시 (1비트/행)null 없으면 생략 가능
Value buffer고정 폭 타입(int32, float64 등)의 값 배열항상
Offset buffer가변 폭 타입(string, binary)의 시작/끝 오프셋가변 폭에만

예: ["hello", null, "world"] 를 Arrow string 배열로 표현하면:

  • Validity bitmap: 0b101 (행 0, 2가 유효)
  • Offsets: [0, 5, 5, 10] (hello=0..5, null=5..5, world=5..10)
  • Values: "helloworld" (연속 UTF-8 바이트)

이 레이아웃은 SIMD 벡터화에 최적이다. null 검사를 비트 마스크 AND로 처리하고, 숫자 연산을 128/256비트 레지스터로 묶을 수 있다.

지원 타입 분류

  • Primitive: int8~int64, uint8~uint64, float16/32/64, bool
  • Variable-width: utf8 (string), large_utf8, binary, large_binary
  • Nested: list, large_list, fixed_size_list, struct, map, union
  • Dictionary: 카디널리티가 낮은 문자열을 int32 코드로 압축
  • Extension types: timestamp, date, time, duration, decimal128/256

3. Arrow IPC: 프로세스 경계를 넘는 포맷

Arrow IPC는 RecordBatch(스키마 + 여러 배열의 묶음)를 파일이나 소켓으로 보내는 직렬화 명세다. FlatBuffers를 메타데이터 포맷으로 사용해 스키마를 앞에 한 번 전송하고 이후 배치들을 스트리밍한다.

두 가지 포맷

IPC 스트림 포맷 (.arrow, pipe, socket):

[Schema message]
[RecordBatch message] ×N
[EOS message]

순차 읽기에 최적. Kafka 토픽이나 네트워크 소켓에 적합하다.

IPC 파일 포맷 (.arrow 또는 .arrows, random access):

[Magic: "ARROW1"]
[RecordBatch message] ×N
[Footer: 모든 배치 오프셋 + 스키마]
[Footer length]
[Magic: "ARROW1"]

파일 끝에 footer가 있어 임의 배치에 seek 접근이 가능하다. Parquet의 column group과 유사하나 압축보다 속도를 우선한다.

Feather v2 = IPC File

많이 쓰이는 Feather v2 포맷은 Arrow IPC 파일 포맷의 별칭이다. Pandas/Polars의 to_feather() / read_feather()는 Arrow IPC 파일을 쓰고 읽는다.


4. Arrow Flight: gRPC 위의 고처리량 전송

Arrow Flight는 Arrow IPC 스트림을 gRPC 양방향 스트리밍으로 전달하는 전송 프로토콜이다. JDBC 소켓보다 빠른 이유:

  1. HTTP/2 멀티플렉싱: 여러 스트림이 하나의 TCP 연결을 공유
  2. 백프레셔(backpressure): gRPC 스트리밍이 클라이언트 속도에 맞춰 전송을 조절
  3. 데이터 직렬화 없음: Arrow 버퍼를 그대로 gRPC 바이트 시퀀스로 전송; 내용 재해석 없음

Flight 핵심 RPC

클라이언트
1. GetFlightInfo(descriptor)
3. DoGet(ticket) ×N
(병렬 가능)
→ →
← ←
→ →
← ← (IPC 스트림)
Flight 서버
2. FlightInfo 반환
(endpoint 목록 + ticket)
4. RecordBatch 스트리밍
(Arrow IPC 스트림)
Arrow Flight 데이터 흐름: GetFlightInfo → DoGet
RPC방향용도
GetFlightInfo단방향결과 파티션 수, 스키마, endpoint(ticket) 목록 반환
DoGet서버→클라이언트 스트리밍Arrow IPC 스트림 수신
DoPut클라이언트→서버 스트리밍데이터 적재 (Arrow 업로드)
DoExchange양방향 스트리밍쿼리 + 결과를 한 연결로
ListFlights단방향사용 가능한 데이터셋 목록

병렬 전송의 핵심

GetFlightInfo가 여러 endpoint(서버 주소 + ticket 쌍)를 반환하면, 클라이언트는 각 endpoint에 병렬로 DoGet을 날린다. 샤딩된 테이블에서 각 파티션을 별도 서버에서 동시에 가져오는 구조다.


5. Arrow Flight SQL: JDBC 없는 SQL 쿼리

Arrow Flight SQL은 Flight 위에 SQL 의미론(DDL/DML/스키마 탐색)을 표준화한 프로토콜이다. JDBC/ODBC를 대체하는 고성능 인터페이스로 설계되었다.

주요 커맨드

커맨드용도
CommandStatementQuerySQL 쿼리 실행, Arrow IPC로 결과 반환
CommandGetTables카탈로그/스키마/테이블 목록
CommandGetTableTypes지원 테이블 타입
CommandGetCatalogs카탈로그 목록
CommandPreparedStatementQueryPreparedStatement 준비 + 실행
ActionCreatePreparedStatement서버에 PreparedStatement 등록

지원 엔진 (2024-2026)

엔진상태
Apache DataFusion내장, datafusion-flight-sql 크레이트
DuckDBADBC + Flight SQL 드라이버
Dremio프로덕션 사용 (Arrow Flight Native Client)
InfluxDB IOx주 쿼리 인터페이스
Apache Drill지원
Impala / Starburst실험적

6. ADBC: Arrow 시대의 ODBC

ADBC (Arrow Database Connectivity)는 Arrow를 기본 교환 포맷으로 사용하는 데이터베이스 연결 API다. ODBC는 행 지향으로 설계되어 컬럼형 DB와 쓸 때 변환 비용이 발생한다. ADBC는 이 비용을 없앤다.

ODBC
App (Python/Java)
ODBC Driver Manager
벤더 드라이버
(행 버퍼 SQLFetch)
DB (행→컬럼 변환)
행 지향 API → 컬럼 재구성
vs
ADBC
App (Python/Java/R)
ADBC Driver Manager
ADBC 드라이버
(Arrow RecordBatch)
DB (Arrow 네이티브)
컬럼형 API → 제로카피
ODBC vs ADBC 비교

Python에서 사용 예:

import adbc_driver_flightsql.dbapi as flight_dbapi

conn = flight_dbapi.connect("grpc://localhost:32010")
cursor = conn.cursor()
cursor.execute("SELECT id, amount FROM orders WHERE region='KR'")
# Arrow RecordBatch로 직접 반환
batches = cursor.fetch_arrow_table()   # PyArrow Table

ADBC 1.x 사양(Apache 2024)은 Python, Java, Go, C에서 동일한 API를 제공한다.


7. 생태계: Arrow를 어디서 만나는가

데이터 분석 프레임워크

프레임워크Arrow 활용 방식
Polars내부 메모리 포맷이 Arrow (Rust), 제로카피로 PyArrow 변환
DuckDBArrow 스캔 시 제로카피 (duckdb.arrow(), scan_arrow_ipc())
Apache DataFusionArrow 기반 쿼리 엔진 (Rust, 100% Arrow)
Pandas 2.xArrowDtype — PyArrow 배열을 백엔드로 사용 가능
cuDF (RAPIDS)GPU Arrow 배열 — CPU Arrow와 동일 스키마

클라우드 스토리지/쿼리 서비스

서비스활용
BigQuery Storage APIArrow 스트리밍으로 대용량 내보내기 (v1 GA)
Snowflake Python Connectorfetch_arrow_batches() Arrow 스트리밍
AWS AthenaGetQueryResultsStream — Arrow IPC 지원 (preview)
Azure Synapse AnalyticsArrow Flight 연결 (실험적)

데이터 레이크 포맷

Parquet은 디스크 포맷, Arrow는 인메모리 포맷이다. 이 둘은 경쟁이 아니라 상호 보완이다:

  • Parquet 읽기: 컬럼 디코딩 → Arrow RecordBatch (Apache Parquet 라이브러리가 Arrow로 직접 디코드)
  • Iceberg / Delta / Hudi: 스캔 결과를 Arrow 배치로 반환 (Spark, DataFusion, Trino 등에서 활용)

8. 운영 고려사항과 한계

언제 Flight SQL이 JDBC보다 빠른가

  • 결과집합이 크고 컬럼이 많을 때: 직렬화 차이가 두드러진다. 1M 행 × 20컬럼 숫자 데이터 기준, 벤치마크에서 JDBC 대비 3–10× 빠른 전송이 보고된다.
  • 분석형 쿼리(집계, 광범위 스캔): OLTP 포인트 쿼리는 차이가 적다.
  • Python/Rust 클라이언트: Java보다 Python에서 JDBC 오버헤드가 더 크므로 효과가 더 크다.

한계와 주의점

항목내용
트랜잭션 지원Flight SQL 자체에는 BEGIN/COMMIT 명세가 없음. 엔진마다 ActionBeginTransaction 확장으로 처리
DML 결과INSERT/UPDATE 행수 반환은 엔진 확장. 표준화 진행 중
보안gRPC TLS 필수. mTLS + 토큰 인증은 엔진마다 구현 방식이 다름
스키마 변경스트리밍 중 스키마 변경 불가. 새 연결 필요
메모리 사용량제로카피는 같은 버퍼를 여러 소비자가 공유하므로 해제 시점 관리가 중요 (reference counting)

제로카피의 실제 조건

"제로카피"는 동일 프로세스 내 또는 공유 메모리 시나리오에서만 완전히 성립한다. 네트워크를 통한 Flight 전송은 직렬화 없이 Arrow 버퍼를 그대로 바이트 시퀀스로 전송한다는 의미이지, 네트워크 복사 자체를 없애는 것은 아니다. 이를 직렬화 제로(zero serialization)라고 부르는 것이 더 정확하다.


9. 선택 기준 체크리스트

✓ 분석 쿼리 결과를 Python/Rust로 가져오고 싶다          → ADBC + Flight SQL
✓ 두 데이터 엔진 사이에 대용량 컬럼형 데이터를 이동한다  → Arrow IPC / Flight
✓ Parquet 파일을 pandas/polars로 읽을 때 빠르게 하고 싶다 → read_parquet(…) 기본이 Arrow 경로
✓ Pandas에서 문자열 컬럼 메모리를 줄이고 싶다           → ArrowDtype (pd.ArrowDtype("string[pyarrow]"))
✓ JDBC를 쓰는데 Python 성능이 너무 느리다               → DuckDB ADBC 또는 Arrow Flight SQL 드라이버 교체 검토
✗ 단순 OLTP 포인트 쿼리 (< 100행)                       → JDBC/psycopg2 그대로가 단순하고 충분
✗ 트랜잭션이 필요한 CRUD 작업                           → 전통 드라이버 사용, Flight SQL은 부적합

References

  • https://arrow.apache.org/docs/format/Columnar.html — Apache Arrow 컬럼형 포맷 명세
  • https://arrow.apache.org/docs/format/Flight.html — Arrow Flight 프로토콜 명세
  • https://arrow.apache.org/docs/format/FlightSql.html — Arrow Flight SQL 명세
  • https://arrow.apache.org/adbc/ — ADBC (Arrow Database Connectivity) 공식 문서
  • https://duckdb.org/docs/guides/python/export_arrow — DuckDB Arrow 내보내기
  • https://pola-rs.github.io/polars/user-guide/misc/arrow/ — Polars와 Arrow 관계
  • https://cloud.google.com/bigquery/docs/bigquery-storage-overview — BigQuery Storage API (Arrow 포맷)
  • https://docs.snowflake.com/en/developer-guide/python-connector/python-connector-arrow — Snowflake Arrow 스트리밍
  • https://github.com/apache/arrow-adbc — ADBC GitHub 저장소