워크로드 스케줄링(Workload Scheduling)

워크로드 스케줄링(Workload Scheduling)

ClickHouse가 여러 쿼리를 동시에 실행하면 CPU, 메모리, IO 같은 공유 리소스를 사용합니다. 스케줄링 제약과 정책을 적용해 서로 다른 워크로드 간에 리소스가 어떻게 활용되고 공유되는지 조절할 수 있습니다. 이 문서에서는 리소스 생성, 워크로드 계층, CPU/메모리/쿼리 슬롯 스케줄링을 설명할게요.

출처: 문서

본문

ClickHouse가 여러 쿼리를 동시에 실행하면 공유 리소스(CPU, 메모리, IO)를 사용합니다. 스케줄링 제약과 정책을 적용해 서로 다른 워크로드 간에 리소스가 어떻게 활용되고 공유되는지 조절할 수 있습니다. 모든 리소스에 대해 공통의 스케줄링 계층을 구성할 수 있습니다. 계층의 루트는 공유 리소스를 나타내고, 리프는 특정 워크로드로 특정 쿼리와 백그라운드 활동의 리소스 요청과 할당을 보유합니다.

리소스 (Resources)

기본적으로 워크로드 스케줄링은 비활성화되어 있습니다. 활성화하려면 스케줄링에 사용할 리소스와 최소한 하나의 워크로드를 만들어야 합니다. 모든 리소스는 독립적이며 어떤 조합으로든 사용할 수 있습니다. CPU 스케줄링을 활성화하려면 MASTER 또는 WORKER 스레드를 위한 CPU 리소스를 만들어야 합니다(자세한 내용은 CPU 스케줄링 참고):

CREATE RESOURCE cpu (MASTER THREAD, WORKER THREAD)

워크로드를 위한 메모리 예약을 활성화하려면 MEMORY 리소스를 만들어야 합니다(자세한 내용은 메모리 예약 참고):

CREATE RESOURCE memory (MEMORY RESERVATION)

쿼리 슬롯 스케줄링을 활성화하려면 QUERY 리소스를 만들어야 합니다(자세한 내용은 쿼리 슬롯 스케줄링 참고):

CREATE RESOURCE query (QUERY)

특정 디스크에 대한 IO 스케줄링을 활성화하려면 WRITE와 READ 접근을 위한 읽기·쓰기 리소스를 만들어야 합니다:

CREATE RESOURCE resource_name (WRITE DISK disk_name, READ DISK disk_name)
-- 또는
CREATE RESOURCE read_resource_name (READ DISK read_disk_name)
CREATE RESOURCE write_resource_name (WRITE DISK write_disk_name)

리소스는 READ 또는 WRITE를 위한 여러 디스크나 READ와 WRITE 둘 다에 사용할 수 있습니다. 모든 디스크에 리소스를 사용하게 하는 구문이 있습니다:

CREATE RESOURCE all_io (READ ANY DISK, WRITE ANY DISK);

리소스는 공유 모드로 분류됩니다:

  • 시분할(time-shared) 리소스 (CPU, IO, Query slots) - 스케줄링 계층의 리프에 대기열에 들어가는 리소스 요청을 관리합니다. 요청은 계층이 정의하는 정책과 제약에 따라 스케줄링됩니다. 리소스 요청은 쿼리가 해당 리소스에 접근할 때 생성됩니다. 예를 들어 쿼리가 디스크에서 데이터를 읽거나 처리를 위해 CPU에 접근하면, 수행된 각 작업 단위마다 또는 소켓을 통해 보내거나 받은 바이트 수마다 리소스 요청이 생성됩니다.
  • 공간 공유(space-shared) 리소스 (Memory) - 스케줄링 계층의 리프에서 리소스 할당을 관리합니다. 할당은 실행 중(running) 또는 대기 중(pending)일 수 있습니다. 대기 중인 할당은 충분한 공간이 확보되거나 다른 할당이 축출(제거)될 때까지 차단됩니다. 결정은 계층이 정의하는 한도와 정책에 기반합니다. 할당과 쿼리(또는 백그라운드 활동) 사이에는 일대일 대응이 있습니다. 할당은 쿼리가 실행을 시작할 때 생성되고 끝날 때 해제됩니다. 실행 중인 할당은 크기를 동적으로 늘리거나 줄일 수 있습니다.

워크로드 계층 (Workload hierarchy)

