JOIN 관련 설정

JOIN 관련 설정

ClickHouse의 JOIN 동작 방식을 제어하는 설정들이에요. 어떤 JOIN 알고리즘을 사용할지, ANY strictness의 동작, 기본 strictness, 크기 제한 초과 시 동작, NULL 처리 방식 등을 다룬답니다. 특히 join_algorithm은 콤마로 구분해 여러 알고리즘을 지정할 수 있어요. 이 설정들은 system.settings 테이블에서 확인할 수 있고 소스 코드에서 자동 생성된 값들이에요.

출처: 문서

본문

이 설정들은 system.settings에서 확인할 수 있고, 소스 코드에서 자동 생성된 값들이에요.

join_algorithm

사용할 JOIN 알고리즘을 지정해요. 여러 알고리즘을 지정할 수 있고, 특정 쿼리에 대해 kind/strictness와 테이블 엔진에 따라 사용 가능한 하나가 선택돼요.

해시 기반 알고리즘이 디스크로 spill하는지 여부는 이 선택의 일부가 아니에요. max_bytes_before_external_join / max_bytes_ratio_before_external_join은 모든 알고리즘의 spill 임계값이고(둘 중 하나가 0이 아니면 enable_adaptive_memory_spill_scheduler가 메모리 압박 아래에서 조인을 더 일찍 spill할 수 있음), max_rows_in_join / max_bytes_in_join은 모든 알고리즘의 하드 캡이에요. 단, legacy_join_size_limits_trigger_spilling이 두 캡을 디스크의 spill 트리거로 되돌리지 않는 한 그렇죠. 선택한 값은 조인이 어떻게 spill하는지를 결정해요. grace_hash는 첫 번째 블록부터 오른쪽 테이블을 파티셔닝하고, hashparallel_hash는 메모리에 모은 뒤 임계값을 넘으면 전환해요.

대부분의 알고리즘은 자신이 그 쿼리에 선택된 경우에만 쿼리에 영향을 줘요. 그러나 일부는 단지 나열되는 것만으로도 계획을 바꿔요. 최종적으로 선택되지 않는 낮은 우선순위 fallback이어도 말이죠. 알고리즘이 선택되기 전에 결정이 이루어지기 때문이에요. 두 가지 효과가 있어요:

  • 조인 키 타입 추론이 더 엄격해져요(머지 조인은 다른 타입의 키를 조인할 수 없음. 예: StringNullable(String)). 이는 USING 컬럼의 결과 타입을 바꿀 수 있고, 조인이 Join-엔진 테이블에서 TYPE_MISMATCH로 실패하게 할 수 있어요. full_sorting_mergeparallel_full_sorting_merge에 의해 트리거돼요.
  • 조인의 보존 측에서 ORDER BY ... LIMIT가 프라이머리 키 순서의 읽기 대신 명시적 정렬을 받아요. 조인이 정렬된 읽기를 깨뜨린다고 가정하기 때문이에요(머지 조인은 자신의 사전-조인 정렬을 삽입하고, 부분 머지 조인은 왼쪽 블록을 재정렬하며, 지연 블록을 생성할 수 있는 조인은 정렬된 읽기를 전파하지 않음). 결과는 같지만 계획은 덜 효율적이에요. full_sorting_merge, parallel_full_sorting_merge, partial_merge, prefer_partial_merge, grace_hash, auto, 그리고 0이 아닌 max_bytes_before_external_join / max_bytes_ratio_before_external_join에 의해 트리거돼요.

둘 다 쿼리가 결국 hash나 다른 알고리즘으로 실행되더라도 적용돼요. 이것이 바람직하지 않으면, 영향을 받는 쿼리에서는 위 알고리즘을 join_algorithm에 나열하지 마세요.

