분석 친화적인 데이터레이크로 진화하기

그런데, Iceberg와 DuckDB를 첨가한..

황솔희
중고나라 기술 블로그

--

안녕하세요. 중고나라 머신러닝팀 데이터엔지니어 황솔희입니다.

낯설고 넓은 사무실에서 메모장을 찾아야할 때, 여러분들은 어떻게 소통하시는 것이 더 편하신가요?

질문: 메모장은 어디에 있나요?

답변1: 메모장은 라운지 왼쪽에서부터 3번째에 있는 서랍의 2번째 칸의 1번째 파티션에 있어요.

답변2: 메모장은 라운지 서랍 중에 사무용품 라벨링이 붙어있는 곳에 있어요.

대부분이 전자처럼 위치를 정확히 서술하는 것보다 후자와 같이 간결하게 얘기하는 것이 더 편하실 거에요.

또 누군가 답변을 해주지 않는다면 서랍을 하나씩 열어가면서 메모장이 있는지 없는지 확인해야할 거에요.

데이터도 이와 비슷한데요 😀

데이터를 “답변1”과 같이 물리적으로 설명하는 것보다 “답변2”처럼 논리적으로 얘기하면 데이터를 분석하거나 커뮤니케이션 할 때 훨씬 간결하게 사용하실 수 있어요. 또, 서랍에 무엇이 들어있는지를 탐색하는 것과 비슷하게 필요한 데이터도 쉽게 파악할 수 있는데요,

오늘은 중고나라에서 데이터 사용자를 위해 이런 점을 고민하고 아키텍쳐를 개선했던 경험을 공유드리고자합니다.

핵심 키워드

  • Apache Iceberg
  • DuckDB

중고나라의 데이터 아키텍쳐 — Before

중고나라에서는 AWS S3에 bronze tier의 raw 데이터를 저장하고 이후 일정 시간 배치를 통해 전처리 및 bulk하는 작업을 거쳐 다른 경로에 저장하고 있어요. 이럴 경우 아래와 같은 문제점이 있었어요.

문제점

  • 파일 데이터를 분석하기 위해 Python의 Pandas 라이브러리를 사용하고 있으며, Pandas에 익숙하지 않은 경우에는 러닝커브로 인해 분석이 힘들었어요.
  • 인트로에서 서랍에 비유했던 것처럼 정확한 S3 파일 경로를 통해 AWS SDK를 사용하여 데이터를 가져와야해서 S3의 정확한 경로를 모르거나 개발자가 아닌 경우는 사용하기 어려웠어요.
  • AWS Athena로 파일 데이터를 SQL을 통해 조회할 수 있지만 파일 스캔 당 요금이 과금되며 로컬 머신에서 실행되지 않을만큼 많은 양의 데이터가 아닌 경우 오버스펙이 될 수 있어요.

위와 같은 점에서 고민을 하며, 이를 Apache Iceberg와 DuckDB를 통해 풀어가려고 했는데요,

이 두가지를 조금 더 자세히 설명 드릴게요.🙂

Apache Iceberg

Apache Iceberg는 대용량 분석을 위한 high-performance format 으로 아래와 같은 특징이 있어요.

  • 데이터 스키마 관리
  • ACID
  • 데이터 버저닝 및 파티셔닝
  • SQL 을 사용한 데이터 관리
  • 데이터 저장 프로퍼티 관리

Iceberg Data Catalog

Iceberg를 사용하기 위해서는 데이터의 메타 데이터를 저장할 카탈로그가 필요해요.

메타 데이터는 용어처럼 “데이터를 설명하는 데이터”로 Iceberg의 경우 메타 데이터로 다음 정보를 가지고 있어요.

  • 데이터가 어디에 저장되어있는지?
  • 데이터의 스키마는 어떠한지? 어떤 데이터인지?
  • 데이터의 압축은 어떻게 되어있는지?
  • 히스토리는 어떻게 변했는지?

MySQL, PostgreSQL과 같은 데이터베이스나 Hive를 카탈로그로도 사용할 수 있지만, Iceberg가 이런 자산들의 상태에 의존적이게되며 관리 및 운영이 필요하다는 점에서 중고나라는 서버리스이면서 AWS 내 서비스와 연동이 간편한 Glue Data Catalog 를 선택했어요.

