ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 빅쿼리로 데이터 인프라 이전하기 (1) 비용 최적화 with Dataform
    etc. 2023. 6. 10. 15:08

    결론부터 말씀드리면,

    사용자 작업이 없는 주말 기준으로 데이터 파이프라인 운영 비용을 30만원대 -> 3만원대로 줄였습니다.


    Phase 0. Trino

    저희는 기존에 AWS EKS를 활용해 스케일링이 가능한 Trino 환경을 사용하고 있었습니다.

    클라이언트 로그 데이터부터 MongoDB, RDS등의 데이터를 DW로 ETL하고, 전사 구성원들이 쿼리를 조회하는 용도로 활용되었죠.

    장점이 있다면 대용량의 데이터를 일별 스냅샷으로 적재해도 상대적으로 부담이 적다는 것과

    누가 언제 어디서 어떻게 쿼리를 조회해도 비용 자체는 안정적으로 관리할 수 있다는 것이었죠.

    그러나, 점차 데이터 사이즈와 쿼리량의 증가로 인해 서버 비용 외에 저장 비용도 누적되고

    급격히 사용자가 몰리는 시점에는 처리 속도도 만족스럽지 못했습니다.

    또한, On-premise로 운영하다 보니 데이터 엔지니어들의 손이 가는 부분이 무척 많았죠.

    Phase 1. 빅쿼리 종량제, 사실 부담돼요

    대안 중에는 Google BigQuery도 있었지만, 선뜻 마음이 가지 않았습니다.

    사용량에 따라 비용이 늘어나는 방식 때문에 비용 컨트롤도 어렵고,

    이로 인해 누구나 자유롭게 데이터에 접근하기도 어려울 것 같다는 생각이 들었습니다.

    실제로 빅쿼리 이전을 계획하면서, 아래와 같은 부분을 고려했습니다.

    1. 운영 리소스를 줄일 수 있는가
    2. ETL 파이프라인 운영 비용이 절감되는가
    3. 데이터 접근성 저해 요소가 없는가
    4. 일반 사용자의 사용량 컨트롤이 가능한가

    이번 포스팅의 목적은 위 네 가지 고민에 대해 제가 찾은 해결책을 공유하고자 합니다.

    Phase 2. 운영 리소스 줄이기

    Trino 환경 운영 당시, 데이터 엔지니어는 2명이었습니다.

    한 명은 인프라, 한 명은 파이프라인 운영 중심으로 업무를 분담했지만

    서버 + 스토리지 + 메타스토어 + 에어플로우 + ETL 코드 + BI 툴 등을 모두 On-premise로 관리하기에는 빠듯했습니다.

    그래서 구글 클라우드로 데이터 환경을 이전하면서

    BigQuery, Dataform, Composer 등의 매니지드 서비스를 통해 조금이나마 운영 부담이 줄어들 것을 기대했습니다.

    실제로 Cloud Build를 활용한 배포 자동화 시스템을 구축하고, ETL 코드 작업을 최소화하여 

    환경 이전 과도기를 제외하고는 보다 수월하게 관리할 수 있음을 체감했습니다.

    Phase 3. ETL 파이프라인 운영 비용 절감하기

    빅쿼리로 이전하면서 가장 신경을 많이 썼던 부분입니다.

    기존 환경보다 단 만원이라도 비용을 절감해야만 이전을 하는 의미가 있었기 때문에,

    데이터 파이프라인의 많은 부분을 새로 설계했습니다.

    1. DW의 대부분을 증분 적재한다.
      • 스냅샷으로 적재하는 이점도 분명 있지만, 실제로 사용자들이 조회하는 것은 대부분 가장 최신 상태의 데이터였고
        스냅샷 적재 시, 조회할 때 주의하지 않으면 전체 내역을 스캔하는 일도 발생할 수 있기 때문에
        이를 방지하기 위해 업데이트 시점 기준으로 증분 적재를 하게 되었습니다.
    2. Dataform을 활용한 증분 코드 작성
      • BigQuery에 포함되어있는 Dataform 서비스를 이용해 SQL + 자바스크립트 기반의 ETL 코드를 작성했습니다.
        아래는 코드 Configuration 예시입니다.

    SQL 쿼리 상단에 들어가는 이 설정이 Dataform 코드의 특징인데요, 하나씩 설명하자면

    1. type : [table, incremental, view] 중 하나를 선택합니다. 
      table은 테이블 전체를 truncate하는 것이고, incremental은 upsert, view는 뷰 테이블을 작성합니다.
    2. schema : 테이블이 적재될 빅쿼리 데이터셋을 지정합니다.
    3. name : 적재할 테이블 이름을 지정합니다.
    4. bigquery : 빅쿼리 관련 설정을 지정합니다.
      1. clusterBy : 빅쿼리 테이블의 클러스터링 필드를 지정합니다.
      2. partitionBy : 테이블 조회 비용에 가장 큰 영향을 끼치는 파티션 필드를 지정합니다.
        파티션을 지정한 후, 파티션 단위로 조회할 경우 해당 파티션에 대한 스캔 비용만 부과되기 때문입니다.
      3. updatePartitionFilter: incremental 기능을 이용할 때 가장 중요한 부분 중 하나입니다.
        upsert 방식을 구현하기 위해, temp 테이블을 작성한 후 기존 테이블에 merge하는데 이 때 파티션 필터가 설정되어 있지 않으면 기존 테이블을 full-scan하게 됩니다.
        실제로 일별 증분으로 처리다고 하면 직전 일자 파티션만 비교해도 되는 경우가 대다수이기 때문에 이 구문을 활용해 비용을 크게 절감할 수 있었습니다.
    5. tags : 해당 코드의 태그를 지정합니다. 태그 기준으로 복수의 코드를 동시 실행시킬 수 있기 때문에 Composer와 연계해서 사용할 때 유용합니다.
    6. uniqueKey : incremental 기능을 이용할 때, 기준이 되는 고유 키 값입니다. 이 값으로 그루핑했을 때 고유한 row가 나오지 않으면 merge 단계에서 에러가 발생합니다.
    7. disabled : 특정 조건이 충족되지 않으면 코드를 실행시키지 않는 기능입니다. 
      현재 저희가 설정해둔 것은 'executionDate'라는 variable 값이 없으면 코드를 실행시키지 않도록 했습니다.
      이렇게 한 까닭은, 증분 적재 시 Composer에서 logical date를 가져와 데이터 조회 일자 변수를 할당하는데 이 값이 없을 때 풀 스캔하는 것을 방지하기 위해서입니다.

    이 외에도 pre_operations, post_operations 등 유용한 코드 블록들을 활용해 쿼리의 복잡도는 낮추고 자동화 효율은 높일 수 있습니다.

    추가로 SQL + airflow를 통한 ETL 스케줄링에서 늘 신경쓰였던 부분이 테이블 간의 의존성 관리였는데요,

    Dataform에서는 ${ref("table path")} 구문을 이용해 종속 테이블이 변경되어도 의존성을 관리하기가 쉽고, 

    Backfill 코드를 따로 관리할 필요 없이 ${when(incremental())} 구문을 이용해 쿼리를 분기할 수도 있습니다.

    처음에 BigQuery를 테스트하는 과정에서 위의 기능들을 제대로 활용하지 못해 추가로 발생한 비용이 서두에서 말씀드린 대로 10배에 달했으니, 꼭 참고하시기 바랍니다.

     

    그럼 이어지는 글에서 데이터 접근성과 사용량 관리에 대해 이야기해 보도록 하겠습니다.

Designed by Tistory.