가능한 값:

  • grace_hash

    Grace hash 조인이 사용돼요. Grace hash는 메모리 사용을 제한하면서 성능 좋은 복잡한 조인을 제공하는 알고리즘 옵션이에요. grace_hash는 첫 번째 블록부터 외부(external)에요. 오른쪽 테이블이 곧바로 파티셔닝되는 반면, hashparallel_hash는 먼저 메모리에 모은 뒤 spill 임계값을 넘을 때만 파티셔닝해요. 오른쪽이 메모리에 맞지 않을 것을 이미 알고 있고 인-메모리 단계를 건너뛰고 싶을 때 선택하세요. spill 임계값 자체는 모든 해시 알고리즘이 사용하는 것과 동일한 max_bytes_before_external_join / max_bytes_ratio_before_external_join이며, legacy_join_size_limits_trigger_spilling이 켜져 있지 않으면 둘 중 하나는 0이 아니어야 해요. 임계값이 없으면 grace_hash는 목록의 다음 알고리즘으로 건너뛰고, 유일한 알고리즘이면 거부돼요.

    grace 조인의 첫 단계는 오른쪽 테이블을 읽고 키 컬럼의 해시 값에 따라 N개 버킷으로 나눠요(초기 N은 grace_hash_join_initial_buckets). 각 버킷이 독립적으로 처리될 수 있도록 보장하는 방식이에요. 첫 번째 버킷의 행은 인-메모리 해시 테이블에 추가되고 나머지는 디스크에 저장돼요. 해시 테이블이 spill 임계값을 넘어 커지면 버킷 수가 증가하고 각 행에 할당된 버킷도 함께 증가해요. 현재 버킷에 속하지 않는 행은 flush되고 재할당돼요. INNER/LEFT/RIGHT/FULL ALL/ANY JOIN을 지원해요.

  • hash

    Hash join 알고리즘이 사용돼요. 모든 kind와 strictness 조합, 그리고 JOIN ON 섹션에서 OR로 결합된 여러 조인 키를 지원하는 가장 일반적인 구현이에요. hash 알고리즘을 사용하면 JOIN의 오른쪽 부분이 RAM에 올라가요.

  • parallel_hash

    hash 조인의 변형으로, 데이터를 버킷으로 나누고 동시에 여러 해시 테이블을 빌드해 이 과정을 가속화해요. parallel_hash 알고리즘을 사용하면 JOIN의 오른쪽 부분이 RAM에 올라가요.

  • partial_merge

    정렬-머지 알고리즘의 변형으로, 오른쪽 테이블만 완전히 정렬돼요. RIGHT JOINFULL JOINALL strictness에서만 지원돼요(SEMI, ANTI, ANY, ASOF는 지원되지 않음). partial_merge 알고리즘을 사용하면 ClickHouse는 데이터를 정렬해 디스크에 덤프해요. ClickHouse의 partial_merge 알고리즘은 고전적인 구현과 약간 다르답니다. 먼저 ClickHouse는 오른쪽 테이블을 조인 키 블록으로 정렬하고 정렬된 블록에 대한 min-max 인덱스를 만들어요. 그런 다음 왼쪽 테이블의 일부를 join key로 정렬하고 오른쪽 테이블 위에서 조인해요. min-max 인덱스는 불필요한 오른쪽 테이블 블록을 건너뛰는 데도 사용돼요.

  • direct

    direct(중첩 루프라고도 함) 알고리즘은 왼쪽 테이블의 행을 키로 사용해 오른쪽 테이블에서 조회를 수행해요. Dictionary, EmbeddedRocksDB, MergeTree 테이블 같은 특수 스토리지에서 지원돼요. MergeTree 테이블의 경우 이 알고리즘은 조인 키 필터를 스토리지 레이어로 직접 푸시해요. 키가 테이블의 프라이머리 키 인덱스를 조회에 사용할 수 있을 때 더 효율적일 수 있고, 그렇지 않으면 왼쪽 테이블 블록마다 오른쪽 테이블의 전체 스캔을 수행해요. INNERLEFT 조인과 다른 조건이 없는 단일 컬럼 동등 조인 키만 지원해요.

  • auto

    auto로 설정하면 hash 조인이 먼저 시도되고, 메모리 제한이 위반되면 즉석에서 다른 알고리즘으로 전환돼요.

  • full_sorting_merge

    조인 전 조인되는 테이블의 전체 정렬을 하는 정렬-머지 알고리즘이에요.

  • ie_join

    ON 섹션에 조인된 테이블의 표현식 간 부등식 비교(<, <=, >, >=) 두 개가 있는 JOIN을 위한 정렬 기반 IEJoin 알고리즘이에요. ALL INNER/LEFT/RIGHT/FULL JOINSEMI/ANTI LEFT/RIGHT JOIN을 지원해요.

    목록에서의 위치가 우선순위를 설정해요. 기본값처럼 다른 알고리즘 뒤에 나열되면 IEJoin은 그것들이 적용되지 않을 때만 사용돼요(ON 섹션에 동등 조건이 없을 때). 첫 번째에 나열되면 ON 섹션에 부등식 조건 두 개가 있을 때마다 사용돼요. 나머지 조건(동등 조건 포함)은 ALL INNER JOIN에 대해 조인 결과에 대한 필터로 적용되고, 다른 kind는 일치에 영향을 주는 잔여 조건으로 오퍼레이터 안에서 평가돼요. ON 섹션에 두 개 이상의 적격 부등식 조건이 있으면 알고리즘이 사용하는 두 개는 컬럼 min/max 통계의 추정 선택도로 선택돼요(Column statistics의 basic 타입 참조). 추정치를 사용할 수 없으면(통계가 없거나 use_statistics가 비활성화), 구문 순서상 처음 두 개가 사용돼요. 목록에 ie_join이 없으면 부등식 조건만 있는 INNER JOIN은 필터가 있는 CROSS JOIN으로 실행되고, 다른 kind는 지원되지 않아요.

    두 입력 모두 조인 전에 메모리에 누적돼요. max_rows_in_joinmax_bytes_in_join은 양쪽의 누적 입력을 함께 제한하고(오른쪽만이 아니라), 오버플로 시 동작은 join_overflow_mode로 설정돼요. 오퍼레이터가 누적 입력 위에 만드는 정렬 인덱스는 제한에 계산되지 않아요. 조인 오퍼레이터 자체는 단일 스레드로 실행되고, 입력의 사전-조인 정렬만 병렬화돼요.

  • parallel_full_sorting_merge

    full_sorting_merge와 같지만, 해시 호환 동등 조인은 조인 키의 해시로 각 독립적 샤드별 머지 조인으로 샤딩돼(max_threads까지) 병렬로 실행돼요. 단일 머지 조인 대신 말이죠. 이렇게 하면 머지 조인의 낮고 스트리밍식 메모리 사용을 유지하면서 모든 스레드를 사용하며, 결과는 정렬되지 않아요.

    조인 키에 의한 해시 샤딩은 해시가 머지-조인 비교와 일치하는 키 타입의 일반 동등 조인에만, 그리고 양쪽이 이미 정렬되지 않은 경우에만 적용돼요. 다음 경우에는 건너뛰어요:

    • ASOF 조인과 부동소수점 / JSON / Object / Dynamic 키 타입: 그 해시는 머지-조인 비교와 일관되지 않아서 같은 키가 다른 샤드에 들어갈 수 있어요.
    • 이미 정렬된 측(순서대로 읽는 MergeTree 읽기 또는 사전 정렬된 입력): 샤드별 머지로의 순서 보존 흩뿌리기는 파이프라인을 데드락시킬 수 있어요. 대신 순서대로 읽기와 그 read_in_order_use_virtual_row 최적화가 유지돼요.
    • initiator가 분산 계획(make_distributed_plan)을 빌드하는 동안: 흩뿌린 정렬은 원격 실행에 직렬화할 수 없기 때문이에요. 로컬 단일 프래그먼트 계획과 워커별 프래그먼트는 그 설정이 비활성화된 상태로 재최적화되므로 여전히 샤딩될 수 있어요.

    건너뛰는 것은 이 재작성만 비활성화하지, 일반적으로 병렬 처리를 비활성화하는 것은 아니에요. 조인은 단일 full_sorting_merge로 실행되고, 순서대로 읽는 MergeTree 측은 query_plan_join_shard_by_pk_ranges가 활성화되면 프라이머리 키 범위로 소스에서 여전히 샤딩될 수 있어요(그 비교는 조인이 사용하는 것과 같으므로 같은 키가 함께 유지됨).

  • prefer_partial_merge

    ClickHouse는 가능하면 항상 partial_merge 조인을 사용하려 하고, 그렇지 않으면 hash를 사용해요. Deprecated, partial_merge,hash와 같아요.

  • default (deprecated)

    레거시 값이니 더 이상 사용하지 마세요. direct,hash와 같아요. 즉 direct 조인과 hash 조인을 순서대로 시도해요.

