이미지 로딩 중...

Polars 데이터 로드 및 저장 완벽 가이드 - 슬라이드 1/9
A

AI Generated

2025. 11. 14. · 2 Views

Polars 데이터 로드 및 저장 완벽 가이드

Python Polars 라이브러리를 사용한 효율적인 데이터 로드 및 저장 방법을 다룹니다. CSV, Parquet, JSON 등 다양한 파일 형식을 다루는 실무 중심의 가이드입니다.


목차

  1. CSV 파일 읽기와 쓰기
  2. Parquet 파일 활용하기
  3. JSON 데이터 다루기
  4. Excel 파일 읽기와 쓰기
  5. 데이터베이스 연동 (SQL)
  6. 스트리밍 및 청크 단위 처리
  7. 압축 파일 직접 읽기
  8. 메모리 맵 파일 활용

1. CSV 파일 읽기와 쓰기

시작하며

여러분이 데이터 분석 프로젝트를 시작할 때 가장 먼저 마주하는 것은 무엇인가요? 바로 CSV 파일일 것입니다.

수백만 행의 고객 데이터를 읽어들이는데 Pandas로는 10분이 넘게 걸려 답답했던 경험, 누구나 있으실 겁니다. 이런 문제는 대용량 데이터를 다루는 현업에서 정말 치명적입니다.

메모리 부족으로 프로그램이 다운되거나, 데이터 타입이 자동으로 잘못 추론되어 분석 결과가 왜곡되기도 합니다. 바로 이럴 때 필요한 것이 Polars의 CSV 읽기/쓰기 기능입니다.

Pandas보다 5-10배 빠른 속도와 메모리 효율성으로 대용량 CSV를 손쉽게 처리할 수 있습니다.

개요

간단히 말해서, Polars의 read_csvwrite_csv는 CSV 파일을 빠르고 안전하게 읽고 쓰는 기능입니다. 실무에서 로그 데이터나 거래 내역 같은 대용량 CSV를 다룰 때, 속도와 메모리는 생산성에 직결됩니다.

예를 들어, 1억 행의 거래 데이터를 분석해야 하는 경우, Pandas로는 불가능하지만 Polars는 수 초 내에 처리 가능합니다. 기존 Pandas에서는 pd.read_csv()로 전체 파일을 메모리에 로드했다면, Polars는 Lazy 모드로 필요한 부분만 효율적으로 읽어들일 수 있습니다.

Polars의 핵심 특징은 병렬 처리, 자동 타입 추론, 메모리 최적화입니다. 이러한 특징들이 대용량 데이터 처리에서 엄청난 성능 차이를 만들어냅니다.

코드 예제

import polars as pl

# CSV 파일 읽기 - 자동 타입 추론
df = pl.read_csv("sales_data.csv")

# 특정 컬럼만 선택하여 읽기 (메모리 절약)
df = pl.read_csv("sales_data.csv", columns=["date", "amount", "customer_id"])

# 데이터 타입 명시적 지정
df = pl.read_csv("sales_data.csv", dtypes={"amount": pl.Float64, "customer_id": pl.Utf8})

# CSV로 저장 - 압축 옵션 포함
df.write_csv("output.csv")

# 구분자 변경 및 헤더 제어
df.write_csv("output.tsv", separator="\t", include_header=True)

설명

이것이 하는 일: Polars는 CSV 파일을 읽어 DataFrame으로 변환하거나, DataFrame을 CSV로 저장합니다. 내부적으로 Rust 기반의 병렬 처리 엔진이 동작하여 멀티코어를 최대한 활용합니다.

첫 번째로, pl.read_csv()는 파일을 읽으면서 자동으로 각 컬럼의 데이터 타입을 추론합니다. 이 과정에서 날짜, 숫자, 문자열을 정확히 구분하여 메모리를 최적화합니다.

예를 들어, "2024-01-01" 형식의 문자열을 자동으로 Date 타입으로 변환합니다. 그 다음으로, columns 파라미터를 사용하면 필요한 컬럼만 선택적으로 읽어들입니다.

100개의 컬럼 중 5개만 필요하다면, 나머지 95개는 메모리에 로드조차 하지 않아 엄청난 성능 향상을 가져옵니다. dtypes 파라미터로 데이터 타입을 명시하면 자동 추론의 오류를 방지할 수 있습니다.

고객ID가 숫자로만 이루어져 있어도 실제로는 문자열로 다뤄야 하는 경우, 이 옵션으로 정확히 제어할 수 있습니다. write_csv()는 DataFrame을 CSV로 저장하는데, separator 옵션으로 쉼표 대신 탭이나 파이프 등을 구분자로 사용할 수 있습니다.

다른 시스템과의 데이터 연동 시 매우 유용합니다. 여러분이 이 코드를 사용하면 기존 Pandas 대비 월등히 빠른 데이터 로딩, 적은 메모리 사용량, 그리고 타입 안정성을 얻을 수 있습니다.

특히 일일 배치 작업에서 처리 시간을 몇 시간에서 몇 분으로 단축시킬 수 있습니다.

실전 팁

💡 대용량 CSV는 columns 옵션으로 필요한 컬럼만 읽어 메모리를 절약하세요. 전체를 읽었다가 나중에 필터링하는 것보다 10배 이상 빠릅니다.

💡 데이터 타입 추론이 잘못될 때는 dtypes 파라미터로 명시적 지정하세요. 특히 우편번호나 전화번호처럼 숫자로 보이지만 문자열인 경우 주의가 필요합니다.

💡 null_values 옵션으로 "NA", "N/A", "missing" 등 다양한 결측치 표현을 통일하세요. 데이터 품질 관리에 필수적입니다.

