데이터 엔지니어링

GCP BigQuery Partitioning 살펴보기

GCP BigQuery Partitioning 살펴보기

들어가며

현대 비즈니스 환경에서 데이터는 가장 중요한 자산 중 하나로 자리 잡았습니다. 기업들은 데이터 분석을 통해 고객의 행동을 예측하고, 시장 트렌드를 파악하며, 비즈니스 전략을 최적화하는 데 집중하고 있습니다. 이러한 데이터 중심의 접근 방식에서 중요한 역할을 하는 것이 바로 빅데이터 분석 플랫폼입니다.

Google BigQuery는 이러한 요구를 충족시키기 위해 설계된 강력한 데이터 웨어하우스 솔루션입니다. BigQuery는 뛰어난 성능과 확장성을 자랑하며, 기업들이 대규모 데이터를 실시간으로 분석할 수 있도록 지원합니다. 하지만 BigQuery를 최적화하여 사용하는 방법을 잘 이해하지 못하면, 데이터 분석의 속도와 비용에서 비효율이 발생할 수 있습니다.

이번 블로그 글에서는 BigQuery의 성능을 극대화할 수 있는 핵심 기능 중 하나인 파티셔닝(Partitioning)에 대해 소개하고자 합니다. 파티셔닝은 데이터 저장 및 쿼리 효율성을 크게 향상시킬 수 있는 방법으로, 이를 잘 활용하면 데이터 분석의 속도와 비용을 획기적으로 개선할 수 있습니다.

 

파티셔닝(Partitioning)이란?

BigQuery 파티셔닝의 기본 개념

파티셔닝은 테이블을 특정 기준에 따라 여러 개의 작은 부분(파티션)으로 나누는 것을 말합니다. 파티션을 사용하면 데이터가 시간, 범주, 번호 등 특정 기준에 따라 분할됩니다. 쿼리를 실행할 때, BigQuery는 전체 테이블을 스캔하지 않고 쿼리에 필요한 파티션만 스캔합니다. 이는 필요한 데이터의 범위를 줄여 쿼리 성능을 크게 향상시킵니다. 예를 들어, 날짜 기반 파티션 테이블에서 특정 날짜 범위의 데이터를 조회하는 경우, 해당 날짜 범위의 파티션만 스캔하게 됩니다. 이는 쿼리의 I/O 비용을 감소시키고 처리 속도를 증가시킵니다. BigQuery는 처리하는 데이터 양에 따라 비용이 부과1되기 때문에, 파티셔닝을 사용하여 필요한 데이터만 스캔한다면 전체 비용을 절감할 수 있습니다.

 

파티션으로 나눈 테이블과 표준 테이블 비교

표준 테이블

예를 들어, 아래와 같은 쿼리를 살펴봅시다.

CREATE OR REPLACE TABLE `TEST_DATASET_US.test_table_not_partitioned` 
AS (
  SELECT
    *
  FROM
    `bigquery-public-data.google_trends.top_terms`
  WHERE
    week BETWEEN "2015-01-01" AND "2024-07-15"
);

위 쿼리는 빅쿼리 퍼블릭 데이터의 구글 트렌드 데이터세트에서 top_terms 테이블을 선택하여 파티션되지 않은 표준 테이블로 만드는 쿼리입니다. 이를 통해 생성된 테이블은 아래와 같습니다.

 

그림 1. 파티션 없는 표준 테이블의 예시 모습
그림 1. 파티션 없는 표준 테이블의 예시 모습

전체 용량은 약 2.91GB를 차지하고 있는 것을 볼 수 있습니다.

그림 2. 파티션 없는 표준 테이블의 스토리지 정보
그림 2. 파티션 없는 표준 테이블의 스토리지 정보

위 테이블에서 week가 2024년 6월 23일에 해당하는 데이터를 조회하고 싶다고 가정해 봅시다. 이 테이블은 파티셔닝이 되어있지 않기 때문에, week="2024-06-23"인 행을 찾으려면 테이블의 모든 부분을 조회해야 합니다.

SELECT
  *
FROM
  `TEST_DATASET_US.test_table_not_partitioned`
WHERE
  week = "2024-06-23"
그림 3. 예상되는 처리량을 미리 확인해 볼 수 있다.
그림 3. 예상되는 처리량을 미리 확인해 볼 수 있다.

 