ClickHouse는 스케줄링 계층을 정의하는 편리한 SQL 구문을 제공합니다. 모든 리소스는 공통 WORKLOAD 계층에 분산됩니다. 분배 규칙은 특정 리소스에 대해 일부 측면에서 변경될 수 있지만 계층은 동일합니다. 모든 WORKLOAD는 모든 리소스에 대해 필요한 스케줄링 노드를 유지합니다. 자식 워크로드는 어떤 워크로드 안에서든 만들어져 계층을 구축할 수 있습니다. ClickHouse는 워크로드 계층의 특정하거나 미리 정의된 구조를 강제하지 않습니다. 다음은 모든 리소스를 "user"와 "system" 워크로드로 나누고 각각 90%와 10%를 보장하는 계층의 예입니다. 워크로드에 정의된 가중치는 max-min 공정성에 사용되므로 아래로부터의 best-effort 보장만 제공합니다(위로부터의 한도나 쿼터가 아닙니다). 모든 스케줄링은 각 호스트에서 독립적으로 수행되므로 max_* 설정이 정의한 한도는 호스트별입니다. 워크로드 "user"는 자신의 리소스를 "development"와 "production" 워크로드로 나누며, "production"이 "development"보다 3배 더 많은 리소스를 가집니다:

CREATE RESOURCE cpu (MASTER THREAD, WORKER THREAD)
CREATE RESOURCE memory (MEMORY RESERVATION)
CREATE RESOURCE s3_read (READ DISK s3)
CREATE RESOURCE s3_write (WRITE DISK s3)
CREATE WORKLOAD all SETTINGS max_concurrent_threads_ratio_to_cores = 2, max_memory_ratio = 0.8, max_bytes_per_second = '2Gi'
CREATE WORKLOAD user IN all SETTINGS weight = 9
CREATE WORKLOAD system IN all
CREATE WORKLOAD development IN user
CREATE WORKLOAD production IN user SETTINGS weight = 3

자식이 없는 리프 워크로드의 이름은 쿼리 설정 SETTINGS workload = 'name'에 사용할 수 있습니다. 자세한 내용은 Workload 마크업을 참고하세요. 워크로드를 사용자 정의하려면 다음 설정을 사용할 수 있습니다:

  • priority - (시분할 전용) 형제 워크로드는 정적 값에 따라 서비스됩니다(낮은 값이 더 높은 우선순위). 선점(preemption)을 구동합니다.
  • precedence - (공간 공유 전용) 형제 워크로드는 정적 값에 따라 허용됩니다(낮은 값이 더 높은 전위). 축출과 허용을 구동합니다.
  • weight - 같은 정적 priority 또는 precedence를 가진 형제 워크로드는 가중치에 따라 공정하게 리소스를 공유합니다. 선점, 축출, 허용에 영향을 줍니다.
  • max_io_requests - 이 워크로드의 동시 IO 요청 수에 대한 한도.
  • max_bytes_inflight - 이 워크로드의 동시 요청에 대한 총 인플라이트 바이트 한도.
  • max_bytes_per_second - 이 워크로드의 바이트 읽기 또는 쓰기 속도 한도.
  • max_burst_bytes - 워크로드가 스로틀되지 않고 처리할 수 있는 최대 바이트 수(각 리소스에 대해 독립적으로).
  • max_concurrent_threads - 이 워크로드의 쿼리에 대한 스레드 수 한도.
  • max_concurrent_threads_ratio_to_cores - max_concurrent_threads와 같지만 사용 가능한 CPU 코어 수로 정규화됩니다.
  • max_cpus - 이 워크로드의 쿼리를 서비스할 CPU 코어 수 한도.
  • max_cpu_share - max_cpus와 같지만 사용 가능한 CPU 코어 수로 정규화됩니다.
  • max_burst_cpu_seconds - max_cpus 때문에 스로틀되지 않고 워크로드가 소비할 수 있는 최대 CPU 초.
  • max_memory - 이 워크로드에 예약된 총 메모리 한도.

워크로드 설정으로 지정된 모든 한도는 각 리소스에 대해 독립적입니다. 예를 들어 max_bytes_per_second = '10Mi'인 워크로드는 모든 읽기·쓰기 리소스에 대해 독립적으로 10 MB/s 대역폭 한도를 가집니다. 읽기와 쓰기에 대한 공통 한도가 필요하다면 READ와 WRITE 접근에 같은 리소스를 사용하는 것을 고려하세요. 다른 리소스에 대해 다른 워크로드 계층을 지정할 방법은 없습니다. 그러나 특정 리소스에 대해 다른 워크로드 설정 값을 지정하는 방법은 있습니다:

CREATE OR REPLACE WORKLOAD all SETTINGS max_io_requests = 100, max_bytes_per_second = '1Mi' FOR network_read, max_bytes_per_second = '2Mi' FOR network_write

또한 워크로드나 리소스는 다른 워크로드에서 참조되면 drop할 수 없습니다. 워크로드의 정의를 업데이트하려면 CREATE OR REPLACE WORKLOAD 쿼리를 사용하세요.

워크로드 설정은 적절한 스케줄링 노드 집합으로 변환됩니다. 더 낮은 수준의 세부 사항은 스케줄링 노드 유형과 옵션 설명을 참고하세요.

워크로드 마크업 (Workload markup)

쿼리는 workload 설정으로 표시하여 서로 다른 워크로드를 구분할 수 있습니다. workload가 설정되지 않으면 "default" 값이 사용됩니다. 설정 프로필을 사용해 다른 값을 지정할 수 있습니다. 모든 사용자의 쿼리가 workload 설정의 고정 값으로 표시되도록 하려면 설정 제약을 사용해 workload를 상수로 만들 수 있습니다.

