-
[트러블슈팅]DuckDB에서 복수의 parquet파일에 접근할때 컬럼을 못찾는 문제Programming/python 2025. 3. 30. 23:55
SELECT a, b, c, d, f FROM read_parquet("s3://some-data/schema_name/table_name/base_date=*/*", hive_partitioning=true) WHERE base_date = '2025-03-26'
S3에 있는 하이브 파티셔닝된 폴더에 parquet파일이 저장되어있다고 했었을때, 위와 같은 쿼리로 duckdb sql같은 메소드를 사용할때 아래와 같은 에러가 발생한다.
--------------------------------------------------------------------------- BinderException Traceback (most recent call last) File <timed exec>:1 BinderException: Binder Error: Table "read_parquet" does not have a column named "f" Candidate bindings: : "d" LINE 7: a, b, c, d, f ^
read_parquet함수를 사용할때 "f"라는 컬럼이 없다면서 뜨는 에러인데 여기서 문제는 해당 parquet파일을 열어보면 모든 로우에 f라는 컬럼이 존재한다는 사실이다.
이는 아스테릭을 사용해서 복수의 파일을 읽을때 다른 파일에 해당 스키마가 존재하지 않을 때 발생하는 에러인데, 해결 방법과 함께 원인도 함께 알아보도록 하자.
문제 원인
https://duckdb.org/docs/stable/data/multiple_files/combining_schemas#combining-schemas
Combining Schemas
Examples Read a set of CSV files combining columns by position: SELECT * FROM read_csv('flights*.csv'); Read a set of CSV files combining columns by name: SELECT * FROM read_csv('flights*.csv', union_by_name = true); Combining Schemas When reading from mul
duckdb.org
다중 파일들을 가져올때 duckDB는 정책적으로 이 파일들의 스키마들을 합친다. 이는 각 파일마다 다를 수 있는 고유의 스키마가 존재할 수 있기 때문이다. DuckDB는 다중 파일의 스키마 통합에 두가지 방식을 제공한다.
- 컬럼 위치 기준 방식
- 컬럼 이름 기준 방식
DuckDB는 기본적으로 첫번째 파일을 기준으로 스키마를 읽고 컬럼 위치에 따라서 후속파일의 스키마를 통합한다. 1번 방식이 default값이다. 하지만 이 방식은 모든 컬럼이 동일하다는 가정에서 유효한 방식이다.
1번 방식은 다음과 같은 예를 통해서 설명할 수 있다.
flights1.csv
FlightDate|UniqueCarrier|OriginCityName|DestCityName 1988-01-01|AA|New York, NY|Los Angeles, CA 1988-01-02|AA|New York, NY|Los Angeles, CA
flights2.csv
FlightDate|UniqueCarrier|OriginCityName|DestCityName 1988-01-03|AA|New York, NY|Los Angeles, CA
위와 같은 다른 복수의 두개의 파일이 있다고 했을 때 Duckdb는 컬럼의 위치로 통합을 해서 아래와 같은 결과를 구성한다.
FlightDate UniqueCarrier OriginCityName DestCityName 1988-01-01 AA New York, NY Los Angeles, CA 1988-01-02 AA New York, NY Los Angeles, CA 1988-01-03 AA New York, NY Los Angeles, CA 따라서 파일의 스키마가 다를 경우에는 union_by_name 옵션을 사용해서 DuckDB가 대신해서 모든 컬럼 이름을 읽도록 설정할 수 있다.(2번 방식)
2번 방식은 다음과 같은 예를 통해서 설명할 수 있다.
만약 컬럼이 추가되거나 컬럼명이 변경되어서 다른 스키마의 복수의 파일을 읽으려고 할때는 이름으로 통합하는 방식이다. 이 작업은 앞서 설명한 바와 같이 union_by_name이라는 옵션을 통해서 사용할 수 있다.
flights3.csv
FlightDate|OriginCityName|DestCityName 1988-01-01|New York, NY|Los Angeles, CA 1988-01-02|New York, NY|Los Angeles, CA
flights4.csv
FlightDate|UniqueCarrier|OriginCityName|DestCityName 1988-01-03|AA|New York, NY|Los Angeles, CA
이 파일들의 컬럼들을 위치 기준으로 통합하려고 한다면 서로 컬럼의 숫자가 다르기 때문에 오류가 발생한다. union_by_name 옵션을 통해 컬럼을 통합하면 찾지 못한 값은 NULL로 설정되어 올바르게 통합된다.
FlightDate OriginCityName DestCityName UniqueCarrier 1988-01-01 New York, NY Los Angeles, CA NULL 1988-01-02 New York, NY Los Angeles, CA NULL 1988-01-03 New York, NY Los Angeles, CA AA 해결방법1
위에 나온대로 union_by_name 옵션을 통해 이름으로 통합하는 방식으로 변경한다.
SELECT a, b, c, d, f FROM read_parquet("s3://some-data/schema_name/table_name/base_date=*/*", hive_partitioning=true, union_by_name = true) WHERE base_date = '2025-03-26'
해결 방법2
SELECT a, b, c, d, f FROM read_parquet("s3://some-data/schema_name/table_name/base_date=2025-03-26/*", hive_partitioning=true) WHERE base_date = '2025-03-26'
위와 같이 where절을 통해 명시할 수 있는 부분이라면 최대한 해당 파티션안에 명시해서 접근해서 가져오는게 훨씬 속도도 빠르고 해당 스키마만 가져올 수 있다. 풀스캐닝을 피할 수 있는 편법인 셈이다.
다만 이 방식은 where절에 있는 필터 조건이 파티션 되어있을 경우에 사용할 수 있는 방식이므로 애초에 폴더안에 여러 파일로 구성되어있다면 사용할 수 없다.
따라서 스키마를 검사해서 달라진 파일의 위치를 duckDB가 읽는 것의 뒷부분으로 파일 이름을 바꿔서 위치시켜준다면 1번 방식으로 읽을 때 가장 첫번째 파일을의 스키마의 컬럼 기준으로 읽기 때문에 해당 쿼리가 접근하려는 쿼리의 기준 파일을 맨 위에 위치시켜주도록 하는 것이 가장 최소한으로 수정하고 해당 문제를 피하면서 빠르게 파일을 스캔할 수 있는 방법이다.
%%time df = duckdb.execute(query2).fetchdf() # CPU times: user 12.2 s, sys: 3 s, total: 15.2 s # Wall time: 4min 52s
참고로 해결방법1과 같이 union_by_name 옵션은 모든 파일의 스키마를 읽어야하기 때문에 메모리 소비를 증가시킬 수 있고 속도도 느려진다. 내 기준으로 위의 쿼리는 약 5분정도 걸렸었는데, 해결 방법2를 통해서 문제를 해결하면 해당 파일을 가져오는데 2초가 걸렸다. 무려 150배의 시간 차이가 발생하는 것이다.
이를 감안한다면 특히 파일의 갯수가 많아질수록 메모리의 소비도 많고 시간도 오래걸리는 이름을 통한 통합 방식은 지양해야겠다.
'Programming > python' 카테고리의 다른 글
airflow의 새로운 경쟁자 오케스트레이션 플랫폼 dagster (0) 2021.12.23 [에러로그]parquet파일 변환 fastparquet v.s pyarrow (0) 2021.11.19 파이썬 데코레이터에서 wraps를 사용해야 하는 이유 (0) 2021.06.27 [python]str을 dict로 바꾸는 두 가지 방법 (2) 2021.01.24 파이썬 제너레이터(Generator) (0) 2020.06.06