비용 기반 최적화

비용 기반 최적화 (Cost-based optimizations)

Trino는 여러 비용 기반 최적화를 지원해요. 테이블 통계를 바탕으로 더 나은 조인 순서와 조인 분포를 자동으로 선택해 쿼리 성능을 높여요.

출처: 문서

본문

Trino는 아래에 설명된 여러 비용 기반 최적화를 지원해요.

조인 열거 (Join enumeration)

쿼리에서 조인이 실행되는 순서는 쿼리 성능에 큰 영향을 줄 수 있어요. 조인 순서가 성능에 가장 큰 영향을 주는 측면은 처리되고 네트워크로 전송되는 데이터의 크기예요. 많은 데이터를 만들어내는 조인이 쿼리 실행 초기에 수행되면, 이후 단계들이 필요 이상으로 오래 큰 데이터를 처리해야 해 쿼리 처리에 필요한 시간과 자원이 늘어나요.

비용 기반 조인 열거로 Trino는 커넥터가 제공하는 테이블 통계를 사용해 서로 다른 조인 순서의 비용을 추정하고, 계산된 비용이 가장 낮은 조인 순서를 자동으로 선택해요.

조인 열거 전략은 join_reordering_strategy 세션 프로퍼티가 관리하며, optimizer.join-reordering-strategy 구성 프로퍼티가 기본값을 제공해요. 가능한 값은 다음과 같아요.

  • AUTOMATIC(기본) — 전체 자동 조인 열거를 활성화해요.
  • ELIMINATE_CROSS_JOINS — 불필요한 크로스 조인을 제거해요.
  • NONE — 순수하게 문법적(syntactic)인 조인 순서.

AUTOMATIC 조인 열거를 사용하는데 통계를 사용할 수 없거나 다른 이유로 비용을 계산할 수 없다면, ELIMINATE_CROSS_JOINS 전략이 대신 사용돼요.

조인 분포 선택 (Join distribution selection)

Trino는 해시 기반 조인 알고리즘을 사용해요. 각 조인 연산자에 대해 build 측이라고 하는 하나의 조인 입력에서 해시 테이블을 만들어야 해요. 다른 입력인 probe 측은 그 위에서 반복하며, 각 행마다 해시 테이블을 조회해 일치하는 행을 찾아요.

조인 분포에는 두 가지 타입이 있어요.

  • Partitioned: 쿼리에 참여하는 각 노드가 데이터의 일부만으로 해시 테이블을 만들어요.
  • Broadcast: 쿼리에 참여하는 각 노드가 모든 데이터로 해시 테이블을 만들어요. 데이터가 각 노드에 복제돼요.

각 타입에는 장단점이 있어요. Partitioned 조인은 조인 키의 해시로 두 테이블을 재분배해야 해요. 이 조인은 broadcast 조인보다 훨씬 느릴 수 있지만, 전체적으로 훨씬 더 큰 조인을 허용해요. Broadcast 조인은 build 측이 probe 측보다 훨씬 작으면 더 빨라요. 하지만 broadcast 조인은 필터링 후 조인의 build 측 테이블이 각 노드의 메모리에 들어맞아야 하는 반면, distributed 조인은 모든 노드에 걸친 분산 메모리에만 들어맞으면 돼요.

비용 기반 조인 분포 선택으로 Trino는 partitioned 조인을 쓸지 broadcast 조인을 쓸지 자동으로 선택해요. 비용 기반 조인 열거로 Trino는 어느 쪽이 probe이고 build인지 자동으로 선택해요.

조인 분포 전략은 join_distribution_type 세션 프로퍼티가 관리하며, join-distribution-type 구성 프로퍼티가 기본값을 제공해요. 유효한 값은 다음과 같아요.

  • AUTOMATIC(기본) — 각 조인에 대해 조인 분포 타입이 자동으로 결정돼요.
  • BROADCAST — 모든 조인에 broadcast 조인 분포가 사용돼요.
  • PARTITIONED — 모든 조인에 partitioned 조인 분포가 사용돼요.

복제 테이블 크기 상한 (Capping replicated table size)

조인 분포 타입은 조인 재정렬 전략이 AUTOMATIC이거나 조인 분포 타입이 AUTOMATIC일 때 자동으로 선택돼요. 두 경우 모두 join-max-broadcast-table-size 구성 프로퍼티 또는 join_max_broadcast_table_size 세션 프로퍼티로 복제 테이블의 최대 크기를 상한할 수 있어요. 이렇게 하면 비용 기반 옵티마이저가 조인 테이블의 크기를 잘못 추정해 나쁜 계획이 만들어질 때 클러스터 동시성을 개선하고 나쁜 계획을 방지할 수 있어요.

기본적으로 복제 테이블 크기는 100MB로 상한돼요.

문법적 조인 순서 (Syntactic join order)

비용 기반 최적화를 사용하지 않으면 Trino는 기본적으로 문법적 조인 순서를 사용해요. 이 경우 쿼리를 공식적으로 최적화할 방법은 없지만, Trino가 조인을 구현하는 방식을 활용해 더 나은 성능을 낼 수는 있어요.

Trino는 인메모리 해시 조인을 사용해요. 조인 문장을 처리할 때 Trino는 조인의 가장 오른쪽 테이블을 build 측으로 메모리에 로드한 다음, 그다음 오른쪽 테이블을 probe 측으로 스트리밍해 조인을 실행해요. 쿼리에 조인이 여러 개 있으면 이 첫 조인의 결과가 build 측으로 메모리에 남고, 세 번째로 오른쪽인 테이블이 probe 측으로 사용되며, 추가 조인에 대해 이 과정이 계속돼요. 조인 순서가 더 복잡해지면, 예를 들어 괄호로 조인의 특정 부모를 지정할 때 Trino는 여러 하위 조인을 한 번에 실행할 수 있지만, 그 과정의 각 단계는 같은 논리를 따르고 최종적으로 결합되는 결과에도 마찬가지가 적용돼요.

이런 동작 때문에 SQL 쿼리에서 조인을 가장 큰 테이블부터 가장 작은 테이블 순으로 문법적으로 정렬하는 것이 최적이에요. 메모리 사용을 최소화하기 때문이에요.

예를 들어 작은·중간·큰 테이블이 있고 left join을 사용한다면:

SELECT
  *
FROM
  large_table l
  LEFT JOIN medium_table m ON l.user_id = m.user_id
  LEFT JOIN small_table s ON s.user_id = l.user_id

⚠️ 경고: 이러한 최적화는 Trino의 기능이 아니에요. 조인이 구현되는 방식의 부산물일 뿐이므로, 이 동작은 예고 없이 바뀔 수 있어요.

커넥터 구현 (Connector implementations)

Trino 옵티마이저가 비용 기반 전략을 사용하려면 커넥터 구현이 테이블 통계를 제공해야 해요.

더 알아보기 (Learn more)

비용 기반 최적화로 조인 성능을 개선하는 방법을 배웠어요. 이어서 푸시다운(Pushdown)을 살펴보면 좋아요.