쿼리 설정 workload는 리프 워크로드(즉 자식이 없는 워크로드)만 참조할 수 있습니다.

SELECT count() FROM my_table WHERE value = 42 SETTINGS workload = 'production'
SELECT count() FROM my_table WHERE value = 13 SETTINGS workload = 'development'

백그라운드 활동에 workload 설정을 할당하는 것도 가능합니다. 머지와 뮤테이션은 각각 merge_workloadmutation_workload 서버 설정을 사용합니다. 이 값들은 특정 테이블에 대해 merge_workloadmutation_workload 머지 트리 설정으로도 재정의할 수 있습니다.

CPU 스케줄링

워크로드를 위한 CPU 스케줄링을 활성화하려면 CPU 리소스를 만들고 동시 스레드 수에 대한 한도를 설정하세요:

CREATE RESOURCE cpu (MASTER THREAD, WORKER THREAD)
CREATE WORKLOAD all SETTINGS max_concurrent_threads = 100

ClickHouse 서버가 여러 스레드로 많은 동시 쿼리를 실행하고 모든 CPU 슬롯이 사용 중이면 과부하 상태에 도달합니다. 과부하 상태에서 해제된 모든 CPU 슬롯은 스케줄링 정책에 따라 적절한 워크로드로 재스케줄링됩니다. 같은 워크로드를 공유하는 쿼리의 경우 슬롯은 라운드 로빈으로 할당됩니다. 별도의 워크로드에 있는 쿼리의 경우 슬롯은 워크로드에 지정된 가중치, 우선순위, 한도에 따라 할당됩니다. CPU 시간은 스레드가 차단되지 않고 CPU 집약적 작업을 처리할 때 소비됩니다. 스케줄링을 위해 두 종류의 스레드가 구분됩니다:

  • 마스터 스레드(Master thread) — 쿼리나 머지·뮤테이션 같은 백그라운드 활동에 대해 작업을 시작하는 첫 번째 스레드.
  • 워커 스레드(Worker thread) — 마스터가 CPU 집약적 작업을 위해 생성할 수 있는 추가 스레드.

더 나은 응답성을 위해 마스터와 워커 스레드에 별도의 리소스를 사용하는 것이 바람직할 수 있습니다. 높은 max_threads 쿼리 설정 값을 사용하면 많은 워커 스레드가 CPU 리소스를 쉽게 독점할 수 있습니다. 그러면 들어오는 쿼리는 그들의 마스터 스레드가 실행을 시작할 수 있도록 CPU 슬롯을 기다리며 차단되어야 합니다. 이를 피하려면 다음 구성을 사용할 수 있습니다:

CREATE RESOURCE worker_cpu (WORKER THREAD)
CREATE RESOURCE master_cpu (MASTER THREAD)
CREATE WORKLOAD all SETTINGS max_concurrent_threads = 100 FOR worker_cpu, max_concurrent_threads = 1000 FOR master_cpu

이것은 마스터와 워커 스레드에 별도의 한도를 만듭니다. 100개의 워커 CPU 슬롯이 모두 사용 중이어도 사용 가능한 마스터 CPU 슬롯이 있는 한 새 쿼리는 차단되지 않습니다. 쿼리는 하나의 스레드로 실행을 시작합니다. 나중에 워커 CPU 슬롯이 사용 가능해지면 그런 쿼리는 확장되어 워커 스레드를 생성할 수 있습니다. 반면 이런 접근 방식은 총 슬롯 수를 CPU 프로세서 수에 바인딩하지 않으므로 너무 많은 동시 스레드를 실행하면 성능에 영향을 줍니다. 마스터 스레드의 동시성을 제한해도 동시 쿼리 수는 제한되지 않습니다. CPU 슬롯은 쿼리 실행 중간에 해제되어 다른 스레드가 다시 획득할 수 있습니다. 예를 들어 2개의 동시 마스터 스레드 한도로 4개의 동시 쿼리가 모두 병렬로 실행될 수 있습니다. 이 경우 각 쿼리는 CPU 프로세서의 50%를 받습니다. 동시 쿼리 수를 제한하려면 별도의 로직을 사용해야 하며 현재 워크로드에서는 지원되지 않습니다. 워크로드에 대해 별도의 스레드 동시성 한도를 사용할 수 있습니다.

