Polars 2.0, 5배 더 빠르다는데... 대체 뭐가 바뀐 걸까?

BBetter Stack
컴퓨터/소프트웨어AI/미래기술

스크립트

00:00:00Pandas의 가장 강력한 경쟁자가 버전 2.0 출시를 앞두고 있습니다.
00:00:04Polars 업데이트가 임박했는데, 가장 큰 변화는 아마 예상치 못한 부분일 겁니다.
00:00:09기본적으로 기존에 작성한 모든 쿼리가 다른 엔진에서 동작하게 됩니다.
00:00:14코드 한 줄 바꾸지 않고 말이죠.
00:00:15또한 Polars는 입력한 순서대로 행이 반환된다는 보장을 더 이상 하지 않습니다.
00:00:19퇴보처럼 보일 수 있지만 실제로는 아니며, 첫 번째 변화를 가능하게 만든 대가입니다.
00:00:24Polars가 2.0으로 한층 더 세를 넓히며 Pandas를 바짝 위협하고 있습니다.
00:00:30역대 가장 큰 업데이트를 함께 살펴보겠습니다.
00:00:37자, 그동안 Pandas는 거의 모든 데이터 작업에서 표준으로 자리 잡아 왔습니다.
00:00:43단순하고 대중적이라 데이터 분석뿐만 아니라 머신러닝에서도 핵심적인 역할을 하죠.
00:00:49그러다 얼마 전 Polars가 등장했습니다.
00:00:51Polars는 항상 Pandas보다 빨랐는데요.
00:00:53그 부분도 이따가 다루겠습니다.
00:00:54이제 이번 대규모 2.0 업데이트를 통해 더 널리 채택될지도 모릅니다.
00:01:00간단히 배경을 말씀드리면, Polars에는 조용히 존재해 온 두 개의 엔진이 있습니다.
00:01:05대부분이 사용해 온 방식은 인메모리(in-memory) 엔진입니다.
00:01:08일종의 대형 창고라고 생각하시면 됩니다.
00:01:10창고 바닥 전체에 데이터셋을 전부 올려둔 뒤, 쿼리의 각 단계를 데이터 전체에 대해 실행하죠.
00:01:17그리고 이건 엄청나게 빠릅니다.
00:01:19모든 데이터가 RAM에 들어가는 동안에는 말이죠.
00:01:21스트리밍 엔진은 동작 방식이 다릅니다.
00:01:23하나의 거대한 창고 대신, 조립 라인에 가까운 방식으로 작동합니다.
00:01:27Polars는 데이터를 개발진이 Morsel이라 부르는 작은 조각들로 잘라냅니다.
00:01:32이 조각들은 CPU 캐시에 딱 맞도록 크기가 맞춰집니다.
00:01:36그러고 나서 쿼리 플랜을 따라 전달되는 것이죠.
00:01:39이제 전체 데이터셋이 처리되길 기다릴 필요 없이 쿼리의 각 부분이 계속 진행될 수 있습니다.
00:01:43속도가 향상되는 비결이 바로 여기에 있습니다.
00:01:44궁극적으로는 메모리보다 큰 데이터를 다루기 위한 방향이기도 합니다.
00:01:49하지만 주의할 점이 있습니다.
00:01:50조립 라인에 작업자 8명이 있을 때, 먼저 시작했다고 먼저 끝나는 것은 아닙니다.
00:01:56Polars도 마찬가지입니다.
00:01:58따라서 2.0에서는 조인, 그룹화, 언피벗 등에 대해 문서에서도 말 그대로
00:02:04행 순서를 더 이상 보장하지 않습니다.
00:02:06명시적으로 요청하지 않는 이상 말이죠.
00:02:08작업 흐름을 빨라지게 하는 코딩 툴에 관심이 있다면 꼭 구독해 주세요.
00:02:11유용한 영상이 지속적으로 업로드됩니다.
00:02:13자, 그럼 실제 모습이 어떤지 보여드리겠습니다.
00:02:15uv pip install --pre polars 명령어를 입력하겠습니다.
00:02:18--pre 플래그를 기억해 두세요.
00:02:20잠시 후에 다시 설명해 드리겠습니다.
00:02:22좋습니다.
00:02:222, 1, 0 키를 가진 LazyFrame이 있습니다.
00:02:27다른 프레임과 Left Join을 한 뒤 collect를 호출해 보겠습니다.
00:02:32결과를 한번 보세요.
00:02:34Polars 1과 동일한 코드입니다.
00:02:35순서가 더 이상 보장되지 않습니다.
00:02:37똑같은 코드인데 동작이 달라진 것이죠.
00:02:39이제 Join 연산에 maintain_order=”left”를 추가해 봅니다.
00:02:43다시 실행해 보겠습니다.
00:02:44여기서 다시 돌려볼게요.
00:02:45그러면 원래의 순서가 돌아옵니다.
00:02:47즉, Polars가 데이터를 무작위로 섞는 것이 아닙니다.
00:02:51순서가 중요하다면 직접 지정하라는 식입니다.
00:02:54직접 명시를 해줘야 하죠.
00:02:56그리고 또 세심한 디테일이 있습니다.
00:02:58쿼리에 explain() 함수를 실행해 보세요.
00:03:01동작 방식을 숨기지 않습니다.
00:03:03어디를 봐야 하는지만 알면 됩니다.
00:03:05이것이 가장 큰 동작상의 변화입니다.
00:03:06하지만 2.0은 신기한 게, 딱히 기능 추가 중심의 릴리즈가 아닙니다.
00:03:10사실상 리팩토링 및 정리 작업에 가깝습니다.
00:03:12그리고 정리된 분량이 제법 많습니다.
00:03:15원래 Pandas 스타일이었던 read_csv는 제거되고 내부적으로 scan_csv().collect() 방식을 사용하는 형태로 정리되었습니다.
00:03:22LazyFrame.profile()도 삭제되었습니다.
00:03:25melt는 이제 unpivot으로 변경되었습니다.
00:03:28join_nulls는 nulls_equal로 바뀌었습니다.
00:03:30정수형(Integer)을 범주형(Categorical)으로 바로 캐스팅하는 기능은 제거되었습니다.
00:03:35대신 cat.to()를 사용하여 전환할 수 있습니다.
00:03:38문자열을 Date 유형으로 직접 캐스팅하는 기능도 제거되었습니다.
00:03:41str.to_date()를 사용해야 하죠.
00:03:43부호 있는 정수와 부호 없는 64비트 정수를 더할 때, Float으로 변경되어 정밀도를 조용히 잃는 대신 Int128 타입을 반환합니다.
00:03:53변경 사항은 이뿐만이 아닙니다.
00:03:55concat 함수도 개선되었는데요.
00:03:57의도를 임의로 추측하는 대신, 높이(행 수)가 일치하지 않으면 거부합니다.
00:04:02이는 사실 Polars 측의 현명한 선택입니다.
00:04:05제거된 기능을 호출하면 AttributeRemovedError가 발생합니다.
00:04:09에러 메시지를 통해 대체된 기능을 알려주죠.
00:04:12melt를 호출하면 에러 메시지에서 index와 on 인자를 써서 unpivot을 사용하라고 명시해 줍니다.
00:04:17이 모든 에러 메시지가 사실상 올바른 사용 가이드 역할을 합니다.
00:04:20코드를 실행하고, 오류가 나면 메시지에 따라 해당 부분만 고치며 계속 진행하면 됩니다.
00:04:26이것이 많은 이들이 Polars를 메인 도구로 채택하게 된 이유이기도 합니다.
00:04:31Pandas는 수많은 문제를 런타임 시점으로 미룹니다.
00:04:35Polars는 이러한 방식으로 문제들을 사전에 잡아내고자 합니다.
00:04:37collect_schema()를 호출하면 단 한 행도 읽기 전에 타입 오류를 미리 파악할 수 있습니다.
00:04:44앞으로 벌어질 상황을 미리 예측하는 것이죠.
00:04:47그리고 점점 더 효율적으로 발전하고 있습니다.
00:04:48그렇다면 Polars의 위치는 정확히 어디일까요?
00:04:50우선 DuckDB는 SQL 중심입니다.
00:04:53Polars는 Python용으로 구축된 표현식 API이고요.
00:04:56Dask와 Spark도 있습니다.
00:04:58그것들은 분산 처리용이죠.
00:04:59Polars는 단일 머신 처리에 집중합니다.
00:05:01오픈소스 라이브러리 자체는 오픈소스이지만,
00:05:04Polars Cloud는 그렇지 않습니다.
00:05:05이 핵심적 차이는 성능 부분에 들어가면 중요해집니다.
00:05:09발표에 따르면 새 스트리밍 엔진이 무려 5배나 더 빠르다고 합니다.
00:05:13몇 년 동안 Polars를 써본 결과 Pandas보다 빠르긴 하지만, 5배 같은 대단한 속도 향상은 경험해보지 못했는데요.
00:05:21더 큰 데이터셋에서 한번 테스트해보세요.
00:05:23확실히 속도 차이를 체감할 수 있을 겁니다.
00:05:25실행이나 데이터셋마다 다르겠지만, 체감 성능과 런타임 수치로 확실히 확인하실 수 있습니다.
00:05:30그리고 이것이 이번 릴리즈에서 가장 중요한 차이점일 것입니다.
00:05:34엔진이 디스크로 데이터를 넘겨 처리하는 진정한 아웃 오브 코어(Out-of-core) 실행 기능은 아직 반영되지 않았습니다.
00:05:40따라서 현재의 스트리밍은 청크(chunk) 단위 청크 및 파이프라인 처리를 의미합니다.
00:05:43데이터셋 크기가 마법처럼 RAM보다 커질 수 있다는 뜻은 아닙니다.
00:05:465배라는 수치는 Polars 측의 자체 기대치입니다.
00:05:50포스트 어디에도 벤치마크 표는 제공되지 않습니다.
00:05:53그리고 솔직히 대부분은 크게 개의치 않는 분위기입니다.
00:05:55많은 사용자가 화려함은 없어도 실용적인 이번 업데이트에 만족해하는 듯합니다.
00:05:59기능이 대거 쏟아져 나오지 않아서,
00:06:01새로 익혀야 할 것도 없습니다.
00:06:02코딩 중에 발생하는 에러 메시지가 무얼로 대체해야 할지 친절히 알려주니까요.
00:06:07하위 호환성을 깨고, 코드를 정리하며, 시맨틱 버저닝(SemVer) 규칙을 정석대로 따르는 모습입니다.
00:06:12하지만 두 가지 불만이 반복적으로 제기되고 있습니다.
00:06:14첫 번째는 행 순서 문제입니다.
00:06:15maintain_order의 기본값이 false이면 코드에 치명적인 버그가 생길 수 있습니다.
00:06:19아무런 에러도 발생하지 않기 때문이죠.
00:06:21연산 수치 자체는 완벽히 맞을 수 있습니다.
00:06:23단지 다른 순서의 행에 매핑되어 있을 뿐입니다.
00:06:26이건 예외 발생보다 알아채기가 훨씬 어렵습니다.
00:06:29두 번째 불만은 '스트리밍'이라는 용어입니다.
00:06:32아직 완전한 아웃 오브 코어가 아닌 엔진에 사용하기엔 혼란스럽다는 의견입니다.
00:06:38그리고 실제 릴리즈 후보(RC) 버그들도 존재합니다.
00:06:41RC가 공개된 이후 높은 우선순위 이슈가 발생했는데,
00:06:44스트리밍 엔진에서 group_by_dynamic 사용 시 datetime 범위를 벗어나는 오류가 납니다.
00:06:49limit 메소드를 호출할 때도,
00:06:51Join 이후 limit이 조기 종료되지 않는 문제가 있습니다.
00:06:54또한 문자열을 Datetime으로 변환 시 에러를 내는 대신 null을 반환하기도 합니다.
00:06:58아까 --pre 설치 옵션을 기억해두라고 말씀드린 이유가 이것 때문입니다.
00:07:022.0 이전의 사전 버전이니까요.
00:07:04이건 어디까지나 릴리즈 후보(RC) 버전입니다.
00:07:06그리고 지금은 딱 그 단계처럼 동작합니다.
00:07:08그건 괜찮습니다.
00:07:09그렇죠?
00:07:09아직 정식 완제품 버전은 아니니까요.
00:07:11한 가지 더 말씀드리면,
00:07:12Rust 크레이트는 여전히 0.55 버전입니다.
00:07:15따라서 Rust 환경에서 Polars를 사용하는 경우 아직 2.0을 쓸 수 없습니다.
00:07:19하지만 대부분의 개발자분들은 Python을 사용할 것으로 생각됩니다.
00:07:24그렇다면 지금 업그레이드해야 할까요?
00:07:25새 프로젝트라면 저라도 적용할 것 같습니다.
00:07:27맞죠?
00:07:27우리는 새로운 기술과 업데이트에 적응해 나가는 법이니까요.
00:07:30기존 파이프라인이 이미 sink_parquet이나 sink_csv를 쓰고 있었다면 사실상
00:07:34이미 스트리밍 방식을 계속 써오고 계셨던 셈입니다.
00:07:36하지만 관망하는 게 나은 유스케이스들도 있습니다.
00:07:39그렇죠?
00:07:39코드가 행 순서에 의존적이라면 막연히 업그레이드하지 마세요.
00:07:43뒤에 sort 연산이 붙어있지 않은 group_by 구문들을 grep으로 검색해 보세요.
00:07:47group_by_dynamic을 쓰는 중이라면 안정화될 때까지 기다리는 것을 추천합니다.
00:07:51어쨌든 어디로 향하고 있는지 방향성을 보는 것은 흥미롭습니다.
00:07:54이것이 결국 2.0이 되겠지만, 아직 정식 출시는 아닙니다.
00:07:57미리 적응해보실 수는 있겠지만 아직 완전하진 않죠.
00:08:00제가 계속 생각하게 되는 점은 이번 릴리즈가 참 이례적이라는 것입니다.
00:08:03Polars 2.0은 새로운 기능을 전혀 추가하지 않았습니다.
00:08:07그러면서도 기존 코드가 작동하는 방식에는 변화를 줍니다.
00:08:10새 엔진은 내 컴퓨터에서 더 빠르긴 하지만, 5배까지 빠르진 않았습니다.
00:08:14따라서 2.0이 기대되는 이유는 새로운 기능이 대거 추가되어서가 아닙니다.
00:08:18메이저 버전 업그레이드를 통해 우리가 이미 사용 중인 모든 기능의 기반을
00:08:24대대적으로 정리 정돈하기에 기대되는 것입니다.
00:08:26BetterStack의 Josh였습니다.
00:08:28이와 같은 코딩 팁과 유용한 정보를 더 원하시면 꼭 구독 부탁드립니다.
00:08:30다음 영상에서 뵙겠습니다.