💡 infer_schema_length를 늘려 데이터 타입 추론의 정확도를 높이세요. 기본값은 처음 100행만 보지만, 1000이나 10000으로 설정하면 더 안전합니다.

💡 Lazy 모드(scan_csv)를 사용하면 필요한 연산만 실행하여 성능을 극대화할 수 있습니다. 쿼리 최적화가 자동으로 이루어집니다.


2. Parquet 파일 활용하기

시작하며

여러분이 매일 같은 CSV 파일을 반복해서 읽어들일 때마다 몇 분씩 기다려본 적 있나요? 100MB CSV를 읽는데 30초, 데이터 타입 변환에 또 10초...

이런 시간 낭비가 쌓이면 하루 몇 시간을 그냥 날리게 됩니다. 이런 문제는 CSV의 구조적 한계 때문입니다.

CSV는 텍스트 기반이라 압축도 안 되고, 읽을 때마다 타입 추론을 처음부터 다시 해야 합니다. 게다가 컬럼 일부만 읽고 싶어도 전체를 스캔해야 하죠.

바로 이럴 때 필요한 것이 Parquet 파일 포맷입니다. 컬럼 기반 저장, 자동 압축, 메타데이터 포함으로 CSV보다 10배 빠른 읽기 속도를 제공합니다.

개요

간단히 말해서, Parquet는 빅데이터용 컬럼 기반 바이너리 파일 포맷으로, Polars에서 가장 추천하는 저장 방식입니다. 데이터 분석 워크플로우에서 중간 결과물을 저장하거나, 대용량 데이터를 효율적으로 공유할 때 Parquet는 필수입니다.

예를 들어, 전처리된 데이터셋을 팀원들과 공유할 때 100MB CSV 대신 10MB Parquet를 사용하면 전송도 빠르고 로딩도 빠릅니다. 기존 CSV로 저장했다면 파일 크기도 크고 로딩도 느렸지만, Parquet는 자동 압축으로 파일 크기를 70% 줄이고 로딩 속도는 10배 빠르게 만듭니다.

Parquet의 핵심 특징은 컬럼 기반 저장(필요한 컬럼만 읽기 가능), 자동 압축(Snappy, GZIP 등), 스키마 내장(타입 정보 포함)입니다. 이러한 특징들이 데이터 엔지니어링에서 표준으로 자리잡은 이유입니다.

코드 예제

import polars as pl

# CSV를 읽어서 Parquet로 저장 (압축 자동 적용)
df = pl.read_csv("large_data.csv")
df.write_parquet("large_data.parquet")

# Parquet 파일 읽기 - 매우 빠름
df = pl.read_parquet("large_data.parquet")

# 특정 컬럼만 선택하여 읽기 (컬럼 기반 저장의 장점)
df = pl.read_parquet("large_data.parquet", columns=["date", "amount"])

# 압축 알고리즘 선택
df.write_parquet("output.parquet", compression="gzip")  # 더 작은 파일
df.write_parquet("output.parquet", compression="snappy")  # 더 빠른 속도

# 여러 Parquet 파일을 한번에 읽기
df = pl.read_parquet("data/*.parquet")

설명

이것이 하는 일: Polars는 DataFrame을 Parquet 포맷으로 저장하거나 읽어들입니다. 내부적으로 Apache Arrow 포맷과 호환되어 제로카피로 데이터를 교환할 수 있습니다.

첫 번째로, write_parquet()는 DataFrame을 컬럼 단위로 분해하여 저장합니다. 각 컬럼은 독립적으로 압축되고, 메타데이터에는 타입 정보, 통계값(min/max), 인덱스 정보가 포함됩니다.

이 덕분에 나중에 읽을 때 필요한 컬럼만 디코딩할 수 있습니다. 그 다음으로, read_parquet()는 파일의 메타데이터를 먼저 읽어 어떤 컬럼이 어디 있는지 파악합니다.

columns 파라미터로 특정 컬럼만 요청하면 해당 컬럼의 데이터만 디스크에서 읽어들여 I/O를 최소화합니다. 100개 컬럼 중 3개만 필요하다면 실제로 3%만 읽는 것입니다.

압축 알고리즘 선택도 중요합니다. snappy는 압축/해제 속도가 빠르지만 압축률은 보통이고, gzip은 압축률은 높지만 속도가 느립니다.

일반적으로 snappy가 기본값이며 대부분의 상황에 적합합니다. 와일드카드 패턴(*.parquet)을 사용하면 여러 파일을 자동으로 합쳐서 하나의 DataFrame으로 읽어들입니다.

날짜별로 분할된 데이터를 다룰 때 매우 편리합니다. 여러분이 이 코드를 사용하면 저장 공간 절약, 초고속 로딩, 타입 안정성 보장이라는 세 마리 토끼를 모두 잡을 수 있습니다.

특히 데이터 파이프라인에서 중간 결과물을 Parquet로 저장하면 전체 워크플로우 속도가 몇 배 빨라집니다.

실전 팁

💡 분석 중간 결과물은 항상 Parquet로 저장하세요. CSV 대비 10배 빠른 재로딩으로 시행착오 시간을 크게 줄입니다.

💡 Parquet 파일은 파티셔닝과 조합하면 더욱 강력합니다. 날짜별로 폴더를 나눠 저장하면 특정 기간만 빠르게 읽을 수 있습니다.

💡 압축 알고리즘은 용도에 따라 선택하세요. 저장 공간이 부족하면 gzip, 속도가 중요하면 snappy, 균형을 원하면 기본값을 사용하세요.