join_any_take_last_row

오른쪽 테이블이 키에 대해 둘 이상의 일치 행을 가질 때 ANY strictness의 조인 동작을 변경해요.

이 설정은 Join 엔진 테이블과 해시 기반 조인 알고리즘에 적용돼요. 조인이 병렬로 빌드되면 행의 순서가 비결정적일 수 있어요. 즉 join_any_take_last_row = 1ANY JOIN 쿼리에 대해 비결정적 행을 반환할 수 있어요.

가능한 값:

  • 0 — 오른쪽 테이블에 둘 이상의 일치 행이 있으면 처음 발견된 것만 조인돼요.
  • 1 — 오른쪽 테이블에 둘 이상의 일치 행이 있으면 마지막으로 발견된 것만 조인돼요.

함께 보기:

  • JOIN 절
  • Join 테이블 엔진
  • join_default_strictness

join_default_strictness

JOIN 절의 기본 strictness를 설정해요.

가능한 값:

  • ALL — 오른쪽 테이블에 여러 일치 행이 있으면 ClickHouse가 일치하는 행에서 카테시안 곱을 만들어요. 이것이 표준 SQL의 정상 JOIN 동작이에요.
  • ANY — 오른쪽 테이블에 여러 일치 행이 있으면 처음 발견된 것만 조인돼요. 오른쪽 테이블에 일치 행이 하나만 있으면 ANYALL의 결과는 같아요.
  • ASOF — 불확실한 일치가 있는 시퀀스 조인용.
  • Empty string — 쿼리에 ALL 또는 ANY가 지정되지 않으면 ClickHouse가 예외를 던져요.

