DuckDB 사용법(DuckDB Python + Jupyter Lab)


  • 이 글은 DuckDB 사용법을 작성한 글입니다
  • 예상 독자
    • DuckDB가 궁금하신 분
    • 데이터 분석가 : Pandas가 느리다고 생각해서 다른 대안을 찾고 있는 분
    • 데이터 엔지니어 : 데이터 엔지니어링의 Transform 영역에서 활용할 수 있는 수단을 찾고 있는 분. 작은 규모의 회사에서 데이터 웨어하우스를 고민하고 있는 분
  • 키워드 : Duckdb 사용법, Duckdb 설치, Duckdb Python

DuckDB 소개, DuckDB란?

  • Github Repo
  • DuckDB는 fast in-process Analytical Database
  • 왜 Duck인가?
    • Duck(오리)은 날 수도 있고, 수영도 하고, 걷는 놀라운 동물. 환경에 유연함
    • 다재다능하고 탄력적인 데이터 관리 시스템을 만들고 싶었는데, 이런 시스템이 오리와 유사하다고 생각해서 DuckDB라고 이름을 지음
  • C++로 작성되었고, Column-oriented 데이터베이스로 매우 빠름
  • Parquet, CSV, JSON 파일에 직접 쿼리할 수 있음
    • AWS S3, GCS, Azure Blob Storage 등에서도 쿼리할 수 있음
    • S3 Iceberg도 지원
  • 사용하기 쉬움
  • 로컬 Data Warehouse(나만을 위한 DW)라고 생각해도 될 듯
  • Portable Python Dataframe 라이브러리라고 나온 ibis의 백엔드 엔진이 DuckDB
  • 공식 Docker 이미지가 존재하지 않음
    • 애플리케이션에서 DuckDB를 동시에 실행하려고 함. 별도의 컨테이너화를 하지 않아도 됨
    • 웹페이지, 앱 등에서 그냥 DuckDB를 설치해서 사용하면 됨
  • DuckDB 기반 관리형 서비스 : MotherDuck
  • Live Demo를 보고 싶다면 DuckDB Live Demo에서 확인할 수 있음

DuckDB Live Demo



BIG DATA IS DEAD

  • Google Cloud의 BigQuery를 만든 멤버 중 한 명인 Jordan Tigani가 2023년 2월에 올린 BIG DATA IS DEAD라는 글을 보다가 MotherDuck을 알게 되고, DuckDB를 알게 되었음
  • BIG DATA IS DEAD 글의 핵심은 다음과 같음
    • 대부분의 기업은 “빅데이터”를 가지고 있지 않음
      • BigQuery 사용자 대부분은 1TB 미만의 데이터를 보유
      • 일반적인 기업의 데이터 웨어하우스 크기는 100GB가 일반적임
      • 특정 VC의 포트폴리오 회사 중 제일 큰 B2B 기업은 약 1TB, 제일 큰 B2C 기업은 약 10TB를 가지고 있었음. 그러나 대부분 이것보다 적은 데이터를 가짐
    • 데이터를 쿼리하는 패턴
      • 시간이 지나면서 데이터는 많아지지만, 컴퓨팅 리소스는 거의 변하지 않음
      • 1년에 1,000달러 이상을 지출하는 고객을 살펴보니, 쿼리의 90%가 100MB 미만의 데이터를 처리함
      • 데이터가 많은 고객도 많은 양의 데이터를 쿼리하지 않음
      • 최근 24시간 내의 데이터가 가장 자주 활용
      • 오래 된 데이터는 사용되지 않음
    • 싱글 머신의 성능 향상
      • 요즘 AWS의 표준 인스턴스는 64 Core, 256GB RAM 서버
        • 2006년에 AWS에서 EC2를 처음 출시할 땐 싱글 코어에 2GB RAM 서버였음
      • 예전엔 대형 서버가 비쌌으나, 요즘엔 저렴해짐
      • 과거에 분산 처리가 필요했던 작업들이 싱글 머신에서 처리될 수 있음
    • 데이터는 자산이 아닌 부채일 수 있음
      • 사람들이 빅데이터를 가지게 되는 이유는 필요해서가 아닌 단순히 삭제하는 것을 귀찮아해서
      • 같은 쿼리만 반복적으로 한다면 집계된 데이터만 저장하는 게 좋을 수 있음
      • 언젠가 쓸지 모른다는 생각으로 보관할 수 있음
    • 빅데이터의 1%인지 확인할 수 있는 질문
      • 정말 엄청난 양의 데이터를 생성하고 있는가?
      • 그렇다면 정말 한 번에 그 많은 데이터를 사용할 필요가 있는가?
      • 그렇다면 그 데이터가 정말 1대의 컴퓨터에 담기에 큰가?
      • 그렇다면 당신이 단순히 데이터를 쌓아두기만 하는 사람이 아닌지 확실할 수 있는가?
      • 그렇다면 데이터를 요약해서 보관하는 것이 더 나은 방법이 아닐까?
      • 하나라도 아니요라고 답하면 데이터 규모에 맞는 새로운 데이터 도구를 사용하는 것이 좋을 수 있음
  • 위와 같은 내용 때문에 복잡한 분산 시스템 대신 단순하고 효율적인 솔루션이 필요하다고 함
  • BIG DATA IS DEAD엔 직접적으로 나오지 않지만, DuckDB가 이런 상황에 적절한 도구라는 것을 알 수 있음
  • 시간이 지나면서 점점 싱글 머신이 확장되어 분산 처리까지 갈 필요가 없다는 의미 => DuckDB는 이 용도에 적합