💡 대용량 데이터는 여러 Parquet 파일로 분할하여 저장하세요. 단일 파일보다 병렬 처리에 유리하고 일부만 읽기도 쉽습니다.

💡 Parquet는 스키마가 내장되어 있어 타입 추론 오류가 없습니다. CSV의 타입 추론 문제에서 해방될 수 있습니다.


3. JSON 데이터 다루기

시작하며

여러분이 웹 API에서 받은 JSON 데이터를 분석하려고 할 때, 중첩된 구조 때문에 막막했던 경험 있으시죠? 사용자 정보 안에 주문 목록이 있고, 그 안에 또 상품 정보가 있는 복잡한 JSON을 평평한 테이블로 만드는 것은 정말 골치 아픈 일입니다.

이런 문제는 API 기반 서비스가 늘어나면서 더욱 흔해졌습니다. JSON은 유연하고 편리하지만, 데이터 분석을 위해서는 결국 테이블 형태로 변환해야 하는데 이 과정이 복잡하고 오류가 발생하기 쉽습니다.

바로 이럴 때 필요한 것이 Polars의 JSON 읽기 기능입니다. 중첩된 JSON을 자동으로 평탄화하고, 배열과 객체를 적절히 처리하여 분석 가능한 DataFrame으로 만들어줍니다.

개요

간단히 말해서, Polars의 read_jsonwrite_json은 JSON 데이터를 DataFrame으로 변환하거나 그 반대 작업을 수행합니다. API 응답 데이터, 로그 파일, NoSQL 데이터베이스 export 등 JSON은 어디에나 있습니다.

예를 들어, REST API에서 받은 수천 개의 사용자 레코드를 분석해야 할 때, Polars는 JSON을 즉시 구조화된 테이블로 만들어줍니다. 기존 Python의 json.loads()로 딕셔너리 리스트를 만든 후 Pandas DataFrame으로 변환했다면, Polars는 한 번에 직접 DataFrame으로 읽어들입니다.

Polars의 JSON 처리 핵심은 자동 스키마 추론, 중첩 구조 지원, 스트리밍 모드입니다. 이러한 기능들이 복잡한 JSON 데이터를 손쉽게 분석 가능하게 만듭니다.

코드 예제

import polars as pl

# JSON 파일 읽기 (배열 형태의 JSON)
df = pl.read_json("users.json")

# JSON Lines 형태 읽기 (한 줄에 하나의 JSON 객체)
df = pl.read_ndjson("logs.ndjson")

# 중첩된 JSON 필드 접근
df = pl.read_json("nested.json")
# struct 컬럼을 개별 컬럼으로 분리
df = df.unnest("user_info")

# DataFrame을 JSON으로 저장
df.write_json("output.json")

# JSON Lines 형태로 저장 (대용량에 유리)
df.write_ndjson("output.ndjson")

설명

이것이 하는 일: Polars는 JSON 파일이나 문자열을 파싱하여 DataFrame으로 변환합니다. 배열 형태의 일반 JSON과 한 줄에 하나씩 객체가 있는 NDJSON(Newline Delimited JSON) 두 가지를 모두 지원합니다.

첫 번째로, read_json()은 JSON 배열을 읽어 각 객체를 DataFrame의 행으로 만듭니다. 이 과정에서 모든 객체의 키를 수집하여 컬럼을 결정하고, 값의 타입을 자동으로 추론합니다.

일부 객체에만 있는 키는 다른 행에서 null로 처리됩니다. 그 다음으로, read_ndjson()는 대용량 JSON 처리에 최적화되어 있습니다.

한 줄씩 읽어들이므로 메모리 효율이 높고, 로그 파일처럼 무한히 추가되는 데이터도 스트리밍으로 처리할 수 있습니다. 일반 JSON보다 파싱 속도도 월등히 빠릅니다.

중첩된 JSON 필드는 Polars에서 struct 타입으로 표현됩니다. unnest() 메서드를 사용하면 struct 컬럼을 개별 컬럼들로 펼칠 수 있습니다.

예를 들어, user_info struct에 name, age 필드가 있다면, unnest("user_info")user_info.name, user_info.age 컬럼으로 분리됩니다. write_json()write_ndjson()는 DataFrame을 다시 JSON 형태로 저장합니다.

일반 JSON은 읽기 쉽지만 대용량에는 부적합하고, NDJSON은 대용량 처리와 스트리밍에 적합합니다. 여러분이 이 코드를 사용하면 API 데이터 분석, 로그 분석, NoSQL 데이터 전처리 등의 작업을 간단하게 처리할 수 있습니다.

특히 웹 크롤링이나 API 수집 후 데이터 분석 파이프라인에서 필수적입니다.

실전 팁

💡 대용량 JSON은 NDJSON 형태로 변환하여 처리하세요. 메모리 사용량이 크게 줄고 스트리밍 처리가 가능합니다.

💡 중첩된 JSON 필드는 unnest()로 펼치되, 너무 깊은 중첩은 여러 번 호출해야 합니다. df.schema로 구조를 먼저 확인하세요.

💡 JSON 배열 필드는 List 타입으로 저장되므로, explode()로 각 원소를 별도 행으로 만들 수 있습니다. 일대다 관계 데이터 분석에 유용합니다.

💡 API 응답을 바로 DataFrame으로 만들 때는 pl.read_json(response.text)처럼 문자열도 바로 읽을 수 있습니다.

💡 JSON 스키마가 일관되지 않으면 타입 추론이 어려울 수 있습니다. 이럴 때는 명시적으로 dtypes를 지정하거나 후처리로 타입 변환하세요.


4. Excel 파일 읽기와 쓰기