Iceberg with AWS Glue Data Catalog

기존 데이터 아키텍쳐에는 bronze tier 데이터의 전처리 과정에서 코드 작업을 통해 데이터 파티션을 지정하였는데요, 이런 경우 아래와 같은 이슈가 있었습니다.

  • 데이터 스키마를 pyspark object로 관리하여 코드에 접근할 수 없는 유저는 스키마를 파악하기 힘들어요.
  • 데이터 필드에 대한 코멘트를 코드 내 주석으로 관리하여 접근 및 활용이 어려워요.

위 이유로 데이터를 분석하는 입장에서 원하는 데이터를 추출하기 위해 데이터를 탐색하는 과정이 길어졌어요.

이런 문제점을 Glue Data Catalog를 사용하여 풀고자했어요.

Glue Data Catalog를 메타 데이터 스토어로 사용하여 데이터의 스키마, 저장된 장소, 코멘트 정보를 저장하고 Catalog의 별칭으로 정확한 데이터 저장 장소를 명시하지 않고 편리하게 데이터를 가져올 수 있도록 구성하였어요.

Athena 와도 연동하기 편리하며 Data Discovery Platform인 DataHub에 연결하여 사용자가 필요한 데이터를 검색하여 스키마와 코멘트를 확인할 수 있어요. 😊

Iceberg로 파일 스키마 및 코멘트 관리하기

Apache Spark 또는 Python 의 Pyiceberg 라이브러리를 사용하여 파일의 스키마와 코멘트, 파티션, 프로퍼티 등을 관리할 수 있어요.

Pyiceberg를 사용할 경우 extension 설치나 Spark 구성을 하지 않아도 되어 간편하지만, SQL문과 다소 차이가 있어 가독성이 좋지 않았어요. 이런 이유로 중고나라에서는 Spark(Pyspark)를 사용해서 Iceberg 카탈로그를 관리하고 있어요.

Pyiceberg

# pyiceberg (공식 docs 예시) https://py.iceberg.apache.org/api/#create-a-table
schema = Schema(
NestedField(field_id=1, name="datetime", field_type=TimestampType(), required=True),
NestedField(field_id=2, name="symbol", field_type=StringType(), required=True),
NestedField(field_id=3, name="bid", field_type=FloatType(), required=False),
NestedField(field_id=4, name="ask", field_type=DoubleType(), required=False),
NestedField(
field_id=5,
name="details",
field_type=StructType(
NestedField(
field_id=4, name="created_by", field_type=StringType(), required=False
),
),
required=False,
),
)

Apache Spark(Pyspark)

# set spark session
spark = SparkSession.builder \
.config("spark.sql.extensions", "org.apache.iceberg.spark.extensions.IcebergSparkSessionExtensions") \
.config("spark.sql.defaultCatalog", "iceberg_catalog") \
.config("spark.sql.catalog.iceberg_catalog", "org.apache.iceberg.spark.SparkCatalog") \
.config("spark.sql.catalog.iceberg_catalog.warehouse", "<s3 uri>") \
.config("spark.sql.catalog.iceberg_catalog.catalog-impl", "org.apache.iceberg.aws.glue.GlueCatalog") \
.config("spark.sql.catalog.iceberg_catalog.io-impl", "org.apache.iceberg.aws.s3.S3FileIO") \

# Spark SQL
CREATE TABLE iceberg_catalog.analysis_log.user_action_log(
action string not null comment '액션 분류',
abTestType string null comment 'A B 테스트 타입 (default: A)',
metaData struct<
sample_col:int comment '예시',
options:struct<
sample_options:string comment '예시 옵션',
>,
> null comment '액션(이벤트) attributes',
appVersion string not null comment '클라이언트 앱 버전',
eventDatetime timestamp comment '로그 생성 시간',
year integer comment 'partition - year(eventDatetime)',
month integer comment 'partition - month(eventDatetime)',
day integer comment 'partition - day(eventDatetime)'
)
USING iceberg
PARTITIONED BY (
year,
month,
day
)
TBLPROPERTIES(
'write.parquet.compression-codec'= 'snappy'
)
;