DuckDB 목표 & DuckDB 장점

  • 세상에 다양한 DB가 있는데, 모든 상황에 적합한 데이터베이스는 없음. 특정 사용 사례에 따라 더 잘 맞도록 설계됨
  • 다음은 DuckDB의 목표 및 장점

1. Simple

  • SQLite처럼 설치하고 사용하기 쉬워야 함
  • 외부 종속성 없이 동작하고, 별도의 서버 설치나 관리가 필요 없음
  • DuckDB는 별도의 프로세스로 실행되지 않고 호스트 프로세스에서 실행됨


2. Portable

  • 모든 OS에서 동작해야 함 (Windows, MacOS, Linux)
  • 다양한 CPU 아키텍처(x86, ARM) 지원
  • 소형 디바이스, 웹 브라우저, 모바일에서도 실행 가능(DuckDB-Wasm 사용)
  • Java, C, C++, Go, Node.js, Python 등 다양한 API 제공


3. Feature Rich

  • 데이터 분석을 위한 복잡한 쿼리를 지원(윈도우 함수)
  • 커스텀한 MVCC(Multi-Version Concurrency Control)를 통해 ACID 트랜잭션을 보장한다고 주장
    • MVCC : 데이터베이스에서 여러 버전의 데이터를 동시에 관리. 수정할 때마다 새 버전 생성
    • HyPer의 serializable variant MVCC 방식을 구현
  • 쿼리의 속도를 높이기 위한 보조 인덱스를 지원
  • Python, R과 통합


4. Fast

  • OLAP 쿼리 워크로드를 지원
  • 전체 테이블에 대한 집계, 큰 테이블의 JOIN 등을 빠르게 처리
  • 개별 값당 소모되는 CPU 사이클의 양을 줄이는 것이 중요하고, 이를 위해 just-in-time 쿼리 실행 엔진, Vectorized(벡터화된) 쿼리 실행 엔진을 사용
  • TPC-H, TPC-DS 벤치마크로 테스트
    • TPC-H : Decision Support Benchmark, 공급망/구매 데이터로 구성된 비즈니스 의사결정 시나리오
    • TPC-DS : Data Warehouse Benchmark, 온라인 쇼핑 데이터로 구성된 데이터 웨어하우스 시나리오
    • 확인하는 것들 : 쿼리 응답 시간, 처리량, 사용자가 많을 때의 처리량, 데이터 크기별 성능


5. Extensible


6. Free

  • DuckDB의 개발자들은 네덜란드 공무원일 때 개발을 시작
  • 누구나 자유롭게 제공하는 것을 사회에 대한 책임과 의무로 봄
  • 따라서 MIT 라이선스를 채택


7. Thorough Testing

  • 최초엔 Research 그룹에서 만들었지만, 안정적인 데이터베이스 시스템이 되는 것을 목표로 함
  • 여러 플랫폼과 컴파일러에서 반복적으로 테스트
  • Github Actions을 보면 여러 테스트가 돌아가고 있는 것을 확인할 수 있음