시작하며

여러분이 비즈니스 팀에서 보내온 Excel 파일을 분석해야 할 때, 수식과 서식이 섞여 있어 데이터만 추출하기 어려웠던 경험 있으시죠? 여러 시트에 데이터가 분산되어 있고, 병합된 셀과 빈 행이 섞여 있어 자동화하기 참 까다롭습니다.

이런 문제는 Excel이 데이터 저장보다는 시각화와 계산에 최적화되어 있기 때문입니다. 같은 데이터도 사람마다 다르게 정리하고, 색상과 서식으로 의미를 표현하는데 이런 것들은 프로그램이 읽기 어렵습니다.

바로 이럴 때 필요한 것이 Polars의 Excel 읽기 기능입니다. 복잡한 Excel 파일에서 순수 데이터만 추출하여 깔끔한 DataFrame으로 만들어줍니다.

개요

간단히 말해서, Polars의 read_excel은 Excel 파일(.xlsx, .xls)을 읽어 DataFrame으로 변환하는 기능입니다. 비즈니스 현장에서는 여전히 Excel이 데이터 공유의 주요 수단입니다.

예를 들어, 영업팀의 월간 실적 보고서나 재무팀의 예산 데이터를 분석할 때, Excel을 읽지 못하면 수동으로 복사-붙여넣기를 해야 합니다. 기존에는 Pandas의 pd.read_excel()을 사용했지만, 속도가 느리고 의존성 관리가 복잡했습니다.

Polars는 더 빠른 파싱과 간단한 설치로 이 문제를 해결합니다. Polars Excel 처리의 핵심은 다중 시트 지원, 헤더 행 지정, 범위 선택입니다.

이러한 기능들이 실무의 복잡한 Excel 파일을 프로그래밍 방식으로 처리 가능하게 만듭니다.

코드 예제

import polars as pl

# Excel 파일의 첫 번째 시트 읽기
df = pl.read_excel("sales_report.xlsx")

# 특정 시트 이름 지정
df = pl.read_excel("report.xlsx", sheet_name="2024년 실적")

# 시트 인덱스로 지정 (0부터 시작)
df = pl.read_excel("report.xlsx", sheet_id=1)

# 헤더가 여러 행인 경우 시작 행 지정
df = pl.read_excel("report.xlsx", header_row=2)

# Excel에 DataFrame 쓰기 (xlsx 엔진 필요)
df.write_excel("output.xlsx")

# 여러 시트를 딕셔너리로 한번에 읽기
sheets = pl.read_excel("report.xlsx", sheet_name=None)

설명

이것이 하는 일: Polars는 Excel 파일을 파싱하여 워크시트의 데이터를 DataFrame으로 변환합니다. 내부적으로는 openpyxl이나 xlsx2csv 같은 라이브러리를 활용합니다.

첫 번째로, read_excel()은 지정된 시트를 찾아 셀 데이터를 읽어들입니다. sheet_name으로 시트 이름을, sheet_id로 인덱스를 지정할 수 있습니다.

시트 이름에 한글이나 공백이 포함되어도 문제없이 처리합니다. 그 다음으로, header_row 파라미터로 헤더 위치를 지정합니다.

Excel에서는 제목, 부제, 헤더가 여러 행에 걸쳐 있는 경우가 많은데, 이 옵션으로 실제 데이터가 시작하는 위치를 정확히 알려줄 수 있습니다. sheet_name=None으로 설정하면 모든 시트를 한번에 읽어 딕셔너리로 반환합니다.

키는 시트 이름, 값은 각 시트의 DataFrame입니다. 여러 시트에 분산된 데이터를 통합 분석할 때 편리합니다.

write_excel()은 DataFrame을 Excel 파일로 저장하는데, 이를 위해서는 별도의 엑셀 라이터 엔진이 필요할 수 있습니다. 일반적으로 분석 결과를 비개발자와 공유할 때 유용합니다.

여러분이 이 코드를 사용하면 비즈니스 팀과의 협업이 훨씬 수월해집니다. 매주 받는 Excel 보고서를 자동으로 분석하여 인사이트를 추출하는 파이프라인을 쉽게 구축할 수 있습니다.

실전 팁

💡 Excel 파일이 복잡하면 먼저 Excel에서 직접 열어 구조를 파악하세요. 헤더 위치, 시트 이름, 빈 행 등을 미리 확인하면 파라미터 설정이 쉬워집니다.

💡 대용량 Excel은 읽기가 매우 느립니다. 가능하면 데이터 제공자에게 CSV나 Parquet로 받도록 요청하세요. 어쩔 수 없다면 한 번 읽어서 Parquet로 변환해두세요.

💡 병합된 셀이나 수식은 제대로 읽히지 않을 수 있습니다. Excel에서 값으로 붙여넣기(Paste Values)를 먼저 하거나, 후처리로 정리하세요.

💡 여러 시트를 읽을 때는 루프보다 sheet_name=None으로 한번에 읽는 것이 효율적입니다. 파일 열기 오버헤드를 줄일 수 있습니다.

💡 Excel 쓰기는 속도가 느리므로 최종 결과 공유용으로만 사용하세요. 중간 데이터는 Parquet나 CSV로 저장하는 것이 좋습니다.


5. 데이터베이스 연동 (SQL)

시작하며

여러분이 PostgreSQL이나 MySQL에 있는 수백만 행의 데이터를 분석해야 할 때, 전부 메모리로 가져오면 시스템이 다운되고, 쿼리로만 처리하자니 복잡한 분석이 어려웠던 경험 있으시죠? DB는 저장에 최적화되어 있고, Python은 분석에 최적화되어 있어 둘 사이의 간극을 메우기가 쉽지 않습니다.