이 구성 예제는 admin과 production을 위한 독립적인 CPU 슬롯 풀을 제공합니다. production 풀은 analytics와 ingestion 사이에서 공유됩니다. 또한 production 풀이 과부하되면 필요 시 해제된 슬롯 10개 중 9개가 분석 쿼리에 재스케줄링됩니다. ingestion 쿼리는 과부하 기간 동안 10개 중 1개만 받습니다. 이는 사용자 대면 쿼리의 지연 시간을 개선할 수 있습니다. Analytics는 60개의 동시 스레드 자체 한도를 가지며 항상 ingestion을 지원하기 위해 최소 40개의 스레드를 남겨둡니다. 과부하가 없으면 ingestion은 100개의 스레드를 모두 사용할 수 있습니다. 쿼리를 CPU 스케줄링에서 제외하려면 쿼리 설정 use_concurrency_control을 0으로 설정하세요. CPU 스케줄링은 아직 머지와 뮤테이션에 지원되지 않습니다. 워크로드에 공정한 할당을 제공하려면 쿼리 실행 중 선점과 다운스케일링을 수행해야 합니다. 선점은 cpu_slot_preemption 서버 설정으로 활성화됩니다. 활성화되면 모든 스레드가 주기적으로(cpu_slot_quantum_ns 서버 설정에 따라) CPU 슬롯을 갱신합니다. CPU가 과부하되면 이러한 갱신이 실행을 차단할 수 있습니다. 실행이 오랜 시간 동안 차단되면(cpu_slot_preemption_timeout_ms 서버 설정 참고) 쿼리가 축소되고 동시 실행 스레드 수가 동적으로 감소합니다. CPU 시간 공정성은 워크로드 사이에는 보장되지만, 같은 워크로드 안의 쿼리 사이에는 일부 극단적 경우 위반될 수 있음을 유의하세요.

슬롯 스케줄링은 쿼리 동시성을 제어하는 방법을 제공하지만, 서버 설정 cpu_slot_preemptiontrue로 설정되지 않으면 공정한 CPU 시간 할당을 보장하지 않습니다. 그렇지 않으면 경쟁하는 워크로드 사이의 CPU 슬롯 할당 수에 따라 공정성이 제공됩니다. 선점 없이는 CPU 슬롯을 무기한 보유할 수 있으므로 동일한 CPU 초 수를 의미하지 않습니다. 스레드는 시작 시 슬롯을 획득하고 작업이 끝나면 해제합니다.

CPU 리소스를 선언하면 concurrent_threads_soft_limit_numconcurrent_threads_soft_limit_ratio_to_cores 설정의 효과가 비활성화됩니다. 대신 워크로드 설정 max_concurrent_threads가 특정 워크로드에 할당된 CPU 수를 제한하는 데 사용됩니다. 이전 동작을 얻으려면 WORKER THREAD 리소스만 만들고, 워크로드 allmax_concurrent_threadsconcurrent_threads_soft_limit_num과 같은 값으로 설정하고, workload = "all" 쿼리 설정을 사용하세요. 이 구성은 "fair_round_robin" 값으로 설정된 concurrent_threads_scheduler 설정에 해당합니다.

스레드 vs CPU

워크로드의 CPU 소비를 제어하는 두 가지 방법이 있습니다:

  • 스레드 수 한도: max_concurrent_threadsmax_concurrent_threads_ratio_to_cores
  • CPU 스로틀링: max_cpus, max_cpu_share, max_burst_cpu_seconds

CPU 스로틀링 설정은 cpu_slot_preemption 서버 설정이 활성화된 경우에만 활성화되고 그렇지 않으면 무시됩니다.

첫 번째는 현재 서버 부하에 따라 쿼리용으로 생성되는 스레드 수를 동적으로 제어할 수 있게 합니다. max_threads 쿼리 설정이 지시하는 것을 효과적으로 낮춥니다. 두 번째는 토큰 버킷 알고리즘으로 워크로드의 CPU 소비를 스로틀링합니다. 스레드 수에는 직접 영향을 주지 않지만 워크로드의 모든 스레드의 총 CPU 소비를 제한합니다. max_cpusmax_burst_cpu_seconds를 사용한 토큰 버킷 스로틀링은 다음을 의미합니다. delta 초의 어떤 간격 동안에도 워크로드의 모든 쿼리에 의한 총 CPU 소비는 max_cpus * delta + max_burst_cpu_seconds CPU 초보다 클 수 없습니다. 장기적으로 평균 소비를 max_cpus로 제한하지만, 단기적으로는 이 한도를 초과할 수 있습니다. 예를 들어 max_burst_cpu_seconds = 60max_cpus=0.001이 주어지면, 스로틀 없이 1개 스레드를 60초 동안 또는 2개 스레드를 30초 동안 또는 60개 스레드를 1초 동안 실행할 수 있습니다. max_burst_cpu_seconds의 기본값은 1초입니다. 더 낮은 값은 많은 동시 스레드가 주어진 허용 max_cpus 코어를 충분히 활용하지 못하게 할 수 있습니다. CPU 슬롯을 보유하는 동안 스레드는 세 가지 주요 상태 중 하나일 수 있습니다:

  • 실행 중 (Running): 실제로 CPU 리소스를 소비합니다. 이 상태에서 보낸 시간은 CPU 스로틀링에 계산됩니다.
  • 준비 (Ready): CPU를 사용할 수 있게 되기를 기다립니다. 이 상태에서 보낸 시간은 CPU 스로틀링에 계산되지 않습니다.
  • 차단 (Blocked): IO 연산이나 기타 차단 syscall(예: 뮤텍스 대기)을 수행합니다. 이 상태에서 보낸 시간은 CPU 스로틀링에 계산되지 않습니다.

