불필요한 부피를 버리자: DuckDB와 Parquet로 고성능 헬스 데이터 레이크 구축

발행: (2026년 4월 9일 AM 09:10 GMT+9)
4 분 소요
원문: Dev.to

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 를 통합해 아름답고 코드 기반의 대시보드를 만들 수 있습니다.

자신의 건강 데이터를 분석하고 계신가요? 댓글에 어떤 인사이트를 발견했는지 알려주세요!

0 조회
Back to Blog

관련 글

더 보기 »