그림 4. 실제로 쿼리가 처리/청구한 바이트. 해당 바이트를 기준으로 빅쿼리 비용이 청구된다.
그림 4. 실제로 쿼리가 처리/청구한 바이트. 해당 바이트를 기준으로 빅쿼리 비용이 청구된다.

테이블 전체가 차지하는 스토리지와 쿼리가 처리한 바이트를 비교해 보면, 해당 쿼리가 전체 테이블을 조회했다는 것을 알 수 있습니다.

그림 5. 쿼리 결과 테이블. 약 15.7만 개의 행이 반환되었다.
그림 5. 쿼리 결과 테이블. 약 15.7만 개의 행이 반환되었다.

이처럼, 파티셔닝이 되지 않은 테이블에서 쿼리할 때는 테이블 전체를 조회해야 하는 것을 확인해 볼 수 있습니다.

파티션을 나눈 테이블(Partitioned Table)

이번에는 week 열을 기준으로 파티션을 나눈 테이블을 만들어 보겠습니다.

CREATE OR REPLACE TABLE `TEST_DATASET_US.test_table_partitioned`
PARTITION BY
  week 
AS (
  SELECT
    *
  FROM
    `bigquery-public-data.google_trends.top_terms`
  WHERE
    week BETWEEN "2015-01-01" AND "2024-07-15"
);

week열은 데이터타입이 DATE이기 때문에 해당 열을 기준으로 바로 파티션을 나눌 수 있습니다.

만들어진 테이블은 아래와 같습니다.

그림 6. 파티션을 나눈 테이블의 예시 모습
그림 6. 파티션을 나눈 테이블의 예시 모습

 파티션에 대한 메타데이터를 제외하면 테이블이 차지하는 용량은 표준 테이블과 다르지 않습니다.

그림 7. 파티션을 나눈 테이블의 스토리지 정보
그림 7. 파티션을 나눈 테이블의 스토리지 정보

이번에는 파티션을 나눈 테이블에서 마찬가지로 week가 2024년 6월 23일에 해당하는 데이터를 조회해 보겠습니다.

SELECT
  *
FROM
  `TEST_DATASET_US.test_table_partitioned`
WHERE
  week = "2024-06-23"
그림 8. 마찬가지로 예상되는 처리량을 미리 확인해 볼 수 있다.
그림 8. 마찬가지로 예상되는 처리량을 미리 확인해 볼 수 있다.
그림 9. 실제로 쿼리가 처리/청구한 바이트.
그림 10. 쿼리 결과 테이블. 약 15.7만 개의 행이 반환되었다.

테이블을 스캔하기 전에 프루닝이 적용되어, WHERE절에 적용한 필터와 일치하지 않는 파티션은 건너뛰고 일치하는 파티션만 스캔한 것을 확인해 볼 수 있습니다.

그림 11-12. 표준 테이블 / 파티션으로 나눈 테이블의 실행 세부정보 비교
그림 11-12. 표준 테이블 / 파티션으로 나눈 테이블의 실행 세부정보 비교

표준 테이블과 파티션으로 나눈 테이블의 실행 세부정보를 비교해 보면, 표준 테이블에서 쿼리할 때 사용한 슬롯 시간과 읽은 레코드 수가 훨씬 더 높은 것을 확인해 볼 수 있습니다.

Partitioned Table이 필요한 경우

그렇다면 어떤 데이터를 파티션으로 나누는 것이 좋을까요? 모든 테이블을 반드시 파티션으로 나눠야 할까요? 그렇지는 않습니다. 파티션으로 나누는 것이 반드시 항상 이득인 것은 아니기 때문에, 데이터가 어떤 종류의 데이터인지, 해당 데이터셋에 대한 쿼리는 주로 어떤 쿼리가 이뤄지는지 등 다양한 상황에 따라 파티셔닝이 필요한지 따져봐야 합니다.