CPU 스로틀링과 스레드 수 한도를 결합한 구성의 예를 고려해 보겠습니다. 여기서 우리는 모든 쿼리의 총 스레드 수를 사용 가능한 CPU의 2배로 제한합니다. Admin 워크로드는 사용 가능한 CPU 수와 관계없이 최대 정확히 2개의 스레드로 제한됩니다. Admin은 우선순위 -1(기본 0보다 낮음)을 가지며 필요 시 어떤 CPU 슬롯이든 먼저 받습니다. Admin이 쿼리를 실행하지 않으면 CPU 리소스는 production과 development 워크로드 사이에 나뉩니다. CPU 시간의 보장된 몫은 가중치(4 대 1)에 기반합니다: 최소 80%가 (필요하면) production에, 최소 20%가 (필요하면) development에 갑니다. 가중치가 보장을 형성하는 동안 CPU 스로틀링이 한도를 형성합니다: production은 제한되지 않고 100%를 소비할 수 있는 반면, development는 다른 워크로드의 쿼리가 없어도 적용되는 30% 한도를 가집니다. Production 워크로드는 리프가 아니므로 그 리소스는 가중치(3 대 1)에 따라 analytics와 ingestion으로 나뉩니다. 즉 analytics는 최소 0.8 * 0.75 = 60%의 보장을 가지며, max_cpu_share에 기반해 총 CPU 리소스의 70% 한도를 가집니다. ingestion은 최소 0.8 * 0.25 = 20%의 보장을 남기며 상한이 없습니다.

ClickHouse 서버에서 CPU 활용을 극대화하려면 루트 워크로드 allmax_cpusmax_cpu_share를 사용하지 마세요. 대신 max_concurrent_threads에 더 높은 값을 설정하세요. 예를 들어 8개 CPU 시스템에서 max_concurrent_threads = 16을 설정하세요. 이렇게 하면 8개의 스레드가 CPU 작업을 실행하고 다른 8개 스레드가 IO 연산을 처리할 수 있습니다. 추가 스레드는 CPU 압력을 만들어 스케줄링 규칙이 강제되도록 합니다. 반대로 max_cpus = 8을 설정하면 서버가 8개의 사용 가능한 CPU를 초과할 수 없으므로 CPU 압력이 결코 발생하지 않습니다.

메모리 예약 (Memory reservations)

메모리 예약 스케줄링은 실험적입니다. MEMORY RESERVATION 리소스가 존재할 때만 적용되며, 그 SQL 표면과 동작은 향후 릴리스에서 변경될 수 있습니다. 아직 머지와 뮤테이션에는 지원되지 않으며, 실행 중인 쿼리의 축출은 best-effort입니다: 쿼리의 다음 메모리 동기화 지점에서 즉시가 아니라 적용됩니다.

워크로드를 위한 메모리 예약을 활성화하려면 MEMORY RESERVATION 리소스를 만들고 워크로드 설정으로 예약된 총 메모리에 대한 최소한 하나의 한도를 설정하세요:

CREATE RESOURCE memory (MEMORY RESERVATION)

ClickHouse는 모든 쿼리와 백그라운드 활동의 메모리 할당을 추적합니다. 할당된 바이트 수는 스케줄링 계층을 통해 루트까지 집계됩니다. 모든 쿼리는 자신이 속한 리프 워크로드에 연관된 할당을 가집니다. 쿼리의 reserve_memory 설정이 0보다 크면 할당은 대기 중(pending) 상태로 생성됩니다. 대기 중인 할당은 워크로드 계층에서 요청된 메모리 양을 예약합니다. 사용 가능한 메모리가 충분하지 않으면 충분한 메모리가 확보되거나 다른 할당이 축출(제거)될 때까지 할당은 대기 상태로 유지됩니다. 할당이 허용되면 실행 중(running)이 됩니다. 실행 중인 할당은 쿼리의 메모리 소비에 따라 크기를 동적으로 늘리거나 줄일 수 있습니다. 리프 워크로드의 대기 중인 할당은 FIFO 순서로 허용됩니다. 여러 워크로드에 대기 중인 할당이 있을 때는 precedence와 weight 설정에 따라 허용됩니다. 더 높은 precedence 워크로드가 먼저 서비스됩니다. 같은 precedence의 형제 워크로드는 max-min 공정한 방식으로 가중치에 따라 메모리를 공유합니다. 즉 정규화된 메모리 사용(현재 사용 + 요청 증가분을 가중치로 나눈 값)이 낮은 워크로드가 먼저 서비스됩니다. 축출 중에는 반대 논리가 적용됩니다. 메모리를 확보해야 할 때 더 낮은 precedence와 더 높은 정규화된 메모리 사용을 가진 워크로드가 먼저 축출됩니다. 시분할 리소스는 priority를 사용하고 공간 공유 리소스는 precedence를 사용함을 유의하세요. 이것들은 독립적인 설정이며 다른 값으로 설정될 수 있습니다. 더 높은 priority는 비파괴적 선점(지연 또는 스로틀링)을 의미하고, 더 높은 precedence는 파괴적 축출(오류로 중단)을 의미할 수 있습니다. 워크로드는 CPU 스케줄링에 높은 priority를 가지면서 다른 워크로드를 축출하고 이미 완료한 작업을 잃지 않기 위해 메모리 예약에는 같은 precedence를 가질 수 있습니다. max_memory 한도가 있는 모든 워크로드에 대해:

  • 대기 중인 할당은 같은 워크로드의 실행 중인 할당을 축출할 수 없습니다. (관련자와 피해 워크로드가 일치합니다.)
  • 더 낮은 precedence의 대기 중인 할당은 더 높은 precedence의 워크로드를 결코 제거하지 않습니다.
  • 대기 중인 할당은 같은 precedence의 할당을 제거할 수 없습니다. 같은 precedence의 실행 중인 할당은 정규화된 메모리 사용에 기반해 서로 축출할 수 있다는 점을 유의하세요. 축출이 방지되거나 충분한 메모리를 확보하지 못하면 새 할당은 충분한 메모리가 확보될 때까지 차단됩니다. 이 규칙들은 메모리 압력에 기반해 과도한 쿼리를 대기열에 넣을 수 있게 하고 MEMORY_LIMIT_EXCEEDED 오류를 피하는 편리한 방법을 제공합니다.

