파티셔닝

파티셔닝 (Partitioned Tables)

큰 테이블을 쿼리할 때마다 전체를 스캔하면 비용과 성능 둘 다 아깝죠. BigQuery의 파티셔닝은 테이블을 조각(파티션)으로 나눠서, 필요한 부분만 읽을 수 있게 해주는 기능이에요. 파티션 컬럼에 조건을 걸면 BigQuery가 일치하는 파티션만 스캔하고 나머지는 건너뛰는데, 이 과정을 프루닝(pruning) 이라고 불러요.

출처: Introduction to partitioned tables

파티셔닝이 필요한 순간

파티셔닝은 이런 상황에서 특히 유용해요.

  • 테이블 일부만 스캔해 쿼리 성능을 올리고 싶을 때
  • 표준 테이블 할당량을 넘는 작업을 하려는데, 특정 파티션 컬럼 값으로 범위를 좁힐 수 있을 때
  • 쿼리를 실행하기 전에 비용을 미리 예측하고 싶을 때 — 파티션을 프루닝한 뒤 dry run을 돌리면 처리할 데이터량을 추정할 수 있어요
  • 파티션 단위 관리 기능이 필요할 때 — 예를 들어 파티션 만료 시간 설정, 특정 파티션에만 데이터 쓰기, 전체 테이블 스캔 없이 특정 파티션 삭제 같은 작업이요

반대로 이런 상황에서는 파티셔닝 대신 클러스터링(clustering) 이 더 어울려요.

  • 파티셔닝으로 줄 수 있는 것보다 더 세밀한 정렬이 필요할 때
  • 여러 컬럼에 대한 필터나 집계가 자주 일어날 때
  • 컬럼 값의 카디널리티(고유 값 수)가 클 때
  • 쿼리 실행 전 엄격한 비용 예측이 필요하지 않을 때
  • 파티션 하나가 10GB 미만으로 작아져 파티션 수가 지나치게 많아질 때 — 파티션이 많으면 메타데이터가 늘어나 조회 시간에 영향을 줄 수 있어요
  • 파티션 수가 파티션 테이블 한도를 넘을 때
  • DML로 수 분마다 대부분의 파티션을 자주 수정할 때

클러스터링과 파티셔닝을 함께 쓰면 더 세밀한 정렬도 가능해요. 데이터를 먼저 파티션으로 나누고, 각 파티션 안에서 클러스터링 컬럼으로 다시 정렬하는 방식이죠.

파티셔닝의 종류

정수 범위 파티셔닝

특정 INTEGER 컬럼의 값 범위를 기준으로 테이블을 나눌 수 있어요. 이때 필요한 건 네 가지예요.

  • 파티셔닝 컬럼
  • 범위의 시작 값 (포함)
  • 범위의 끝 값 (제외)
  • 파티션 내 각 범위의 간격

예를 들어 customer_id 컬럼에 시작 0, 끝 100, 간격 10으로 정수 범위 파티션을 만들었다고 해볼게요. 값 09가 한 파티션, 1019가 다음 파티션 이런 식으로 99까지 나뉘어요. 범위 밖 값은 __UNPARTITIONED__라는 파티션에, NULL 값은 __NULL__ 파티션에 들어가요.

시간 단위 컬럼 파티셔닝

테이블 안의 DATE, TIMESTAMP, DATETIME 컬럼을 기준으로 파티션을 나눌 수 있어요. 데이터를 쓸 때 BigQuery가 컬럼 값을 보고 자동으로 올바른 파티션에 넣어줘요.

  • TIMESTAMPDATETIME 컬럼은 시간·일·월·년 단위 파티션 지원
  • DATE 컬럼은 일·월·년 단위 파티션 지원
  • 파티션 경계는 UTC 시간 기준

예를 들어 DATETIME 컬럼으로 월 단위 파티션을 만들었다면, 값 DATETIME("2019-01-01")DATETIME("2019-01-15")는 모두 201901 파티션에 들어가요. 또 두 개의 특수 파티션이 생겨요 — 파티셔닝 컬럼이 NULL인 행을 담는 __NULL__, 그리고 컬럼 값이 1960-01-01 이전이거나 2159-12-31 이후인 행을 담는 __UNPARTITIONED__예요.

수집 시간 파티셔닝

테이블을 만들 때 수집 시간 기준으로 파티셔닝하면, BigQuery가 데이터가 유입된 시간을 기준으로 행을 파티션에 배정해요. 시간·일·월·년 단위를 고를 수 있고, 파티션 경계는 UTC 기준이에요.