이런 문제는 데이터가 DB에 있지만 분석은 Python에서 해야 하는 현대적 데이터 파이프라인의 전형적인 딜레마입니다. 전체를 가져오면 메모리 부족, 부분만 가져오면 여러 번 쿼리해야 하고, SQL만으로는 복잡한 통계 분석이 불가능합니다.

바로 이럴 때 필요한 것이 Polars의 데이터베이스 연동 기능입니다. SQL 쿼리로 필요한 데이터만 추출하고, DataFrame으로 변환하여 강력한 분석을 수행할 수 있습니다.

개요

간단히 말해서, Polars는 read_database로 SQL 쿼리 결과를 직접 DataFrame으로 가져오고, write_database로 DataFrame을 DB 테이블로 저장합니다. 실무에서 트랜잭션 데이터는 대부분 관계형 DB에 저장되어 있습니다.

예를 들어, 최근 1년간 매출 데이터를 분석할 때, DB에서 필요한 컬럼과 기간만 추출하여 Python으로 가져와 시각화와 통계 분석을 수행합니다. 기존 Pandas에서는 pd.read_sql()을 사용했지만, 대용량 데이터에서는 느리고 메모리 효율도 떨어졌습니다.

Polars는 더 빠른 데이터 전송과 메모리 최적화로 이를 개선합니다. Polars DB 연동의 핵심은 SQL 쿼리 직접 실행, 커넥션 풀링, 배치 읽기입니다.

이러한 기능들이 대규모 DB 데이터를 효율적으로 Python 분석 환경으로 가져올 수 있게 해줍니다.

코드 예제

import polars as pl
from sqlalchemy import create_engine

# 데이터베이스 연결 엔진 생성
engine = create_engine("postgresql://user:password@localhost:5432/mydb")

# SQL 쿼리로 데이터 읽기
query = "SELECT * FROM sales WHERE date >= '2024-01-01'"
df = pl.read_database(query, connection=engine)

# 특정 컬럼만 선택하여 가져오기 (네트워크 트래픽 감소)
query = "SELECT order_id, customer_id, amount FROM orders WHERE status = 'completed'"
df = pl.read_database(query, connection=engine)

# DataFrame을 데이터베이스 테이블로 저장
df.write_database("analysis_results", connection=engine, if_table_exists="replace")

# 배치로 나눠서 읽기 (대용량 처리)
df = pl.read_database(query, connection=engine, batch_size=10000)

설명

이것이 하는 일: Polars는 SQLAlchemy를 통해 다양한 데이터베이스(PostgreSQL, MySQL, SQLite 등)에 연결하고, SQL 쿼리를 실행하여 결과를 DataFrame으로 가져옵니다. 첫 번째로, create_engine()으로 DB 연결 정보를 설정합니다.

이 엔진은 커넥션 풀을 관리하여 여러 쿼리를 효율적으로 실행할 수 있게 합니다. 연결 문자열 형식은 dialect://user:password@host:port/database입니다.

그 다음으로, read_database()는 제공된 SQL 쿼리를 DB에서 실행하고 결과 집합을 행 단위로 읽어들입니다. 이 과정에서 DB의 데이터 타입을 Polars의 타입으로 자동 변환합니다.

예를 들어, TIMESTAMP는 Datetime, VARCHAR는 Utf8로 매핑됩니다. SQL 쿼리에서 필요한 컬럼만 SELECT하면 네트워크 전송량이 줄고 메모리도 절약됩니다.

WHERE 절로 필터링도 DB 단에서 수행하면 Python으로 가져오는 데이터량을 크게 줄일 수 있습니다. DB의 인덱스를 활용하면 필터링이 매우 빠릅니다.

write_database()는 DataFrame을 DB 테이블로 저장합니다. if_table_exists 파라미터로 테이블이 이미 있을 때 동작을 제어할 수 있습니다.

"replace"는 기존 테이블 삭제 후 생성, "append"는 데이터 추가, "fail"은 오류 발생입니다. 대용량 데이터는 batch_size로 나눠서 읽으면 메모리 부족을 방지할 수 있습니다.

10만 행씩 끊어서 읽어 처리한 후 다음 배치를 읽는 방식으로 무한대 크기의 데이터도 다룰 수 있습니다. 여러분이 이 코드를 사용하면 프로덕션 DB의 데이터를 안전하고 빠르게 분석할 수 있습니다.

ETL 파이프라인, 리포팅 자동화, 데이터 마이그레이션 등 다양한 시나리오에 활용 가능합니다.

실전 팁

💡 SQL WHERE 절로 최대한 필터링하고 필요한 컬럼만 SELECT하세요. DB의 인덱스와 병렬 처리를 활용하면 Python보다 훨씬 빠릅니다.

💡 프로덕션 DB에 직접 쿼리하면 성능 영향을 줄 수 있습니다. 읽기 전용 레플리카나 DW를 사용하세요.

💡 대용량 쓰기는 batch_size를 사용하여 나눠서 INSERT하세요. 한 번에 수백만 행을 쓰면 트랜잭션 타임아웃이 발생할 수 있습니다.

💡 민감한 정보가 포함된 연결 문자열은 환경변수나 시크릿 관리 도구를 사용하세요. 코드에 하드코딩하지 마세요.

💡 SQLAlchemy 엔진은 재사용하세요. 매번 새로 만들면 연결 오버헤드가 큽니다. 한 번 생성하여 여러 쿼리에 사용하세요.


6. 스트리밍 및 청크 단위 처리

시작하며