Spark에 Iceberg 를 세팅하고 CREATE 문을 실행하면 Glue Data Catalog 에 데이터베이스, 테이블이 생성되게 되며 이 정보를 별칭으로 편리하게 데이터를 가져올 수 있어요. 위 코드 예시에서는 analysis_log가 데이터베이스, user_action_log가 테이블로, 데이터베이스와 테이블을 조합한 analysis_log.user_action_log 라는 별칭으로 S3 path 정보를 정확하게 기입하지 않고 접근할 수 있어요. 단, 당연하게도 AWS Glue Data Catalog 에 접근할 AWS IAM 권한이 있어야합니다. 😅

이제 아래와 같은 것들이 가능해졌어요!

  • 버전 관리: 데이터에 품질에 문제가 발생하였을 때 이전 버전으로 돌아가 원인을 분석할 수 있어요.
  • 데이터 탐색: DataHub 와 연동하여 파일에 저장된 컬럼 및 설명을 빠르게 파악할 수 있어요.
  • 스키마 일관성: 데이터 카탈로그에 스키마를 저장함으로써, 스키마의 일관성을 보장할 수 있어요.

더 나아가서, DuckDB를 활용해서 호스트 자원을 사용해 SQL로 데이터를 추출할 수 있는 환경을 만들고 싶었어요.

DuckDB

DuckDB는 오픈소스 in-process OLAP 데이터베이스로 다음과 같은 특징을 가지고 있어요.

  • Embeded database(Same application): 호스트 프로세스 내에 내장되어 DuckDB를 사용하기 위한 별도의 서버 구성 및 운영이 필요하지 않아요. 이로 인해, 데이터베이스와 소켓 커넥션이 필요하지 않아 데이터를 copy 하고 소켓에 push 하는 과정의 오버헤드가 줄어들어요.
  • Vectorized query engine: SIMD(Single Instruction Multiple Data)로 memory stall 을 감소시켜 CPU 사용률을 향상, cash locality로 빠르게 데이터를 스캔할 수 있어요.
  • parquet 파일에서 push down 지원을 지원해요.
  • columnar engine, storage 사용으로 메모리에 압축된 상태로 유지해요.
  • 빠른 설치: python pip 을 통해 설치 가능해요.

그럼 이 DuckDB로 조금 전 구성한 Iceberg 를 데이터베이스의 테이블처럼 사용하여 SQL로 조회해보겠습니다.

자격 증명

Glue Data Catalog를 사용하기 위해 AWS IAM 자격 증명이 필요해요.

access key 기반의 자격증명이 필요한 경우 다음 코드처럼 세팅하면 되는데요, 저는 코드 보안과 매번 세팅해야하는 번거로움을 줄이고 싶어 custom library를 생성하여 aws credential의 profile 이름을 지정해 사용할 수 있도록 하였습니다.

SET s3_access_key_id = 'my key';
SET s3_secret_access_key = 'my secret key'

# Custom library
duck = DuckDB(profile='my_profile') # 지정한 profile의 access key와 secret access key를 DuckDB 에 세팅하도록 구성

성능 및 비용

DuckDB는 parquet 형식의 파일을 읽을 때 row group 과 columnar 특징을 활용한 push down을 지원해요.

이로 인해, 불필요한 네트워크 비용을 발생시지키 않아요.

또한, out of core 지원으로 메인 메모리보다 큰 데이터일 경우에도 쿼리를 수행할 수 있어요.

예시

그럼 이제 Iceberg 테이블을 DuckDB로 쿼리해볼까요?

DuckDB의 Iceberg extension 을 설치하고 iceberg_scan 함수에 Iceberg가 저장된 경로를 명시해주면 됩니다.

Iceberg 테이블을 DuckDB로 쿼리하는 예시는 다음과 같습니다.

SELECT 
action,
count(1) as action_count
FROM iceberg_scan('s3://my-iceberg/analysis_log.db/user_action_log')
WHERE year=2024 AND month=1 AND day=1 -- partition
AND eventDatetime >= '2024-01-01 15:00:00'
AND eventDatetime < '2024-01-02 16:00:00'
GROUP BY action
;