워크로드 한도는 max_memory_usage 쿼리 설정 같은 메모리 소비를 제한하는 다른 방법과 독립적입니다. 이들은 함께 사용하여 메모리 소비에 대한 더 나은 제어를 얻을 수 있습니다. 사용자(워크로드가 아닌)를 기반으로 독립적인 메모리 한도를 설정하는 것도 가능합니다. 이것은 덜 유연하며 메모리 예약과 대기 중인 쿼리 대기열 같은 기능을 제공하지 않습니다. 메모리 오버커밋 참고.

워크로드 설정 max_waiting_queries는 워크로드의 대기 중인 할당 수를 제한합니다. 한도에 도달하면 서버는 SERVER_OVERLOADED 오류를 반환합니다. max_waiting_queries는 자식 워크로드에 상속되지 않으며 리프 워크로드에서만 의미가 있음을 유의하세요. 메모리 예약 스케줄링은 아직 머지와 뮤테이션에 지원되지 않습니다. reserve_memory 설정이 0보다 큰 쿼리만 메모리 예약을 기다리는 동안 차단됩니다. 그러나 reserve_memory가 0인 쿼리도 워크로드 메모리 풋프린트에 계산되며, 다른 대기 중이거나 증가하는 할당을 위해 메모리를 확보할 필요가 있으면 축출될 수 있습니다. 적절한 워크로드 마크업이 없는 쿼리는 메모리 예약 스케줄링의 적용을 받지 않으며 스케줄러가 축출할 수 없습니다. 쿼리에 비탄력적(non-elastic) 메모리 예약을 제공하려면 reserve_memorymax_memory_usage 쿼리 설정을 모두 같은 값으로 설정하세요. 이 경우 쿼리는 고정된 메모리 양을 예약하고 할당을 동적으로 늘릴 수 없습니다. 탄력적 메모리 예약은 메모리 압력이 없는 한 제거되지 않고 reserve_memory 위로 max_memory_usage까지 증가할 수 있음을 유의하세요. 그러나 실제 소비가 더 낮아도 reserve_memory 아래로는 줄일 수 없습니다. 구성을 고려해 보겠습니다:

CREATE RESOURCE memory (MEMORY RESERVATION)

이 예제에서 모든 쿼리와 백그라운드 활동이 예약한 총 메모리는 10 GiB를 초과할 수 없습니다. system 워크로드는 최소 1 GiB(10 GiB의 10%)의 보장을 가지며, user 워크로드는 최소 9 GiB(10 GiB의 90%)의 보장을 가집니다. user 워크로드 안에서 production과 staging 워크로드는 같은 precedence 1로 가중치(3 대 1)에 따라 메모리를 공유합니다. Testing 워크로드는 production과 staging보다 낮은 precedence 2를 가집니다. 따라서 testing 워크로드는 production과 staging이 사용하지 않는 메모리만 사용할 수 있습니다. 메모리 압력이 발생하면 testing 워크로드 할당이 먼저 축출됩니다. 그다음 더 많은 메모리를 확보해야 하면, staging 워크로드 할당이 그들의 보장을 초과할 경우 production 워크로드 할당보다 먼저 축출됩니다. production과 staging의 대기 중인 쿼리는 메모리를 확보하기 위해 testing 워크로드의 실행 중인 할당을 축출할 수 있지만, 같은 precedence이므로 서로 축출할 수 없습니다. 메모리 압력이 발생하면 대기열에서 기다리며, 이는 너무 많은 동시 실행 쿼리로 인한 MEMORY_LIMIT_EXCEEDED 오류를 피할 수 있게 합니다. system 워크로드는 production, staging, testing보다 높은 precedence 0(기본)을 가지지만 그것들은 형제 워크로드가 아닙니다. 최소 공통 조상은 워크로드 all이며, 그 두 자식 모두 같은 precedence를 가집니다. 따라서 대기 중인 system 워크로드는 그들 중 어떤 것도 축출할 수 없고, 그 반대도 마찬가지입니다. 이는 시스템 활동이 쉽게 축출되지 않도록 보장합니다.