여러분이 100GB짜리 로그 파일을 분석해야 하는데, 서버 메모리가 32GB밖에 없다면 어떻게 하시겠습니까? 전체를 읽으려고 하면 메모리 부족으로 프로그램이 죽고, 파일을 쪼개자니 어떻게 나눠야 할지 막막합니다.

이런 문제는 빅데이터 시대의 전형적인 도전과제입니다. 데이터는 계속 커지는데 하드웨어 자원은 한정되어 있고, 모든 데이터를 한 번에 메모리에 올리는 것은 불가능합니다.

바로 이럴 때 필요한 것이 스트리밍과 청크 단위 처리입니다. 전체 데이터를 작은 조각으로 나눠 순차적으로 처리하여 제한된 메모리로도 무한대 크기의 데이터를 다룰 수 있습니다.

개요

간단히 말해서, 청크 단위 처리는 큰 데이터를 작은 블록으로 나눠 하나씩 읽고 처리하는 방식입니다. 실무에서 서버 로그, IoT 센서 데이터, 클릭스트림 같은 대용량 데이터를 다룰 때, 전체를 메모리에 올릴 수 없는 상황이 자주 발생합니다.

예를 들어, 하루치 웹 로그가 50GB인데 서버 메모리가 16GB라면, 청크 단위로 처리하는 것이 유일한 방법입니다. 기존 방식에서는 파일을 수동으로 분할하거나, 복잡한 스트리밍 로직을 직접 구현해야 했습니다.

Polars는 scan_* 함수와 lazy evaluation으로 이를 자동화합니다. 청크 처리의 핵심은 메모리 제한 극복, 점진적 처리, 배치 집계입니다.

이러한 기능들이 하드웨어 제약을 넘어서는 데이터 처리를 가능하게 만듭니다.

코드 예제

import polars as pl

# Lazy 모드로 대용량 CSV 스캔 (메모리에 로드하지 않음)
lazy_df = pl.scan_csv("huge_file.csv")

# 필요한 연산만 정의 (아직 실행 안됨)
result = lazy_df.filter(pl.col("amount") > 1000).group_by("category").agg(pl.col("amount").sum())

# collect()로 실제 실행 - 최적화된 쿼리 플랜으로 실행
final_df = result.collect()

# 청크 단위로 CSV 읽어서 처리
total = 0
for chunk in pl.read_csv_batched("large.csv", batch_size=100000):
    total += chunk["amount"].sum()
print(f"Total amount: {total}")

# Parquet도 스트리밍 지원
lazy_df = pl.scan_parquet("data/*.parquet")

설명

이것이 하는 일: Polars의 scan_* 함수들은 데이터를 메모리에 로드하지 않고 lazy DataFrame을 만듭니다. 실제 데이터는 collect()를 호출할 때 필요한 부분만 읽어들입니다.

첫 번째로, scan_csv(), scan_parquet() 등은 파일의 메타데이터만 읽어 스키마를 파악합니다. 실제 데이터는 읽지 않으므로 100GB 파일도 즉시 리턴됩니다.

이 시점에서는 쿼리 플랜만 준비되어 있는 상태입니다. 그 다음으로, filter(), select(), group_by() 같은 연산을 체이닝하면 쿼리 플랜에 추가됩니다.

이 연산들도 즉시 실행되지 않고 나중에 실행할 계획만 기록됩니다. 이를 lazy evaluation이라고 합니다.

collect()를 호출하면 Polars는 전체 쿼리 플랜을 분석하여 최적화합니다. 예를 들어, filter를 먼저 적용하고 필요한 컬럼만 읽으며, 여러 연산을 합쳐서 한 번에 처리합니다.

이 최적화로 실행 시간과 메모리 사용량이 크게 줄어듭니다. read_csv_batched()는 명시적으로 청크 단위로 읽습니다.

제너레이터를 반환하므로 for 루프로 각 청크를 순회하며 처리할 수 있습니다. 각 청크는 독립적으로 처리되고 메모리에서 해제되므로, 무한대 크기의 파일도 처리 가능합니다.

스트리밍 집계는 각 청크의 부분 결과를 누적하는 방식입니다. sum, count 같은 집계는 간단하지만, 평균이나 표준편차는 조금 더 복잡한 로직이 필요합니다.

Polars는 이런 복잡한 집계도 자동으로 처리합니다. 여러분이 이 코드를 사용하면 메모리 제약 없이 대규모 데이터 분석이 가능합니다.

배치 ETL, 로그 분석, 대용량 리포팅 등 다양한 빅데이터 시나리오에 적용할 수 있습니다.

실전 팁

💡 가능한 한 scan과 lazy 모드를 사용하세요. 쿼리 최적화로 실행 시간이 몇 배 빨라지고, 불필요한 데이터를 읽지 않아 메모리도 절약됩니다.

💡 청크 크기는 메모리와 처리 속도의 균형을 맞춰 설정하세요. 너무 작으면 오버헤드가 크고, 너무 크면 메모리 부족이 발생합니다. 보통 10만~100만 행이 적당합니다.

💡 필터는 가능한 한 먼저 적용하세요. scan 직후 filter를 하면 나머지 데이터를 아예 읽지 않아 엄청난 성능 향상을 얻습니다.

💡 집계 연산은 청크별로 부분 결과를 계산하고 나중에 합치세요. 모든 청크를 메모리에 모으면 스트리밍의 의미가 없습니다.

💡 디버깅할 때는 먼저 작은 샘플로 테스트하세요. head(1000).collect()로 처음 1000행만 빠르게 확인한 후, 전체 데이터로 확장하세요.


