불필요한 부피를 버리자: DuckDB와 Parquet로 고성능 헬스 데이터 레이크 구축
Source: Dev.to
아키텍처: XML 혼돈에서 SQL 속도로
목표는 메모리를 많이 소모하는 느린 XML 구조에서 분석 쿼리에 최적화된 컬럼형 압축 저장 포맷으로 전환하는 것입니다.
graph TD
A[Apple Health Export XML] -->|Python Streaming Parser| B(Raw Data Extraction)
B -->|Schema Mapping| C[Apache Parquet Files]
C -->|Zero‑Copy Load| D{DuckDB Engine}
D -->|Sub‑second SQL| E[Evidence.dev Dashboard]
D -->|Advanced Analytics| F[Pandas/Polars]
style D fill:#fff2cc,stroke:#d6b656,stroke-width:2px
style C fill:#dae8fc,stroke:#6c8ebf,stroke-width:2px
사전 준비 사항
- Python 3.9+
- DuckDB – “분석용 SQLite”
- Pandas / PyArrow – Parquet 처리용
export.xml파일 (또는 샘플 파일)
Step 1: 거대한 파일 파싱 (XML → Parquet)
표준 xml.etree.ElementTree는 파일 크기가 2 GB를 초과하면 RAM을 초과해 충돌합니다. 대신 스트리밍 파서를 사용하고 HeartRate 레코드만 추출합니다.
import xml.etree.ElementTree as ET
import pandas as pd
import pyarrow as pa
import pyarrow.parquet as pq
def parse_health_data(xml_path: str, output_parquet: str):
data = []
# iterparse는 전체를 메모리에 올리지 않고 큰 파일을 처리합니다
context = ET.iterparse(xml_path, events=('end',))
for event, elem in context:
if elem.tag == 'Record' and elem.get('type') == 'HKQuantityTypeIdentifierHeartRate':
data.append({
'timestamp': elem.get('startDate'),
'value': float(elem.get('value')),
'unit': elem.get('unit')
})
# 메모리 해제
elem.clear()
# 메모리 사용량을 낮게 유지하기 위해 배치 저장
if len(data) > 100_000:
save_batch(data, output_parquet)
data = []
# 남은 행 저장
if data:
save_batch(data, output_parquet)
def save_batch(data: list[dict], path: str):
df = pd.DataFrame(data)
df['timestamp'] = pd.to_datetime(df['timestamp'])
table = pa.Table.from_pandas(df)
pq.write_to_dataset(table, root_path=path, partition_cols=None)
# 사용 예시:
# parse_health_data('export.xml', 'heart_rate_lake')
Step 2: DuckDB의 마법
데이터가 Parquet에 저장되면 컬럼 단위로 보관됩니다. DuckDB는 전체 파일을 메모리로 로드하지 않고도 바로 쿼리할 수 있습니다.
import duckdb
# 영구 DuckDB 인스턴스에 연결 (또는 ':memory:'를 사용해 메모리 내 DB)
con = duckdb.connect(database='health_analytics.db')
# Parquet 파일 위에 바로 뷰 생성 (zero‑copy!)
con.execute("""
CREATE VIEW heart_rates AS
SELECT *
FROM read_parquet('heart_rate_lake/*.parquet')
""")
# 예시 분석 쿼리: 시간대별 안정 심박수 추세
res = con.execute("""
SELECT
date_trunc('hour', timestamp) AS hour,
avg(value) AS avg_bpm,
max(value) AS max_bpm
FROM heart_rates
WHERE value
""")
결론
“부피가 큰” 포맷을 버리고 현대적인 데이터 스택(Python + Parquet + DuckDB)을 도입함으로써, 정적인 백업이던 건강 데이터를 동적인 연구 도구로 전환할 수 있습니다. 회복 추세, 수면 품질, 심혈관 건강 등은 밀리초 단위로 탐색이 가능해집니다.
다음 단계는?
year또는month기준으로 Parquet 파일을 파티셔닝하면 스캔 속도가 더욱 빨라집니다.- Evidence.dev 를 통합해 아름답고 코드 기반의 대시보드를 만들 수 있습니다.
자신의 건강 데이터를 분석하고 계신가요? 댓글에 어떤 인사이트를 발견했는지 알려주세요!