단, 저희가 사용한 Glue Data Catalog의 경우 version-hint.txt 파일을 glue가 가지고 있어서, 메타데이터 포인터를 가져오지 못하는 이슈가 있었는데요 😱 이 이슈는 version-hint.txt 로 메타데이터 포인터를 얻는 대신 사용할 메타데이터 파일 경로를 정확히 전달하여 해결할 수 있습니다.

저희는 Iceberg metadata 폴더 내에서 가장 최신의 metadata 파일 경로를 가져오도록 custom library에 구성하여 사용하고 있습니다.

속도

DuckDB는 특히 aggregation 연산 시 속도가 빠른 것을 체감할 수 있었는데요,

M3 Max, 14코어, 36GB 환경에서 76,821,179 rows, 36 cols 를 가진 parquet 파일 데이터를 하나의 필드(11 group)로 group by count 집계 시 7초가 소요되었습니다 🙂

이는 push down 및 집계 시 해시테이블 키 충돌을 해결하기 위해 선형 프로빙 방식을 채택하여 cpu의 캐시 로컬리티를 활용하고, Two part ggregation hash table을 최적화하여 hash table resizing 시 오버헤드를 줄였기 때문인데요. 자세한 내용은 DuckDB 공식 docs인 https://duckdb.org/2022/03/07/aggregate-hashtable.html 에서 확인하실 수 있습니다.

아키텍쳐 전후 비교

그럼 어떻게 달라졌는지 다이어그램으로 확인해볼까요?

Before

아키텍쳐 변경 전

After

아키텍쳐 변경 후

개선된 것들

변경된 아키텍쳐로 아래와 같은 것들이 개선되었어요!

ML 배치

기존의 ML 배치는 데이터웨어하우스인 Redshift를 조회하여 데이터를 가져오도록 구성되어있었어요. 이로 인해, Redshift 의존성이 발생하여 배치 실행 시에 무거운 쿼리가 실행되고 있거나 데이터 파이프라인 이슈로 데이터웨어하우스가 업데이트 되지 않았을 경우 ML 배치까지 함께 영향을 받았습니다. 😥

5252 이 이상은 못 간다. 같이 죽자

이를 Iceberg와 DuckDB를 활용하여 SQL로 파일에서 데이터를 추출할 수 있도록 아키텍쳐를 개선하여 Redshift 의존성을 제거했어요.

물귀신은 내가 처리했으니 걱정말라구!

데이터 분석

또 로컬 환경으로 데이터를 가져올 때도 편리해졌어요.

이전에는 Pandas 라이브러리를 통해 모든 데이터를 로컬로 가져온 뒤 필터로 처리해야했기 때문에, 사용할 데이터는 극히 일부인데 읽어야할 파일이 큰 경우 과도한 메모리 사용으로 jupyter에서 세션이 종료되어 로컬에서 분석이 불가했어요 😥

변경된 아키텍쳐에서는 DuckDB의 push down으로 필요한 데이터만 로컬로 가져와서 처리할 수 있게 되었습니다! 😊

물론 raw 데이터를 가져올 때 뿐 아니라 데이터를 분석하는 환경도 개선되었는데요, Pandas나 Python에 대해 친숙하지 않은 경우에도 SQL로 분석할 수 있어서 조금 더 많은 분들이 파일 데이터를 분석할 수 있게 되었어요.

데이터 탐색

이제 json과 같은 반정형 데이터의 스키마와 코멘트를 Glue Data Catalog를 통해 얻을 수 있어요.

메타데이터를 한 곳에서 관리하기 때문에 신뢰도 높은 정보를 제공할 수 있으며, 업데이트도 간편해졌어요.

이를 추후 Datahub와 연동하여 데이터사이언티스트나 분석가 분들이 더욱 쉽게 데이터를 탐구하고 인사이트를 얻을 수 있도록 도와드릴 예정이에요.

데이터 신뢰도

데이터 파이프라인에 문제가 발생할 경우 Iceberg를 사용하여 특정 시점으로 돌아가 backfill하거나 이슈의 원인을 분석할 수 있게 되었어요.

중고나라의 데이터 아키텍쳐를 고민하고 개선했던 경험을 공유드렸는데요, 더 고도화할 부분을 알고 계시거나 궁금한 것이 있으실 경우 언제든지 댓글주세요! 🤗

긴 글 읽어주셔서 감사합니다.

--

--