7. 압축 파일 직접 읽기

시작하며

여러분이 매일 받는 데이터 파일이 압축되어 있어서, 압축 해제하고 읽고 다시 삭제하는 반복 작업에 지치신 적 있나요? 특히 수십 GB짜리 gzip 파일을 풀면 디스크 공간이 부족해지고, 압축 해제 시간도 상당히 걸립니다.

이런 문제는 네트워크 전송이나 저장 공간 절약을 위해 데이터를 압축하는 것이 일반적이기 때문입니다. 하지만 분석을 위해 매번 압축을 풀고 다시 압축하는 것은 비효율적이고 번거롭습니다.

바로 이럴 때 필요한 것이 압축 파일 직접 읽기 기능입니다. 압축을 풀지 않고도 파일 내용을 바로 읽어 분석할 수 있어 시간과 디스크 공간을 절약합니다.

개요

간단히 말해서, Polars는 gzip, bzip2, zip 등으로 압축된 파일을 직접 읽어 DataFrame으로 만들 수 있습니다. 실무에서 데이터 파이프라인은 저장 비용과 전송 시간을 줄이기 위해 압축을 광범위하게 사용합니다.

예를 들어, 클라우드 스토리지에서 다운로드하는 로그 파일이 gzip으로 압축되어 있다면, 압축을 풀지 않고 바로 읽으면 디스크 I/O를 크게 줄일 수 있습니다. 기존 방식에서는 gzip 모듈로 압축을 풀어 임시 파일로 저장한 후 읽어야 했습니다.

Polars는 파일 확장자를 보고 자동으로 압축 해제하며 읽습니다. 압축 파일 읽기의 핵심은 자동 압축 감지, 스트리밍 해제, 다중 압축 포맷 지원입니다.

이러한 기능들이 압축 데이터 처리를 투명하게 만들어줍니다.

코드 예제

import polars as pl

# gzip 압축된 CSV 직접 읽기 (확장자로 자동 감지)
df = pl.read_csv("data.csv.gz")

# bzip2 압축 파일
df = pl.read_csv("data.csv.bz2")

# zip 아카이브 내 파일 읽기
df = pl.read_csv("archive.zip")

# Parquet도 압축 지원
df = pl.read_parquet("data.parquet.gz")

# Lazy 모드로 압축 파일 스캔
lazy_df = pl.scan_csv("huge_data.csv.gz")

# 압축하여 저장 (Parquet는 기본적으로 압축됨)
df.write_csv("output.csv.gz")

설명

이것이 하는 일: Polars는 파일 확장자나 매직 넘버를 보고 압축 포맷을 자동으로 감지합니다. 파일을 읽으면서 동시에 압축을 해제하여 별도의 임시 파일 없이 직접 처리합니다.

첫 번째로, read_csv("data.csv.gz")를 호출하면 Polars는 .gz 확장자를 보고 gzip 압축임을 인식합니다. 파일을 열면서 gzip 디코더를 통과시켜 압축을 풀고, CSV 파서가 바로 읽어들입니다.

이 과정이 파이프라인으로 연결되어 메모리 효율이 높습니다. 그 다음으로, 스트리밍 방식으로 압축을 해제하므로 전체 파일을 먼저 풀 필요가 없습니다.

필요한 만큼만 읽어서 처리하고, 메모리에서 해제합니다. 100GB 압축 파일도 메모리 사용량은 청크 크기에만 비례합니다.

zip 파일의 경우, 여러 파일이 포함되어 있을 수 있는데 Polars는 첫 번째 파일을 자동으로 읽습니다. 특정 파일을 지정하고 싶다면 압축을 푼 후 읽거나, 다른 라이브러리와 조합해야 합니다.

Parquet 파일 자체가 내부적으로 압축을 지원하므로, Parquet를 다시 gzip으로 압축하는 것은 보통 불필요합니다. CSV를 gzip으로 압축하는 것은 의미가 있지만, Parquet는 이미 압축되어 있습니다.

write_csv("output.csv.gz")로 저장하면 자동으로 gzip 압축하여 씁니다. 네트워크로 전송하거나 장기 보관할 데이터라면 압축 저장이 효율적입니다.

여러분이 이 코드를 사용하면 압축 파일을 다루는 파이프라인이 매우 간단해집니다. 다운로드 받은 데이터를 바로 분석하고, 결과를 압축하여 저장하는 전체 과정이 몇 줄로 끝납니다.

실전 팁

💡 압축된 CSV는 읽기 속도가 느릴 수 있습니다. 반복해서 읽는다면 한 번 읽어서 Parquet로 변환해두세요. Parquet는 압축되어 있으면서도 읽기가 빠릅니다.

💡 gzip은 압축률이 좋지만 병렬 해제가 안 됩니다. 멀티코어를 활용하려면 여러 gzip 파일로 분할하거나, 다른 압축 포맷을 고려하세요.

💡 클라우드 스토리지에서 읽을 때는 압축된 상태로 다운로드하는 것이 네트워크 비용을 크게 줄입니다. Polars로 바로 읽으면 편리합니다.

💡 zip 파일은 랜덤 액세스가 가능하지만, Polars는 순차 읽기에 최적화되어 있습니다. 여러 파일 중 하나만 필요하다면 직접 압축 해제하는 것이 나을 수 있습니다.

💡 압축 파일을 lazy scan할 때는 압축 해제 오버헤드를 고려하세요. 필터를 먼저 적용해도 압축 해제는 필요하므로, 압축 안 된 Parquet만큼 빠르지는 않습니다.


8. 메모리 맵 파일 활용

시작하며