핵심 요약

Polars 2.0은 신규 기능 추가보다 기존 API 정리와 엔진 변경에 집중한 메이저 업데이트로, 행 순서 미보장 특성에 맞춰 maintain_order 옵션을 명시하고 변경된 API에 대응해야 한다.

하이라이트

  • Polars 2.0은 새로운 기능을 추가하는 대신, 기본 엔진을 기존 인메모리 방식에서 새로운 스트리밍 엔진으로 전환한다.

  • 조인, 그룹화, 언피벗 연산 시 행 순서를 더 이상 보장하지 않으며, 순서를 유지하려면 maintain_order=”left” 옵션을 명시해야 한다.

  • read_csv 및 profiling 함수가 제거되고 melt가 unpivot으로, join_nulls가 nulls_equal로 명칭이 변경되는 등 API 대정리가 이뤄졌다.

  • 부호 있는 정수와 부호 없는 64비트 정수를 더할 때 Float 대신 Int128 타입을 반환하여 정밀도 손실을 방지한다.

  • 제거된 기능을 호출하면 AttributeRemovedError가 발생하며, 에러 메시지에서 올바른 대체 인자와 메서드를 직접 안내한다.

  • 스트리밍 엔진의 아웃 오브 코어(Out-of-core) 기능은 아직 반영되지 않았으며, group_by_dynamic 연산 시 datetime 범위 오류 등 RC 버전 이슈가 존재한다.