쿼리 슬롯 스케줄링 (Query slot scheduling)

워크로드를 위한 쿼리 슬롯 스케줄링을 활성화하려면 QUERY 리소스를 만들고 동시 쿼리 수 또는 초당 쿼리 수에 대한 한도를 설정하세요:

CREATE RESOURCE query (QUERY)
CREATE WORKLOAD all SETTINGS max_concurrent_queries = 100

워크로드 설정 max_concurrent_queries는 주어진 워크로드에 대해 동시에 실행될 수 있는 동시 쿼리 수를 제한합니다. 이것은 쿼리 max_concurrent_queries_for_all_users와 서버 max_concurrent_queries 설정의 유사체입니다. 비동기 삽입 쿼리와 KILL 같은 일부 특정 쿼리는 한도에 계산되지 않습니다. 워크로드 설정 max_queries_per_secondmax_burst_queries는 토큰 버킷 스로틀러로 워크로드의 쿼리 수를 제한합니다. 어떤 시간 간격 T 동안에도 max_queries_per_second * T + max_burst_queries보다 많은 새 쿼리가 실행을 시작하지 않도록 보장합니다. 워크로드 설정 max_waiting_queries는 워크로드의 대기 쿼리 수를 제한합니다. 한도에 도달하면 서버는 SERVER_OVERLOADED 오류를 반환합니다. max_waiting_queries는 자식 워크로드에 상속되지 않으며 리프 워크로드에서만 의미가 있습니다.

차단된 쿼리는 무기한 대기하며 모든 제약이 충족될 때까지 SHOW PROCESSLIST에 나타나지 않습니다.

워크로드 및 리소스 저장소

모든 워크로드와 리소스의 정의(CREATE WORKLOADCREATE RESOURCE 쿼리 형태)는 디스크의 workload_path 또는 ZooKeeper의 workload_zookeeper_path에 영구 저장됩니다. 노드 간 일관성을 위해 ZooKeeper 저장소가 권장됩니다. 또는 디스크 저장소와 함께 ON CLUSTER 절을 사용할 수 있습니다.

구성 기반 워크로드 및 리소스

SQL 기반 정의에 더해 워크로드와 리소스를 서버 구성 파일에 미리 정의할 수 있습니다. 이것은 일부 한도가 인프라에 의해 정해지고 다른 한도는 고객이 변경할 수 있는 클라우드 환경에서 유용합니다. 구성 기반 엔티티는 SQL 정의된 것보다 우선하며 SQL 명령으로 수정하거나 삭제할 수 없습니다.

구성 형식

구성은 CREATE WORKLOADCREATE RESOURCE 문과 같은 SQL 구문을 사용합니다. 모든 쿼리는 유효해야 합니다.

사용 권장 사항

클라우드 환경의 전형적인 구성은 다음을 포함할 수 있습니다:

  1. 인프라 한도를 설정하기 위해 루트 워크로드와 네트워크 IO 리소스를 구성에 정의
  2. 이 한도를 강제하기 위해 throw_on_unknown_workload 설정
  3. workload 쿼리 설정의 기본값이 'default'이므로 모든 쿼리에 한도를 자동 적용하기 위해 CREATE WORKLOAD default IN all 생성
  4. 구성된 계층 안에서 사용자가 추가 워크로드를 만들도록 허용

이것은 모든 백그라운드 활동과 쿼리가 인프라 한도를 존중하면서도 사용자별 스케줄링 정책에 대한 유연성을 남겨두도록 보장합니다. 또 다른 사용 사례는 이기종 클러스터의 서로 다른 노드에 대한 서로 다른 구성입니다.

엄격한 리소스 접근

모든 쿼리가 리소스 스케줄링 정책을 따르도록 강제하기 위해 throw_on_unknown_workload 서버 설정이 있습니다. true로 설정되면 모든 쿼리가 유효한 workload 쿼리 설정을 사용해야 하며, 그렇지 않으면 RESOURCE_ACCESS_DENIED 예외가 발생합니다. false로 설정되면 그런 쿼리는 리소스 스케줄러를 사용하지 않습니다. 즉 어떤 RESOURCE에도 무제한 접근을 얻습니다. 쿼리 설정 'use_concurrency_control = 0'은 쿼리가 CPU 스케줄러를 피하고 CPU에 무제한 접근을 얻을 수 있게 합니다. CPU 스케줄링을 강제하려면 'use_concurrency_control'을 읽기 전용 상수 값으로 유지하는 설정 제약을 만드세요.