DuckDB 성능 벤치마크

DuckDB GROUP BY Benchmark

DuckDB JOIN Benchmark

  • Benchmarking Ourselves over Time at DuckDB 이 글도 확인해보니, 시간의 흐름에 따라 DuckDB가 더욱 빨라지고 있는 내용을 담고 있었음. 2023년 말에 급격한 성능 향상이 존재
    • 최근 3년 동안 3~25배 빨라지고, 10배 많은 데이터를 분석할 수 있게 됨

DuckDB benchmark results over time

  • Pandas보단 확실히 빠르고, Rust로 만들어진 Polars와 많이 비교됨
    • DuckDB와 Polars, Pandas를 비교한 Repo : 이 결과에서도 DuckDB가 더 빠르다고 나옴
    • 회사에 도입하려고 하면 우리 데이터로 테스트를 충분히 하고 선택할 것. 벤치마크 결과를 항상 신뢰하는 것보다, 다시 점검하는 것이 필요


DuckDB 설치

  • 공식 문서에 내용을 확인할 수 있음
  • Mac OS 기준

    brew install duckdb
    
  • 윈도우

    winget install DuckDB.cli
    
  • Linux : 최신 버전은 Github에서 확인

    wget https://github.com/duckdb/duckdb/releases/download/v1.1.2/duckdb_cli-linux-aarch64.zip
    
    unzip duckdb_cli-linux-aarch64.zip
    
    ./duckdb
    



DuckDB 실행하기

  • CLI에서 실행은 간단함

    duckdb
    

DuckDB in CLI

  • 데이터 분석가 관점에서 DuckDB를 사용한다면 Jupyter Lab을 사용할 텐데, brew로 설치하면 파이썬 패키지도 설치되어 바로 사용할 수 있음
  • Jupyter Lab 실행
jupyter lab


간단한 실행(DuckDB Python)

  • DuckDB를 사용할 때, duckdb.sql로 실행하는 방법과 duckdb.connect로 Connection을 생성해서 쓰는 방법이 존재
    • 1) duckdb.sql : 전역 메서드로 사용
      • 간단한 스크립트나 빠른 프로토타이핑, 노트북에서 유용
      • 메모리에 데이터를 저장함
      import duckdb
      duckdb.sql("SELECT 1")
      
    • 2) duckdb.connect : Connection 객체를 생성해서 사용
      • 커넥션을 생성할 때 In-Memory 모드와 Both Persistent 모드를 선택할 수 있음
        • Disk에 Spilling을 사용해 메모리보다 큰 워크로드에서 데이터를 처리할 수 있음
          • 특정 작업이나 메모리가 부족할 때 데이터를 디스크에 임시로 저장하는 기법
      • 별도의 옵션을 지정하지 않거나 :memory: 옵션을 사용하면 In-Memory 모드로 생성
      • 파일 경로를 지정하면 파일이 생성되거나 사용됨
      # 데이터를 메모리에 저장해서 사용
      import duckdb
      
      con = duckdb.connect(":memory:")
      
      # 이 케이스에는 test.db 파일을 생성
      import duckdb
      
      con = duckdb.connect("test.db")
      
  • .show()는 결과를 출력만 하고 None을 반환
  • .show()를 사용하지 않으면 Relation 객체로 반환되어서 다른 곳에서 사용 가능
  • .df()를 사용하면 Pandas DataFrame으로 변경. 그러나 DuckDB를 사용하면 .df()는 거의 사용하지 않거나 가공을 DuckDB로 최대한 한 후에 사용하는 것이 좋을 듯(실행 시간이 느린 것이 체감됨)

    import duckdb
    duckdb.sql("SELECT 1").show()
    output = duckdb.sql("SELECT 1 AS a")
    duckdb.sql("SELECT a+1 AS b FROM output").show()
    new_df = duckdb.sql("SELECT 1 AS a").df()
    