타임라인

Polars 2.0의 핵심 변화와 행 순서 보장 중단

  • 기존 쿼리가 별도의 코드 변경 없이 새로운 엔진에서 동작한다.
  • 입력된 행의 반환 순서를 기본적으로 보장하지 않는다.
  • 인메모리 엔진과 스트리밍 엔진의 구조적 차이가 속도 향상과 동작 변화를 만들어낸다.

기존 인메모리 엔진은 모든 데이터를 RAM에 올려둔 뒤 창고 바닥 전체 데이터를 한 번에 처리하는 방식이다. 반면 스트리밍 엔진은 데이터를 CPU 캐시 크기에 맞춘 Morsel이라는 작은 조각으로 잘라 조립 라인처럼 전달한다. 이 과정에서 병렬 작업으로 인해 연산 결과의 행 순서가 달라질 수 있다.

행 순서 지정 방법과 릴리즈의 리팩토링 내역

  • Join 연산에 maintain_order=”left” 인자를 명시하면 원래 순서를 유지할 수 있다.
  • read_csv, LazyFrame.profile() 등 Pandas 스타일의 구형 메서드들이 삭제되었다.
  • 정수와 문자열의 직접적인 캐스팅 방식이 변경되었으며 잘못된 호출에는 AttributeRemovedError가 안내된다.