join_on_disk_max_files_to_merge

MergeJoin 작업이 디스크에서 실행될 때 병렬 정렬에 허용되는 파일 수를 제한해요. 값이 클수록 더 많은 RAM이 사용되고 더 적은 디스크 I/O가 필요해요.

가능한 값: 2부터 시작하는 양의 정수.

join_output_by_rowlist_perkey_rows_threshold

해시 조인에서 행 목록으로 출력할지 여부를 결정하는 오른쪽 테이블의 키당 평균 행 수의 하한이에요.

join_overflow_mode

조인이 다음 한도 중 하나에 도달했을 때 ClickHouse가 수행하는 동작을 정의해요:

  • max_bytes_in_join
  • max_rows_in_join

모든 해시 기반 join_algorithm 값은 디스크로 spill하는 것을 포함해 이 설정을 존중해요. 한도에 도달하면 spill을 트리거하는 대신 쿼리를 중지해요. 예외는 legacy_join_size_limits_trigger_spilling이에요. 이것이 켜져 있으면 이미 디스크에서 실행 중인 조인 부분이 이 설정에 따라 행동하는 대신 더 spill해요.

ie_join도 양쪽에서 누적하는 입력에 대해 이 설정을 존중해요. partial_merge는 여전히 전략 전환으로 한도를 처리해요. join_algorithm 참조.

가능한 값:

  • THROW — ClickHouse가 예외를 던지고 쿼리를 중지해요.
  • BREAK — ClickHouse가 쿼리를 중지하고 예외를 던지지 않아요.

기본값: THROW.

함께 보기:

  • JOIN 절
  • Join 테이블 엔진

join_use_nulls

JOIN 동작의 타입을 설정해요. 테이블을 병합할 때 빈 셀이 나타날 수 있어요. ClickHouse는 이 설정에 따라 그것들을 다르게 채워요.

가능한 값:

  • 0 — 빈 셀이 해당 필드 타입의 기본값으로 채워져요.
  • 1 — JOIN이 표준 SQL과 같은 방식으로 동작해요. 해당 필드의 타입이 Nullable로 변환되고 빈 셀이 NULL로 채워져요.

더 알아보기 (Learn more)