더 세밀한 시간 단위를 쓰면 테이블당 최대 파티션 수에 도달할 위험이 있을 때는 더 큰 단위로 바꾸세요. 예를 들어 일 단위 대신 월 단위로 나누면 파티션 수가 줄어들어요. 파티션 컬럼에 클러스터링을 적용해 성능을 더 끌어올릴 수도 있어요.

수집 시간 파티셔닝 테이블에는 _PARTITIONTIME이라는 의사 컬럼(pseudocolumn) 이 있어요. 이 컬럼의 값은 각 행의 수집 시간을 파티션 경계(시간·일 단위 등)로 잘라낸 값이에요. 예를 들어 시간 단위 파티셔닝 테이블에서 17:22에 데이터가 들어오면 _PARTITIONTIME은 17:00:00이 되고 2021050717 파티션에 들어가죠.

시간 간격 때문에 역추적 데이터를 넣거나 표준시를 조정해야 한다면 특정 파티션에 직접 쓸 수도 있어요. 단, DML 문은 1970-01-01 이전이나 2159-12-31 이후 날짜를 참조할 수 없어요. _PARTITIONTIME 대신 _PARTITIONDATE 의사 컬럼을 쓸 수도 있는데, 이 값은 _PARTITIONTIME의 UTC 날짜를 나타내요.

일·시간·월·년 중 고르기

  • 일 단위가 기본값이에요. 데이터가 넓은 날짜 범위에 퍼져 있거나 시간이 지나며 계속 추가될 때 좋아요.
  • 시간 단위는 짧은 날짜 범위(보통 6개월 미만의 타임스탬프)에 걸친 대용량 데이터에 적합해요. 고르면 파티션 수가 파티션 한도 안에 드는지 확인하세요.
  • 월·년 단위는 하루 데이터양은 적지만 날짜 범위가 넓은 테이블에 어울려요. 500일이 넘는 넓은 범위의 행을 자주 갱신·추가하는 워크플로에도 추천돼요. 이 경우 월·년 단위 파티셔닝과 함께 파티셔닝 컬럼 클러스터링을 쓰는 게 최선이에요.

파티션 vs 샤딩

테이블 샤딩(table sharding)[PREFIX]_YYYYMMDD처럼 이름 접두사를 붙여 데이터를 여러 테이블에 나눠 저장하는 방식이에요. 파티셔닝이 샤딩보다 성능이 좋으므로 파티셔닝을 권장해요. 샤딩된 테이블은 테이블마다 스키마와 메타데이터 사본을 유지해야 하고, 쿼리하는 테이블마다 권한도 확인해야 하다 보니 쿼리 오버헤드가 늘어나거든요.

과거에 만든 날짜 샤딩 테이블은 수집 시간 파티셔닝 테이블로 변환할 수 있어요.

파티셔닝 성능 팁

파티션 수를 늘리지 않는 쪽이 좋아요. 자주 쓰는 대표적인 조언은 이러해요.

  • 파티션 수가 한도에 가까워지거나 파티션 안 데이터가 10GB 미만으로 작아지면 파티셔닝 대신 클러스터링을 고려하세요.
  • Cloud Storage의 작은 파일 여러 개를 파일당 하나의 잡으로 자주 로드한다면, 여러 개의 로드 잡을 하나로 합치세요. 쉼표로 구분된 URI 목록(gs://my_path/file_1,gs://my_path/file_2)이나 와일드카드(gs://my_path/*)로 한 번에 로드할 수 있어요.
  • 로드·셀렉트·복사 잡으로 행을 한 줄씩 추가하는 패턴은 피하세요. 여러 잡을 하나로 묶는 게 좋아요. BigQuery는 관계형 데이터베이스처럼 쓰기에 적합하지 않아, 잦은 단일 행 추가는 피하는 게 원칙이에요.
  • 높은 속도로 데이터를 추가해야 한다면 BigQuery Storage Write API를 쓰세요. 고성능 데이터 수집에 권장되는 방법이고, 정확히 한 번(exactly-once) 전달 의미론을 갖췄어요.

테이블 가격과 보안

파티셔닝 테이블의 비용은 파티션에 저장된 데이터량과 그 데이터에 대한 쿼리 기준으로 계산돼요. 파티션에 데이터 로드·복사·내보내기는 무료지만 BigQuery의 할당량·한도의 적용은 받는다는 점 기억하세요.

파티셔닝 테이블의 접근 제어는 표준 테이블과 동일해요.

더 알아보기