2.0 업데이트는 단순한 기능 추가가 아닌 코드베이스 정리에 중점을 둔다. melt는 unpivot으로, join_nulls는 nulls_equal로 명칭이 바뀌었고 문자열을 날짜로 바꿀 때는 str.to_date()를 사용해야 한다. 제거된 메서드 호출 시 발생하는 AttributeError 메시지가 올바른 사용법을 안내하므로 런타임 오류를 사전에 방지할 수 있다.

Polars의 위상 및 스트리밍 엔진 성능의 실제

  • Polars는 단일 머신 처리에 집중하는 Python 표현식 API이다.
  • 개발진이 주장하는 5배 성능 향상은 디스크 기반 아웃 오브 코어가 아닌 청크 단위 파이프라인 처리 기준이다.
  • 기존 코드가 하위 호환성을 일부 깨뜨리더라도 SemVer 규칙을 충실히 따른다.

DuckDB가 SQL 중심이고 Spark이 분산 처리용인 반면, Polars는 단일 머신 성능 극대화에 초점을 맞춘다. 디스크로 데이터를 넘겨 RAM 크기 이상의 데이터셋을 다루는 진정한 아웃 오브 코어 실행은 아직 구현되지 않았다. 공식 벤치마크 표는 제공되지 않지만 대용량 데이터셋에서 체감 속도 향상이 나타난다.

업데이트 시 주의할 위험 요소와 RC 버그

  • 행 순서 비보장 특성으로 인해 오류 없이 잘못된 결과 데이터가 생성될 수 있다.
  • group_by_dynamic 및 limit 메서드 관련 릴리즈 후보(RC) 버그가 존재한다.
  • Rust 크레이트는 여전히 0.55 버전에 머물러 있어 Python 사용자 중심으로 적용 가능하다.

행 순서 의존성이 있는 코드에 maintain_order 옵션 없이 업그레이드를 적용하면 예외 없이 계산 값이 잘못된 위치에 매핑될 위험이 있다. 현재 RC 버전에서는 group_by_dynamic 사용 시 datetime 범위를 벗어나는 버그가 존재한다. 기존 파이프라인 중 group_by 연산을 사용하는 환경은 정식 버전에 맞춰 안정화될 때까지 관망하는 것이 권장된다.

커뮤니티 글

아직 글이 없습니다. 이 영상에 대한 첫 번째 글을 작성해 보세요!

이 영상에 대해 글쓰기