여러분이 50GB짜리 Parquet 파일에서 단 몇 개의 행만 읽어야 하는데, 전체 파일을 메모리에 로드하는 것은 너무 비효율적이라고 느끼신 적 있나요? 특정 조건의 데이터만 필요한데 전체를 읽는다면 시간과 자원이 낭비됩니다.

이런 문제는 전통적인 파일 읽기 방식이 처음부터 끝까지 순차적으로 읽도록 설계되어 있기 때문입니다. 필요한 부분만 건너뛰어 읽는 것이 어렵고, 메모리에 전체를 올려야 인덱스 접근이 가능합니다.

바로 이럴 때 필요한 것이 메모리 맵(memory-mapped) 파일입니다. 파일을 메모리 주소 공간에 매핑하여 필요한 부분만 물리 메모리로 로드하고, 나머지는 디스크에 그대로 둡니다.

개요

간단히 말해서, 메모리 맵은 파일을 마치 메모리처럼 접근할 수 있게 하는 OS 기능으로, Polars는 이를 활용하여 대용량 파일을 효율적으로 다룹니다. 머신러닝 모델 학습 중 데이터셋의 일부만 랜덤하게 샘플링하거나, 특정 날짜 범위만 추출하는 경우가 많습니다.

예를 들어, 1년치 데이터 중 특정 주의 데이터만 필요하다면, 메모리 맵으로 해당 부분만 읽어 메모리를 절약할 수 있습니다. 기존 방식에서는 전체 파일을 읽거나, seek으로 수동으로 위치를 찾아야 했습니다.

Polars는 Parquet의 메타데이터와 메모리 맵을 조합하여 자동으로 최적화합니다. 메모리 맵의 핵심은 지연 로딩(필요한 페이지만 로드), 가상 메모리 활용, 제로카피입니다.

이러한 특징들이 물리 메모리보다 큰 파일을 마치 메모리에 있는 것처럼 다룰 수 있게 해줍니다.

코드 예제

import polars as pl

# Parquet는 기본적으로 메모리 맵 활용
df = pl.read_parquet("large_file.parquet")

# 명시적으로 메모리 맵 사용 설정
df = pl.scan_parquet("large_file.parquet").collect()

# 특정 행 범위만 읽기 (메모리 맵의 장점)
df = pl.read_parquet("data.parquet", n_rows=1000)

# 조건 필터로 필요한 부분만 로드
lazy_df = pl.scan_parquet("data.parquet")
df = lazy_df.filter(pl.col("date") == "2024-01-01").collect()

# 여러 파일을 메모리 맵으로 효율적으로 병합
df = pl.scan_parquet("data/2024-*.parquet").collect()

설명

이것이 하는 일: 메모리 맵은 파일을 프로세스의 가상 메모리 주소 공간에 매핑합니다. 실제 데이터는 필요할 때만 페이지 단위로 물리 메모리에 로드되고, 사용하지 않는 부분은 디스크에 남아있습니다.

첫 번째로, scan_parquet()는 파일을 메모리 맵으로 열고 메타데이터만 읽습니다. 메타데이터에는 각 컬럼의 오프셋, 압축 정보, 통계값이 포함되어 있어 어느 부분을 읽어야 할지 정확히 알 수 있습니다.

그 다음으로, filter()로 조건을 추가하면 Polars는 Parquet의 통계 정보를 활용하여 스킵할 수 있는 row group을 식별합니다. 예를 들어, date 컬럼의 min/max 값을 보고 해당 조건을 만족하지 않는 row group은 아예 읽지 않습니다.

collect()를 호출하면 필요한 row group과 컬럼만 메모리 맵을 통해 읽어들입니다. OS는 해당 파일 부분을 페이지 캐시에 로드하고, 프로세스는 메모리 주소처럼 접근합니다.

압축 해제는 읽을 때 on-the-fly로 수행됩니다. 메모리 맵의 장점은 여러 프로세스가 같은 파일을 공유할 수 있다는 것입니다.

여러 분석 스크립트가 동시에 같은 대용량 Parquet를 읽어도, 물리 메모리에는 한 번만 로드됩니다. n_rows 파라미터로 처음 N개 행만 읽으면, 실제로는 첫 번째 row group만 읽어들입니다.

메모리 맵 덕분에 파일 끝까지 스캔할 필요가 없어 즉시 리턴됩니다. 여러분이 이 기능을 사용하면 대용량 데이터셋에서 샘플링, 필터링, 탐색적 분석이 매우 빨라집니다.

전체 데이터를 로드하지 않고도 필요한 부분만 빠르게 추출할 수 있습니다.

실전 팁

💡 Parquet는 row group 단위로 저장되므로, 필터 조건은 row group 전체를 스킵할 수 있는 형태가 좋습니다. 날짜나 ID 범위 같은 조건이 효과적입니다.

💡 메모리 맵은 SSD에서 특히 빠릅니다. HDD에서는 랜덤 액세스가 느려 효과가 반감됩니다. 가능하면 SSD에 데이터를 두세요.

💡 여러 프로세스가 같은 파일을 읽는다면 메모리 맵의 공유 효과로 메모리를 크게 절약할 수 있습니다. 멀티프로세싱 파이프라인에 유용합니다.

💡 탐색적 분석 시 head() 대신 scan().limit().collect()를 사용하면 메모리 맵으로 첫 부분만 읽어 매우 빠릅니다.

💡 메모리 맵은 읽기 전용으로 안전합니다. 원본 파일을 수정할 염려 없이 여러 분석을 동시에 돌릴 수 있습니다.


#Polars#CSV#Parquet#JSON#DataIO#데이터분석,Python,Polars

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.