CREATE WORKLOAD default가 실행되지 않았다면 throw_on_unknown_workloadtrue로 설정하지 마세요. 시작 중에 명시적 workload 설정이 없는 쿼리가 실행되면 서버 시작 문제로 이어질 수 있습니다.

스케줄링 노드 계층

스케줄링 서브시스템의 관점에서 각 리소스는 스케줄링 노드의 계층을 나타냅니다. ClickHouse는 WORKLOAD와 RESOURCE 정의가 주어지면 필요한 모든 스케줄링 노드를 자동으로 만듭니다. 스케줄링 노드는 system.scheduler 테이블로 접근할 수 있는 저수준 구현 세부 사항입니다.

시분할(time-shared) 노드 유형:

  • inflight_limit (제약) - 동시 인플라이트 요청 수가 max_requests를 초과하거나 총 비용이 max_cost를 초과하면 차단합니다; 단일 자식을 가져야 합니다.
  • bandwidth_limit (제약) - 현재 대역폭이 max_speed(0은 무제한)를 초과하거나 버스트가 max_burst를 초과하면 차단합니다; 단일 자식을 가져야 합니다.
  • fair (정책) - max-min 공정성에 따라 자식 노드 중 하나에서 서비스할 다음 요청을 선택합니다; 자식 노드는 weight를 지정할 수 있습니다(기본 1).
  • priority (정책) - 정적 우선순위에 따라 자식 노드 중 하나에서 서비스할 다음 요청을 선택합니다(낮은 값이 더 높은 우선순위); 자식 노드는 priority를 지정해야 합니다(기본 0).
  • fifo (큐) - 리소스 용량을 초과하는 요청을 보유할 수 있는 계층의 리프.

공간 공유(space-shared) 노드 유형:

  • limit - 자식 총 할당이 한도를 절대 초과하지 않게 보장하며, 필요 시 하위 트리에서 축출 절차를 시작합니다; 단일 자식을 가져야 합니다.
  • fair_allocation - max-min 공정성에 따라 축출을 강제합니다; 대기 중인 할당은 실행 중인 것을 절대 축출하지 않습니다; 자식 노드는 weight를 지정할 수 있습니다(기본 1).
  • precedence_allocation - 정적 precedence에 따라 축출을 강제합니다(낮은 값이 더 높은 precedence); 더 높은 precedence의 대기 중인 할당은 더 낮은 precedence 할당을 축출합니다; 자식 노드는 precedence를 지정해야 합니다(기본 0).
  • queue - 실행 중 및 대기 중 할당을 보유할 수 있는 계층의 리프.

백업 및 복원

SQL로 정의된 워크로드와 리소스는 ClickHouse 백업에 포함될 수 있습니다. SQL로 정의된 엔티티만 백업됩니다. 구성 기반 워크로드와 리소스(서버 구성 파일의 <resources_and_workloads>로 정의)는 포함되지 않습니다. 이것들은 별도로 백업해야 하는 서버 구성의 일부입니다. 항상 system.workloadssystem.resources를 같은 명령에서 함께 백업하고 복원하세요. 워크로드는 리소스(SETTINGS ... FOR <resource>로)와 부모 워크로드를 참조할 수 있으며, system.workloads만 백업하는 것은 참조된 리소스 정의를 가져오지 않습니다. 참조된 리소스가 백업에 없고 대상 서버에도 이미 없는 워크로드를 복원하면 실패합니다. 백업에서 워크로드와 리소스를 복원하려면 CREATE WORKLOADCREATE RESOURCE 권한이 필요합니다. 기본적으로 기존 엔티티는 변경되지 않고 보존됩니다(if not exists 의미론); 아래 create_workloads_and_resources 설정은 이미 존재하는 엔티티를 어떻게 처리할지 선택합니다. 서버가 ZooKeeper 저장소(workload_zookeeper_path)를 사용하면 클러스터의 한 복제본만 각 엔티티를 만들고, 다른 복제본은 정상적인 복제 메커니즘을 통해 변경을 가져옵니다. 복제 설정의 경우 두 테이블을 ON CLUSTER로 백업하고 복원할 수 있습니다.

:::note create_workloads_and_resources 복원 설정은 복원 중에 이미 존재하는 워크로드나 리소스를 어떻게 처리할지 선택합니다(이 엔티티들의 복원을 켜거나 끄지 않습니다). 다음을 받아들입니다:

  • if not exists (기본) — 기존 엔티티를 변경하지 않고 유지하며 누락된 것만 만듭니다;
  • create — 복원 중인 엔티티가 이미 존재하면 복원을 실패시킵니다;
  • replace — 백업의 정의로 기존 엔티티를 덮어씁니다(이것은 추가로 DROP WORKLOAD / DROP RESOURCE 권한이 필요합니다). :::

참고 항목

더 알아보기 (Learn more)