Data Load

  • CSV, Parquet, JSON에 바로 접근할 수 있음
    • read_csv, read_parquet, read_json 함수를 사용
  • NYC 택시 데이터를 다운받아 테스트(약 300만 Row)

    wget https://d37ci6vzurychx.cloudfront.net/trip-data/yellow_tripdata_2024-01.parquet
    
  • 데이터 DESCRIBE

    duckdb.sql("DESCRIBE SELECT * FROM 'yellow_tripdata_2024-01.parquet'")
    

  • Parquet 파일 Load
    • 노트북 파일과 동일한 경로에 파일이 존재함
    • read_parquet를 사용하는 것과 사용하지 않는 것의 차이는 없음
    • 경로에서 glob 패턴을 사용할 수 있음 : SELECT * FROM ‘test/*.parquet’;
    duckdb.sql("SELECT * FROM 'yellow_tripdata_2024-01.parquet' LIMIT 3")
    
    duckdb.sql("SELECT * FROM read_parquet('yellow_tripdata_2024-01.parquet') LIMIT 3")
    

  • Parquet 파일을 읽고, 쿼리문에서 사용하고 싶다면 다음과 같이 사용할 수 있음
  nyc_data = duckdb.read_parquet("yellow_tripdata_2024-01.parquet")
  duckdb.sql("SELECT * FROM nyc_data")


jupysql을 사용해 더 편하게 실행하기

  • 위 코드를 보면 항상 duckdb 또는 conn을 입력하고 쿼리를 실행해야 함. 반복적으로 하다 보면 이런 것들이 번거로움
  • jupysql을 사용하면 간단하게 실행할 수 있음
  • jupysql 설치

    !pip install jupysql
    
  • SQL 쿼리를 실행한 후, Pandas DataFrame으로 항상 저장하는 옵션 설정

    %config SqlMagic.autopandas = True
    
  • Extension Load
    • 메모리 모드로 conn 생성
    %load_ext sql
    conn = duckdb.connect(':memory:')
    %sql conn --alias duckdb
    
  • 별도의 셸에서 실행. %sql, %%sql을 사용. %%sql은 여러 줄 쿼리 실행 가능
    • 한 셸에서 여러 명령을 실행하려면 세미콜론으로 구분해야 함
    %%sql 
    SELECT
      DATE_TRUNC('hour', tpep_pickup_datetime) AS pickup_hour,
      COUNT(*) AS cnt
    FROM read_parquet('yellow_tripdata_2024-01.parquet')
    GROUP BY ALL
    

  • 위에 사용한 쿼리의 결과를 저장하지 않아서 사용할 수 없고, <<을 사용해 변수에 저장할 수 있음

    %%sql hour_cnt << 
    SELECT
      DATE_TRUNC('hour', tpep_pickup_datetime) AS pickup_hour,
      COUNT(*) AS cnt
    FROM read_parquet('yellow_tripdata_2024-01.parquet')
    GROUP BY ALL
    
    hour_cnt
    


DuckDB UI

  • 2025년 3월 12일에 DuckDB Local UI가 출시됨
  • DuckDB UI 실행하는 방법(CLI)

      duckdb -ui
    
  • SQL 명령어에서 실행하고 싶다면
CALL start_ui();
  • Localhost:4213으로 실행됨
    • 처음에 들어가면 노트북 파일이 없는데 샘플 노트북을 클릭하고, 코드를 실행하면서 구성 요소를 확인할 수 있음
  • UI가 없는 시기보다 훨씬 편해졌으며, 아직 UI에 추가되면 좋을 기능들이 보이지만 앞으로 계속 발전할 것으로 예상

  • 저장된 파일 정보는 duckdb 설치 위치 내부에 저장되어 있음(~/.duckdb/extension_data/ui/ui.db)


SQL 문법

  • DuckDB SQL 문서를 확인하면 자세한 문법을 확인할 수 있음
  • 일반적인 SQL 문법은 대부분 지원하고, PostgreSQL과 유사함
  • SQL 문법이 궁금하시면 제가 인프런에 무료로 공개해둔 초보자를 위한 BigQuery(SQL) 입문 강의를 추천
    • 문법 중에 일부만 다르고, 대부분 큰 흐름은 유사함
  • SQL 문법은 기억하고 싶은 일부분만 기록


GROUPING SETS, ROLLUP, CUBE

  • 여러 차원에 대한 그룹화를 실행할 때 사용
  • GROUP BY에 포함됨
  • GROUPING SETS : 여러 차원에 대한 그룹화
    • 어떤 차원을 그룹화할지 직접 지정
  • ROLLUP : GROUPING SETS의 특수한 형태. 주어진 차원의 Sub Group을 생성
    • (country, city, zip) -> (country, city), (country), ()이 생성
    • () : 전체를 의미
  • CUBE : GROUPING SETS의 또 다른 특수한 형태. 모든 가능한 조합을 생성
    • (country, city, zip) -> (country, city, zip), (country, city), (country, zip), (city, zip), (country), (city), (zip), ()
  • GROUPING() 함수는 해당 컬럼이 그룹화되었는지 아닌지를 비트로 표현
    • 3개의 컬럼일 경우 0 = 000, 1 = 001, 2 = 010, 3 = 011, 4 = 100, 5 = 101, 6 = 110, 7 = 111
  • 샘플 데이터 생성

    %%sql
    CREATE TABLE sales (
        category VARCHAR,
        product_name VARCHAR,
        region VARCHAR,
        sale_date DATE,
        sales_amount INT
    );
    
    -- 샘플 데이터 삽입
    INSERT INTO sales VALUES
        ('전자기기', '스마트폰', '서울', '2024-01-01', 1200000),
        ('전자기기', '스마트폰', '부산', '2024-01-01', 800000),
        ('전자기기', '노트북', '서울', '2024-01-01', 2000000),
        ('전자기기', '노트북', '부산', '2024-01-01', 1500000),
        ('의류', '청바지', '서울', '2024-01-01', 89000),
        ('의류', '청바지', '부산', '2024-01-01', 78000),
        ('의류', '티셔츠', '서울', '2024-01-01', 45000),
        ('의류', '티셔츠', '부산', '2024-01-01', 35000);
    
    %sql SELECT * FROM sales
    

  • GROUPING SETS 실행

    %%sql
    
    # GROUPING SETS
    SELECT 
        category,
        region,
        SUM(sales_amount) AS total_sales,
        COUNT(*) AS transaction_count
    FROM sales
    GROUP BY GROUPING SETS (
        (category, region),
        (region),
        ()
    )
    ORDER BY category NULLS LAST, region NULLS LAST;
    

    • category None은 region의 SUM, category, region 둘 다 None은 전체 합
  • ROLLUP

    %%sql
    
    # ROLLUP
    SELECT 
        category,
        product_name,
        region,
        SUM(sales_amount) AS total_sales
    FROM sales
    GROUP BY ROLLUP(category, product_name, region)
    ORDER BY category NULLS LAST, product_name NULLS LAST, region NULLS LAST;
    

  • (category, product_name, region) => (category, product_name, region), (category, product_name), (category), ()로 나타남

  • CUBE

    %%sql
    
    # CUBE
    SELECT 
        category,
        product_name,
        SUM(sales_amount) AS total_sales,
        COUNT(*) AS transaction_count,
        GROUPING(category, product_name) AS grouping_level
    FROM sales
    GROUP BY CUBE(category, product_name)
    ORDER BY category NULLS LAST, product_name NULLS LAST;
    

  • (category, product_name) => (category, product_name), (category), (product_name), ()
  • GROUPING 함수를 사용해 grouping_level을 구함(2개라서 0~3)


ORDER BY NULLS LAST

  • 위 쿼리에서 나온 예시
  • 정렬할 때 NULLS의 위치를 지정할 수 있음
  • NULLS {FIRST, LAST}


DATE 함수

  • 다양한 Database에서 차이가 있는 것은 대부분 Date 함수
  • 이 내용들은 공식 문서를 확인하는 것이 제일 좋은 방법
  • date_part(part, date) : BigQuery와 반대
    • date_partdatepart로도 사용할 수 있음


FILTER

  • SELECT 절의 집계 함수에서 사용할 수 있음. 특정 집계 함수에서만 적용되는 필터링 기능
    • FILTER가 없으면 CASE WHEN을 사용하는 것이 일반적
%%sql

SELECT 
    category,
    SUM(sales_amount) as total_sales,
    SUM(sales_amount) FILTER (WHERE region = '서울') AS seoul_sales,
    SUM(sales_amount) FILTER (WHERE region = '부산') AS busan_sales,
    -- 각 지역의 매출 비중도 계산
    ROUND(SUM(sales_amount) FILTER (WHERE region = '서울') * 100.0 / SUM(sales_amount), 1) AS seoul_percentage,
    ROUND(SUM(sales_amount) FILTER (WHERE region = '부산') * 100.0 / SUM(sales_amount), 1) AS busan_percentage
FROM sales
GROUP BY category;


UDF

  • 보통 SQL로 UDF를 만들 수 있는데, DuckDB는 24년 10월 기준 Python 함수로 UDF를 생성
  • DuckDB python Function API
  • 파이썬 함수를 만들고, duckdb.create_function 함수를 사용해 등록
    • con.create_function(name, function, parameters, return_type)
    • 이름, 함수, 파라미터, 리턴 타입을 명시해야 함
  • 예시: 텍스트에서 이모지를 추출하는 UDF 만들기

    !pip install emoji
    
    import duckdb
    import emoji  # pip install emoji 필요
    
    def extract_emojis(text):
        if text is None:
            return None
        return ' '.join([c for c in text if emoji.is_emoji(c)])
    
    con_udf = duckdb.connect()
    
    # Python 함수를 DuckDB UDF로 등록
    con_udf.create_function('extract_emojis', extract_emojis, ['VARCHAR'], 'VARCHAR')
    
    %sql con_udf
    # con_udf 연결
    
    %%sql
    # 테스트용 데이터 생성
    CREATE OR REPLACE TABLE messages AS 
      SELECT * FROM (
        VALUES 
        ('안녕하세요 👋 오늘도 화이팅! 🔥'),
        ('맛있는 저녁 먹었어요 🍜 🍖'),
        ('날씨가 좋네요 ☀️'),
        ('축하해요! 🎉 🎊 🎈')
    ) AS t(message);
    
    %%sql
    SELECT 
        message,
        extract_emojis(message) AS emojis
    FROM messages
    

Secrets Manager

  • Secrets Manager 문서
  • DuckDB에서 S3, GCS 등에 접근할 수 있음(내부적으로 httpfs Extension을 사용)
  • SECRET을 생성하면, 그 후에 사용할 수 있음
  • S3 Temporary Secrets
    • 메모리에 저장
    • DuckDB가 실행된 동안에만 활용 가능
    CREATE SECRET my_secret (
        TYPE S3,
        KEY_ID 'my_secret_key',
        SECRET 'my_secret_value',
        REGION 'my_region'
    );
    
  • S3 Persistent Secrets
    • 파일에 저장(~/.duckdb/stored_secrets)
    • 다음 세션에서도 자동으로 로드
    • 자주 사용해야 하면 Persistent Secret을 사용하는 것이 편할 수 있음. 그러나 보안에 취약해질 수 있는 점(Temporary Secret은 메모리에 저장되기 때문에 세션이 종료되면 사라짐)
    • 디스크에 암호화되지 않은 바이너리 형식으로 저장됨
    CREATE PERSISTENT SECRET my_persistent_secret (
        TYPE S3,
        KEY_ID 'my_secret_key',
        SECRET 'my_secret_value'
    );
    
  • 위 설정이 정상적으로 되었다면 s3:// 프리픽스를 사용할 수 있음

    SELECT *
    FROM 's3://my-bucket/file.parquet';
    
  • Secrets 삭제

    DROP PERSISTENT SECRET my_persistent_secret;
    



DuckDB Extension

  • Extension 문서
  • DuckDB는 익스텐션 기능을 동적으로 Load 가능
  • DuckDB Extension은 아직 많이 없어서 오픈소스에 기여하고 싶은 분들이 시도해도 좋을 것 같음
  • 설치된 목록 확인하기

    %%sql
    SELECT 
        extension_name, 
        installed, 
        description
    FROM duckdb_extensions()
    

  • 설치 후, Load
    • INSTALL : 설치는 1회만 하면 됨
    • LOAD : 세션마다 Load 필요
    %%sql
    INSTALL spatial;
    LOAD spatial;
    


bigquery

  • BigQuery 데이터에 쿼리할 수 있는 Extension
    • ATTACH로 접근한 후, D를 붙여서 쿼리 실행
    %%sql
    INSTALL bigquery FROM community;
    LOAD bigquery;
    
    %%sql
    D ATTACH 'project=my_gcp_project' AS bq (TYPE bigquery, READ_ONLY);
    D SELECT * FROM bq.quacking_dataset.duck_tbl;
    


h3


pg_duckdb

  • 이 글을 작성하기 2일 전에 나온 글 : pg_duckdb beta release : Even faster analytics in Postgres
  • PostgreSQL에 DuckDB 분석 엔진을 통합
  • PostgreSQL은 분석 목적이 아닌 트랜잭션 데이터베이스로 만들었지만, 종종 분석에 사용됨
    • 처음엔 괜찮지만 데이터가 많아지면서 한계에 부딪힘
    • 이럴 때 DuckDB를 사용하면 이점을 볼 수 있음
  • 글에서 나온 인덱스 없이 TPC-DS 데이터셋을 로드하는 경우, 기본 PostgreSQL에 pg_duckdb가 1,500배 빨랐다고 함. 스크립트
  • 나온지 얼마 안되서 아직 사용하긴 애매하지만, 더 발전하면 PostgreSQL에서 간단한 분석을 하거나 제품의 사용자가 많지 않을 때 DW 대용으로 사용할 수 있을 것 같음
    • 만약 DW가 없어진다면 과거에 만들었던 데이터 파이프라인은 꽤 바뀔 수 있을 것 같음




DuckDB 활용 방안


BigQuery에서 사용하는 예시

  • (1) BigQuery의 SQL 쿼리로 데이터를 전처리 => GCS에 Parquet로 저장 => Local에서 DuckDB에서 GCS에 접근해서 분석
  • (2) pd.read_gbq를 사용해 BigQuery의 데이터를 Pandas Dataframe으로 가져온 후, 그 데이터를 DuckDB에 연결해서 분석
  • How to combine BigQuery with DuckDB 글을 보면 gsutil을 사용해서 GCS에 데이터를 추출하고, 그 데이터에 접근. 그 후에 다시 bq 커맨드를 사용해 BigQuery에 Load


로컬 데이터 웨어하우스처럼 사용(Pandas 대체)

  • 기존에 로컬에서 데이터 분석을 하거나, 데이터 전처리를 할 때 Pandas를 많이 사용할 텐데 이젠 DuckDB로 대체할 수 있을 것
  • SQL로 데이터를 전처리하는 것이 더 좋은 방법이라고 생각
  • S3, GCS 등에 Parquet로 데이터 저장하고, 거기에서 쿼리를 실행하는 방식


필요할 때 사용하는 가벼운 분석 엔진 용도

  • OLTP의 가벼운 버전인 SQLite의 OLAP 버전이 DuckDB
  • Duckdb-wasm을 사용해 웹에서도 사용 가능


ETL, ELT 파이프라인에서 Transform 단계에서 사용

  • Spark나 Pandas로 데이터를 처리하는 과정에서 DuckDB를 사용할 수 있을 것
  • 데이터가 엄청 많은 경우엔 Spark를 사용할 텐데, 이때는 아직은 어려울 수 있을 것(테스트를 꼭 해보고 판단해보는 것을 추천)


GCS에 있는 Parquet 쿼리하기


httpfs 설치(brew로 설치하면 기본으로 설치됨)

%%sql
INSTALL httpfs FROM community;
LOAD httpfs;


Credential 설정

  • HMAC Keys 생성
    • HMAC Key에 대해 궁금하면 Google Cloud 문서 참고
    • S3와 호환되는 라이브러리를 사용해 GCS에 접근할 때 사용
    • Access Key ID와 Secret Access Key로 구성
    • 서비스 계정의 인증 수단으로 활용이라고 볼 수 있고, GCS 접근용으로만 사용
  • 아래 이미지에서 서비스 계정의 키 만들기 -> 서비스 계정 생성(Cloud Storage 권한 부여 : 스토리지 객체 사용자) -> 액세스 키 생성

  • 아래 코드를 실행해서 DuckDB에 Secret 등록
%%sql
CREATE OR REPLACE TEMPORARY SECRET gcs_secret (
    TYPE GCS,
    KEY_ID 'KEY ID를 넣어주세요',
    SECRET 'SECRET를 넣어주세요'
);


GCS의 Parquet를 불러와서 DuckDB에 저장

  • GCS의 Parquet를 불러올 때 read_parquet 함수를 사용
  • 다만 매번 GCS에서 불러오는 것은 네트워크 비용을 생각하면 느린 일이기 때문에, DuckDB의 Table로 생성해서 사용
%%sql  
CREATE TABLE yello_taxi AS
SELECT *
FROM read_parquet('gs://여러분들의_GCS_BUCKET/파일_경로.parquet');
  • warning 메시지는 sqlglot parser에서 발생하는 것 같으나, 동작에 문제는 없음


그 후에 쿼리 실행

  • 당연한 이야기지만 yello_taxi 테이블을 만들어서 속도가 빠름
  • 매번 read_parquet(‘gcs://~’)를 실행하면 데이터를 가지고 오는 시간이 소요되어서 느렸음
  • 약 300만 Row를 GROUP BY 하는데 Wall time이 65.5ms => 0.06초
%%time
%%sql

SELECT
  DATE_TRUNC('hour', tpep_pickup_datetime) as pickup_hour,
  COUNT(*) as cnt
FROM yello_taxi
GROUP BY ALL;


참고 삼아 BigQuery로 실행

  • BigQuery 테이블을 생성한 후, 동일한 쿼리를 실행
  • 경과 시간에 954ms로 DuckDB가 15배 빠름
  • 성능 비교할 땐 하나의 케이스만 보고 판단하면 안되고, 데이터가 점점 많아질 때나 쿼리의 복잡도에 따라 여러번 실행해야 함
    • DuckDB는 1인 사용자를 위해 최적화가 되어있고, BigQuery는 여러 사용자가 실행해도 문제가 없음
    • 다시 생각해도 회사의 DW가 존재하고 => DW에서 데이터를 가지고 와서 DuckDB에서 분석하는 흐름이 좋을 것 같음
  • BigQuery와 DuckDB 성능 비교를 한 글도 보면 유용함



정리

  • DuckDB가 데이터 분석, 데이터 엔지니어링에서 영향력을 점점 넓힐 것으로 예상
  • 과거부터 존재했던 데이터 웨어하우스를 사용하면서, 로컬에서 빠르게 처리할 때 활용할 수 있을 것
    • DW 비용도 더욱 줄일 수 있음
  • 아예 새롭게 파이프라인을 만든다고 하면 Spark를 사용하지 않고 DuckDB를 사용하는 것도 가능할 것으로 보임. PostgreSQL을 사용하면 규모가 커지기 전까지 모두 DuckDB를 활용
  • Reddit에서 검색해보니 다양한 토론이 존재
  • AWS Lambda에서 DuckDB를 사용하는 케이스도 존재 : serverless-duckdb. Duckdb-wasm이 있으니 프론트엔드에서도 활용할 수 있음
  • MotherDuck 가격도 확인해보니, STANDARD 가격이 월 25달러부터 시작해서 생각보다 저렴하게 사용할 수 있을지도(멤버 별 과금이 아니라 사용량에 기반한 과금)
  • DuckDB의 단점은 하나의 프로세스에서 실행되는 점인데, 이건 애초에 설계할 때 염두하지 않은 내용. 여러 프로세스에서 READ는 가능, WRITE는 불가능. Handling Concurrency
  • DuckDB의 성능 이슈가 있다면 Performance Guide, My Workload Is Slow 글을 보면 도움이 될 것 같음
    • FAQ에도 성능, 어떻게 사용할지가 나옴



참고 자료



  • 글 작성하는데 걸린 시간 : 7시간 20분
    • 하고자 하는 이야기, 개요 정리 : 20분
    • 초안 글 작성 : 6시간 10분
    • 클로드/Cursor와 셀프 글 피드백 : 30분
    • 2차 글 작성 : 20분
  • 간단 회고
    • DuckDB는 어느정도 사용하고, 공부도 이미 한 주제인데 방대한 내용을 정리하고 예시를 추가하는 과정에서 시간이 소요됨

카일스쿨 유튜브 채널을 만들었습니다. 데이터 분석, 커리어에 대한 내용을 공유드릴 예정입니다.

PM을 위한 데이터 리터러시 강의를 만들었습니다. 문제 정의, 지표, 실험 설계, 문화 만들기, 로그 설계, 회고 등을 담은 강의입니다

이 글이 도움이 되셨거나 의견이 있으시면 댓글 남겨주셔요.

Buy me a coffeeBuy me a coffee





© 2017. by Seongyun Byeon

Powered by zzsza