러스트로 작성된 데이터프레임 라이브러리 Polars가 10월 6일(현지시간) 메이저 버전 2.0을 공개했다.
가장 큰 변화는 LazyFrame의 collect가 기본으로 스트리밍 엔진을 쓰게 된 것이다. 메모리를 넘는 데이터를 디스크로 넘기는 out-of-core 처리가 기본으로 켜졌고, 대신 join·group_by 결과의 행 순서는 기본적으로 보장되지 않는다. 기존 파이프라인을 그대로 올리면 결과 순서가 달라질 수 있다.
- Polars 2.0에서
LazyFrame.collect()의 기본 실행기가 스트리밍 엔진으로 바뀌었고, RAM 약 80%에서 디스크로 넘기는 out-of-core 초기 버전이 기본 활성화됐다. - SQL이 일급 인터페이스로 격상됐고, Arrow MapType을 그대로 다루는 네이티브
Map타입이 추가됐다. 데이터 불일치는 암묵 처리 대신 기본 에러로 바뀌었다. - 스트리밍 엔진은 join·group_by·unpivot의 행 순서를 기본 보장하지 않는다. 순서에 의존하는 코드는
maintain_order=True를 붙여야 한다.
PART 01무슨 일이 있었나
Polars 창시자 리치 빙크(Ritchie Vink)가 10월 6일 공식 블로그에 2.0 릴리스 글을 올렸다. Polars는 판다스(pandas)의 대안으로 자리 잡은 러스트 기반 데이터프레임 라이브러리로, 파이썬 바인딩을 통해 주로 쓰인다. 1.0이 API 안정화의 의미였다면 2.0은 엔진의 기본값 자체를 바꾸는 릴리스다.
Polars는 오래전부터 두 개의 실행기를 갖고 있었다. 데이터를 메모리에 다 올려 한 번에 처리하는 인메모리 엔진과, 데이터를 덩어리로 흘려 보내며 처리하는 스트리밍 엔진이다. 그동안 스트리밍은 옵션이었는데 2.0부터는 collect를 부르면 스트리밍이 기본으로 돈다. 팀은 블로그에서 오류가 파이프라인 20분 뒤가 아니라 처음부터 드러나야 한다며, 엄격한 동작을 기본으로 삼았다고 밝혔다.
PART 02핵심 변경 사항
Map 타입은 작지만 실무 체감이 큰 변화다. 파케이(Parquet)나 Arrow 데이터에 키-값 맵 컬럼이 있으면 지금까지는 키와 값을 가진 구조체의 리스트로 펼쳐져 들어왔고, 특정 키를 꺼내려면 리스트를 돌며 필터링해야 했다. 2.0에서는 map.get으로 고정 키나 다른 컬럼 값을 키로 바로 꺼낼 수 있다.
PART 03성능 주장은 어떻게 봐야 하나
팀은 TPC-H와 TPC-DS에서 파생한 데이터로 DuckDB·DataFusion과 비교한 벤치마크를 함께 공개했다. 쿼리당 5회 핫 런, 60초 타임아웃 조건이며, 공식 TPC 결과와는 비교할 수 없다고 명시했다. 16 vCPU에서 192 vCPU로 늘렸을 때 얼마나 빨라지는지(SF100 기준)를 보면 다음과 같다.
눈여겨볼 부분은 팀이 약점도 같이 적었다는 점이다. SF10처럼 작은 규모에서는 Polars가 TPC-H에서 동률, TPC-DS에서 1.8배 느렸고, 192스레드로 가면 상수 오버헤드가 생겨 작은 쿼리가 손해를 본다고 인정했다. 이 문제는 다음 릴리스에서 고칠 계획이라고 한다. 또 DataFusion이 타임아웃되거나 메모리가 부족했던 일부 쿼리(TPC-DS q72·q67, TPC-H q18)는 모든 엔진에서 제외했다. 벤더가 직접 낸 벤치마크인 만큼 자기 워크로드에서 다시 재 보는 것이 맞다.
PART 04업그레이드 전에 할 일
head, first, 인덱스 기반 접근, 정렬 없는 CSV 출력이 이어지는 곳을 찾아 maintain_order=True를 붙이거나 명시적으로 sort한다.collect_schema()는 데이터를 읽지 않고 타입을 해석하므로 빠르다. 파이프라인마다 기대 스키마와 비교하는 테스트를 두면 2.0의 엄격 모드로 생긴 에러를 배포 전에 잡을 수 있다.List(Struct)로 가정하고 explode하던 코드는 깨질 수 있다. 새 map.* 메서드로 바꾸면 코드가 짧아진다.업그레이드 가이드에는 릴리스 글에 없는 세부 변경도 정리돼 있다. pl.read_csv는 내부적으로 scan_csv(...).collect()를 호출하도록 바뀌면서 n_threads·batch_size 같은 인자가 빠졌고, LazyFrame.profile()은 제거됐다. 빈 리스트를 explode()하면 예전처럼 null 한 행이 아니라 0행이 나오며, 옛 동작이 필요하면 empty_as_null=True를 줘야 한다. 수평 pl.concat은 높이가 같아야 하고, 부호 있는 정수와 UInt64를 섞어 계산하면 손실 있는 Float64 대신 Int128이 나온다. SQL은 파싱과 해석이 collect() 시점으로 미뤄져 에러가 늦게 드러난다는 점도 적혀 있다.
polars<2로 고정해 두고, 별도 브랜치에서 같은 입력에 대해 1.x와 2.0 결과를 정렬 후 비교하는 회귀 테스트를 먼저 돌려 보는 방법이 안전하다.로드맵으로는 join·group_by의 out-of-core 지원, 높은 CPU 수에서의 확장성 개선, 분산 실행 제품 Polars Cloud 강화, 지리공간 확장 GeoPolars 초기 작업이 언급됐다. 단일 머신 분석 도구로 출발한 Polars가 DuckDB와 마찬가지로 "메모리보다 큰 데이터"와 SQL 사용자를 정면으로 겨냥하기 시작했다는 신호로 읽힌다.
자주 묻는 질문
Polars 2.0에서 가장 크게 바뀐 점은 무엇인가요?
LazyFrame의 collect가 기본으로 스트리밍 엔진을 쓰고, 메모리를 넘으면 디스크로 넘기는 out-of-core 처리가 기본 활성화됐다는 점이다. 함께 SQL 일급화, 네이티브 Map 타입, 엄격한 데이터 검증이 들어갔다.
Polars 2.0으로 올리면 결과가 달라질 수 있나요?
그렇다. 스트리밍 엔진은 join·group_by·unpivot의 행 순서를 기본적으로 보장하지 않는다. 순서가 중요하면 maintain_order=True를 지정하거나 명시적으로 정렬해야 한다. 또 1.x에서 암묵 처리되던 타입 불일치가 에러로 바뀔 수 있다.
Polars 2.0은 DuckDB보다 빠른가요?
Polars 팀이 공개한 TPC-H·TPC-DS 파생 벤치마크에서는 대부분의 항목에서 가장 빨랐고, 16→192 vCPU 확장률도 높았다. 다만 작은 규모(SF10) TPC-DS에서는 DuckDB보다 느렸고, 벤더 자체 측정이라 자기 워크로드로 확인하는 것이 좋다.
out-of-core는 모든 연산에 적용되나요?
아니다. 현재는 sort, 윈도 함수, 다수 표현식이 디스크로 넘어갈 수 있고, join과 group_by의 out-of-core 지원은 이후 버전에서 추가될 예정이다. 기본 스필 시작점은 RAM 약 80%, 디스크 예산은 64GB다.

댓글 0