파티셔닝이 이득인 경우

  1. 테이블 전체가 아닌 일부분에 대한 스캔이 자주 이뤄져야 하는 경우
    • 위 예시에서 보여준 것처럼, 테이블에서 특정한 값을 가지는 행을 조회할 일이 많은 경우에 파티셔닝이 이득이 될 수 있습니다. 예를 들어, 로그 데이터를 날짜별로 조회하거나 특정 ID 범위의 데이터를 자주 조회하는 경우가 있습니다.
  2. 시간 기반 데이터가 많고, 특정 기간의 데이터를 자주 조회하는 경우
    • 웹 로그, 트랜잭션 기록, IoT 센서 데이터와 같이 시간이 중요한 데이터는 시간 기반으로 파티셔닝하면 효율적입니다. 예를 들어, 특정 날짜나 월별 데이터를 조회할 때, 해당 기간의 파티션만 스캔하면 되므로 성능이 크게 향상됩니다.
  3. 데이터가 주기적으로 업데이트되거나, 데이터 수명이 일정한 경우
    • 월별 판매 데이터나 분기별 재무 데이터처럼 주기적으로 업데이트되는 데이터는 해당 주기 기준으로 파티셔닝하면 관리가 용이합니다. 또한, 일정 기간이 지나면 삭제되는 데이터의 경우, 파티셔닝을 통해 오래된 파티션을 손쉽게 삭제할 수 있습니다.
  4. 정수 값 범위에 따라 데이터를 조회하는 경우가 많은 경우
    • 고객 ID, 제품 ID 등 정수 값 범위에 따라 데이터를 자주 조회하는 경우, 정수 범위 파티셔닝을 통해 성능을 최적화할 수 있습니다. 예를 들어, 특정 고객 ID 범위에 해당하는 데이터를 조회할 때 유용합니다.
  5. 데이터의 크기가 매우 큰 경우
    • 데이터셋이 매우 커서 전체 데이터를 스캔하는 비용이 매우 높은 경우, 파티셔닝을 통해 쿼리 성능을 최적화하고 비용을 절감할 수 있습니다. 대규모 로그 데이터나 소셜 미디어 데이터, GA4 데이터를 예로 들 수 있습니다.

파티셔닝이 필요하지 않은 경우

  1. 데이터가 작고, 전체 데이터를 자주 조회하는 경우
    • 데이터셋이 비교적 작거나, 대부분의 쿼리가 전체 테이블을 대상으로 수행되는 경우에는 파티셔닝의 이점이 크지 않습니다. 이 경우, 파티셔닝으로 인한 추가적인 관리 복잡성만 증가할 수 있습니다.
  2. 데이터의 변경이 빈번하고, 파티션 키가 자주 바뀌는 경우
    • 데이터가 빈번하게 변경되고, 파티션 키가 자주 바뀌는 경우에는 파티셔닝의 관리가 복잡해질 수 있습니다. 이 경우, 파티셔닝의 이점이 감소할 수 있습니다.
  3. 2개 이상의 여러 열에 대한 필터링/집계가 필요한 경우
    • 파티셔닝은 1개 열에 대해서만 가능하기 때문에, 여러 열에 대한 쿼리를 수행해야 하는 경우에는 파티셔닝 대신 클러스터링 기능을 사용하는 것이 좋습니다.
  4. 파티셔닝이 수행되는 열의 카디널리티가 너무 큰 경우
    • 구글 공식 문서에 따르면 파티션으로 나눈 테이블에서 가능한 파티션의 개수는 최대 1만 개 까지이며, 단일 작업에서는 최대 4천 개의 파티션에서 작업할 수 있습니다. 이 수보다 파티션이 수행될 열의 카디널리티가 클 경우, 해당 테이블에는 파티셔닝이 적합하지 않습니다.

파티션 나누기의 유형

위 예시에서 week열 대신 term열에 대해 파티션을 나눈 테이블을 만들 수 있을까요? 이전 쿼리에서 PARTITION BY 뒤에 week 대신 term열을 넣으면 다음과 같은 에러를 볼 수 있습니다.

CREATE OR REPLACE TABLE `TEST_DATASET_US.test_table_partitioned`
PARTITION BY
  term
AS (
  SELECT
    *
  FROM
    `bigquery-public-data.google_trends.top_terms`
);
그림 13. term 열에 대해 파티션 나누기를 시도했을 경우
그림 13. term 열에 대해 파티션 나누기를 시도했을 경우

위에서 확인 가능한 것처럼, 모든 데이터 타입에 대해 파티션 나누기가 가능한 것은 아닙니다. 어떤 열에 대해 파티션 나누기가 가능할까요?

1. 시간 단위 열로 파티션 나누기

가장 일반적으로 사용되는 파티셔닝 유형은 시간 단위 열을 기준으로 하는 것입니다. 이는 데이터가 시간에 따라 축적되며, 시간 범위에 따라 쿼리하는 경우에 매우 유용합니다.

해당되는 데이터 타입으로 DATETIMESTAMP 또는 DATETIME이 존재하며, 해당 데이터 타입별로 _TRUNC surfix 함수를 사용하여 시간, 일일, 주간, 월간, 연간 등 다양한 범위로 파티셔닝할 수 있습니다.

다음은 TIMESTAMP 데이터타입 열에 대해 주간 기준 파티션을 나눈 테이블을 생성하는 쿼리 예시입니다.

CREATE TABLE
  mydataset.newtable (transaction_id INT64, transaction_ts TIMESTAMP)
PARTITION BY
  TIMESTAMP_TRUNC(transaction_ts, WEEK)
  OPTIONS (
    partition_expiration_days = 3,
    require_partition_filter = TRUE
  );

2. 정수 단위 열로 파티션 나누기

정수 단위 열을 기준으로 파티셔닝하는 것은 특정 정수 값 범위로 데이터를 분할하고, 해당 범위를 기반으로 쿼리할 때 유용합니다. 이러한 파티셔닝은 주로 고객 ID, 제품 ID, 주문 번호 등과 같이 정수 값으로 정의된 데이터를 더 효율적으로 관리하고 쿼리 성능을 최적화할 수 있습니다.

해당되는 데이터 타입으로 INTEGER타입이 존재하며, 정수 단위 열로 파티셔닝할 때는 RANGE_BUCKET 함수를 사용하여 특정 정수 값 범위로 데이터를 분할한 후 정수 범위별로 파티셔닝 할 수 있습니다.

다음은 고객 ID를 기준으로 1000 단위로 파티셔닝된 테이블을 생성하는 예시입니다.

CREATE TABLE 
  mydataset.customer_orders (
    customer_id INT64,
    order_id INT64,
    order_date DATE,
    total_amount FLOAT64
  )
PARTITION BY
  RANGE_BUCKET(customer_id, GENERATE_ARRAY(0, 10000, 1000))
  OPTIONS (
    require_partition_filter = TRUE
  );

3. 수집 시간 기준으로 파티션 나누기

수집 시간 기준으로 파티셔닝하는 것은 데이터가 시스템에 수집된 시간에 따라 데이터를 분할하는 방법입니다. 이는 주로 실시간 데이터 스트리밍 또는 로그 데이터와 같이 지속적으로 수집되는 데이터에 유용합니다. 이러한 데이터는 특정 수집 시간 범위에 따라 쿼리할 필요가 있는 경우가 많기 때문에, 수집 시간 기준 파티셔닝을 통해 쿼리 성능을 최적화하고 비용을 절감할 수 있습니다.

이 경우에는 파티션의 기준이 되는 열로 테이블 내의 열을 선택하는 것이 아니라, 내부적으로 존재하는 열인 _PARTITIONTIME / _PARTITIONDATE 열이 파티션의 대상이 되며, 시간 단위 열에 대해 파티션 나누기를 할 때와 마찬가지로 원하는 시간 범위에 대해 파티셔닝할 수 있습니다.

다음은 수집 시간 기준으로 월간 파티션을 나눈 테이블을 만드는 예시입니다.

CREATE TABLE
  mydataset.newtable (transaction_id INT64)
PARTITION BY
  DATE_TRUNC(_PARTITIONTIME, MONTH)
  OPTIONS (
    partition_expiration_days = 3,
    require_partition_filter = TRUE
  );

결론

파티셔닝은 BigQuery에서 데이터 분석의 효율성을 극대화하는 중요한 기능입니다. 적절한 파티셔닝을 통해 쿼리 성능을 향상시키고 비용을 절감할 수 있습니다. 데이터의 특성과 쿼리 패턴에 맞는 파티셔닝을 선택하면, 대규모 데이터셋에서도 빠르고 효율적인 분석이 가능합니다.

다음 글에서는 또 다른 성능 최적화 기법인 클러스터링(Clustering)에 대해 알아보겠습니다. 클러스터링은 데이터를 특정 열을 기준으로 물리적으로 정렬하여 쿼리 성능을 더욱 향상시키는 방법입니다. 클러스터링의 개념과 실전 적용 방법을 통해, 여러분의 데이터 분석 작업이 어떻게 한 단계 더 발전할 수 있는지 살펴보겠습니다.

  1. 주문형 가격 책정 모델 기준 ↩︎

Share this post

About the author

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다