리소스
- 시간 공유 리소스 (CPU, IO, 쿼리 슬롯) - 스케줄링 계층 구조의 리프 노드에서 큐에 들어가는 리소스 요청을 관리합니다. 요청은 계층 구조에 정의된 정책과 제약 조건에 따라 스케줄링됩니다. 쿼리가 해당 리소스를 사용할 때 리소스 요청이 생성됩니다. 예를 들어, 쿼리가 디스크에서 데이터를 읽거나 처리 작업을 위해 CPU를 사용할 때, 수행된 작업의 각 quantum 또는 소켓을 통해 송수신된 바이트 수에 따라 리소스 요청이 생성됩니다.
- 공간 공유 리소스 (메모리) - 스케줄링 계층 구조의 리프 노드에서 리소스 할당을 관리합니다. 할당은 실행 중이거나 대기 중일 수 있습니다. 대기 중인 할당은 충분한 공간이 확보되거나 다른 할당이 축출(강제 종료)될 때까지 차단됩니다. 판단은 계층 구조에 정의된 한도와 정책에 따라 이루어집니다. 할당과 쿼리(또는 백그라운드 활동) 사이에는 일대일 대응 관계가 있습니다. 할당은 쿼리 실행이 시작될 때 생성되고, 실행이 끝나면 해제됩니다. 실행 중인 할당은 크기가 동적으로 늘어나거나 줄어들 수 있습니다.
워크로드 계층 구조
max_* 설정으로 정의된 한도는 host별로 적용된다는 점에 유의하십시오. “user” 워크로드는 자신의 리소스를 다시 “development” 워크로드와 “production” 워크로드로 나누며, “production”은 “development”보다 3배 더 많은 리소스를 가집니다:
SETTINGS workload = 'name'에서 사용할 수 있습니다. 자세한 내용은 워크로드 마크업을 참조하십시오.
워크로드를 사용자 지정하려면 다음 설정을 사용할 수 있습니다.
priority- (time-shared 전용) 형제 워크로드는 정적 값에 따라 처리됩니다(값이 낮을수록 우선순위가 높습니다). 선점을 제어합니다.precedence- (space-shared 전용) 형제 워크로드는 정적 값에 따라 허용됩니다(값이 낮을수록 precedence가 높습니다). 축출 및 승인을 제어합니다.weight- 동일한 정적 우선순위 또는 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 쿼리를 사용하십시오.
워크로드 설정은 적절한 스케줄링 노드 집합으로 변환됩니다. 더 하위 수준의 세부 사항은 스케줄링 노드 타입과 옵션 설명을 참조하십시오.
워크로드 마크업
workload를 지정할 수 있습니다. workload가 설정되지 않으면 “default” 값이 사용됩니다. 설정 프로필을 사용하면 다른 값으로 지정할 수도 있습니다. 특정 사용자의 모든 쿼리에 workload 설정의 고정값이 적용되게 하려면 설정 제약 조건을 사용해 workload를 상수로 만들 수 있습니다.
workload 설정을 할당할 수 있습니다. 머지와 뮤테이션은 각각 merge_workload 및 mutation_workload 서버 설정을 사용합니다. 이 값은 특정 테이블에 대해 merge_workload 및 mutation_workload MergeTree 설정으로 재정의할 수도 있습니다.
CPU 스케줄링
- Master thread — 쿼리 또는 머지나 mutation과 같은 백그라운드 작업에서 가장 먼저 작업을 시작하는 첫 번째 스레드입니다.
- Worker thread — CPU 집약적인 작업을 수행하기 위해 master가 추가로 생성할 수 있는 스레드입니다.
max_threads 쿼리 설정값을 크게 사용하면 많은 수의 worker 스레드가 CPU 리소스를 쉽게 독점할 수 있습니다. 그러면 새로 들어오는 쿼리는 블록되어 master 스레드가 실행을 시작할 CPU 슬롯을 기다려야 합니다. 이를 방지하려면 다음과 같은 구성을 사용할 수 있습니다:
cpu_slot_preemption 서버 설정으로 활성화됩니다. 이 설정을 활성화하면 모든 스레드는 주기적으로 CPU 슬롯을 갱신합니다(cpu_slot_quantum_ns 서버 설정에 따름). 이러한 갱신은 CPU가 과부하 상태일 때 실행을 차단할 수 있습니다. 실행이 장시간 차단되면(cpu_slot_preemption_timeout_ms 서버 설정 참조) 쿼리가 스케일 다운되며, 동시에 실행되는 스레드 수가 동적으로 감소합니다. 워크로드 간에는 CPU 시간의 공정성이 보장되지만, 동일한 워크로드 내부의 쿼리 간에는 일부 예외적인 경우 이 공정성이 지켜지지 않을 수 있습니다.
CPU 리소스를 선언하면
concurrent_threads_soft_limit_num 및 concurrent_threads_soft_limit_ratio_to_cores 설정이 더 이상 적용되지 않습니다. 대신 특정 워크로드에 할당되는 CPU 수를 제한하는 데 워크로드 설정 max_concurrent_threads를 사용합니다. 이전 동작과 동일하게 하려면 WORKER THREAD 리소스만 생성하고, 워크로드 all의 max_concurrent_threads를 concurrent_threads_soft_limit_num과 같은 값으로 설정한 뒤, 쿼리 설정 workload = "all"을 사용하십시오. 이 구성은 concurrent_threads_scheduler 설정 값을 “fair_round_robin”으로 지정한 것과 같습니다.스레드와 CPU
- 스레드 수 제한:
max_concurrent_threads및max_concurrent_threads_ratio_to_cores - CPU 스로틀링:
max_cpus,max_cpu_share,max_burst_cpu_seconds
max_threads 쿼리 설정이 지정하는 값을 낮추는 효과가 있습니다. 두 번째 방법은 토큰 버킷 알고리즘을 사용해 워크로드의 CPU 활용을 스로틀링합니다. 이 방법은 스레드 수에 직접 영향을 주지는 않지만, 워크로드에 속한 모든 스레드의 총 CPU 활용을 스로틀링합니다.
max_cpus 및 max_burst_cpu_seconds를 사용하는 토큰 버킷 스로틀링은 다음을 의미합니다. delta초의 임의의 인터벌 동안 워크로드 내 모든 쿼리의 총 CPU 사용량은 max_cpus * delta + max_burst_cpu_seconds CPU 초를 초과할 수 없습니다. 장기적으로는 평균 활용을 max_cpus로 제한하지만, 단기적으로는 이 제한을 초과할 수 있습니다. 예를 들어 max_burst_cpu_seconds = 60 및 max_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 스로틀링에 반영되지 않습니다.
max_cpu_share 기준으로 전체 CPU 리소스의 70%까지로 제한됩니다. 반면 수집은 최소 0.8 * 0.25 = 20%를 보장받지만 상한은 없습니다.
ClickHouse 서버에서 CPU 활용률을 최대화하려면 루트 워크로드
all에 max_cpus와 max_cpu_share를 사용하지 마십시오. 대신 max_concurrent_threads를 더 큰 값으로 설정하십시오. 예를 들어 CPU가 8개인 시스템에서는 max_concurrent_threads = 16으로 설정하십시오. 이렇게 하면 8개의 스레드는 CPU 작업을 실행하고, 나머지 8개의 스레드는 I/O 작업을 처리할 수 있습니다. 추가 스레드는 CPU 부하를 발생시켜 스케줄링 규칙이 적용되도록 합니다. 반대로 max_cpus = 8로 설정하면 서버가 사용 가능한 8개의 CPU를 초과할 수 없으므로 CPU 부하가 발생하지 않습니다.메모리 예약
메모리 예약 스케줄링은 실험적 기능입니다.
MEMORY RESERVATION 리소스가 있을 때만 적용되며, SQL 인터페이스와 동작은 향후 릴리스에서 변경될 수 있습니다. 아직 머지 및 뮤테이션은 지원되지 않으며, 실행 중인 쿼리의 축출은 best-effort 방식으로만 처리됩니다. 즉시 적용되지 않고, 해당 쿼리의 다음 메모리 동기화 지점에서 적용됩니다.MEMORY RESERVATION 리소스를 생성하고, workload setting을 사용해 예약되는 총 메모리에 대해 하나 이상의 제한을 설정하십시오:
reserve_memory 설정이 0보다 크면 해당 할당은 pending 상태로 생성됩니다. Pending 할당은 워크로드 계층 구조에서 요청된 메모리 양을 예약합니다. 사용 가능한 메모리가 충분하지 않으면, 충분한 메모리가 해제되거나 다른 할당이 축출(강제 종료)될 때까지 해당 할당은 pending 상태로 유지됩니다. 할당이 승인되면 running 상태가 됩니다. Running 할당은 쿼리의 메모리 사용량에 따라 크기가 동적으로 증가하거나 감소할 수 있습니다. 할당의 수명 주기는 다음 상태 다이어그램으로 나타낼 수 있습니다.
리프 workload의 대기 중인 allocation은 FIFO 순서에 따라 승인됩니다. 여러 workload에 대기 중인 allocation이 있으면 precedence 및 weight 설정에 따라 승인됩니다. precedence가 더 높은 workload가 먼저 처리됩니다. 동일한 precedence를 가진 형제 workload는 max-min 공정 방식으로 weight에 따라 메모리를 공유하며, 이는 정규화된 memory usage(현재 사용량에 요청된 증가분을 더한 값을 weight로 나눈 값)가 더 낮은 workload가 먼저 처리된다는 뜻입니다. 축출 중에는 반대 로직이 적용됩니다. 메모리를 확보해야 할 때는 precedence가 더 낮고 정규화된 memory usage가 더 높은 workload가 먼저 제거됩니다.
시간 공유 리소스는 priority를 사용하고 공간 공유 리소스는 precedence를 사용한다는 점에 유의하십시오. 두 설정은 서로 독립적이므로 서로 다른 값으로 설정할 수 있습니다. 더 높은 priority는 비파괴적 선점(지연 또는 스로틀링)을 의미하는 반면, 더 높은 precedence는 파괴적 축출(오류와 함께 중지)을 의미할 수 있습니다. 예를 들어 workload는 CPU scheduling에는 높은 priority를 사용하되, 메모리 예약에는 동일한 precedence를 사용하여 다른 workload를 축출하거나 이미 수행된 작업이 손실되는 상황을 피할 수 있습니다.
max_memory 제한이 있는 모든 workload는 해당 서브트리에서 할당된 전체 메모리가 이 제한을 초과하지 않도록 보장합니다. 대기 중이거나 증가하는 allocation으로 인해 제한을 초과하게 되면 메모리를 확보하기 위해 축출 절차가 시작됩니다. 축출 절차에서는 축출할 victim을 선택합니다. killer와 victim의 최소 공통 조상 workload는 다음과 같은 상황에서 축출을 막습니다.
- 대기 중인 allocation은 동일한 workload에서 실행 중인 allocation을 축출할 수 없습니다. (Killer와 victim workload가 같습니다).
- 더 낮은 precedence의 대기 중인 allocation은 더 높은 precedence의 workload를 절대 종료하지 않습니다.
- 대기 중인 allocation은 동일한 precedence의 allocation을 종료할 수 없습니다. 동일한 precedence의 실행 중인 allocation은 정규화된 memory usage를 기준으로 서로를 축출할 수 있다는 점에 유의하십시오. 축출이 방지되거나 충분한 메모리를 확보하지 못하면 새 allocation은 충분한 메모리가 확보될 때까지 차단됩니다. 이러한 규칙을 통해 메모리 압박에 따라 과도한 쿼리를 큐에 대기시킬 수 있으며, MEMORY_LIMIT_EXCEEDED 오류를 피하는 편리한 방법을 제공합니다.
Workload 제한은 max_memory_usage 쿼리 설정과 같은 다른 메모리 활용 제한 방식과는 독립적입니다. 메모리 활용을 더 세밀하게 제어하기 위해 함께 사용할 수 있습니다. users 기준으로(workload 기준이 아님) 독립적인 메모리 제한을 설정할 수도 있습니다. 이 방식은 유연성이 떨어지며 메모리 예약이나 대기 중인 쿼리의 큐잉 같은 기능은 제공하지 않습니다. Memory overcommit을 참조하십시오.
max_waiting_queries는 해당 workload의 대기 중인 allocation 수를 제한합니다. 제한에 도달하면 server는 SERVER_OVERLOADED 오류를 반환합니다. max_waiting_queries는 하위 workload에 상속되지 않으며 리프 workload에만 의미가 있다는 점에 유의하십시오.
Memory reservation scheduling은 아직 merges 및 뮤테이션에서는 지원되지 않습니다.
reserve_memory 설정이 0보다 큰 쿼리만 메모리 예약을 기다리는 동안 대기 상태가 됩니다. 하지만 reserve_memory가 0인 쿼리도 해당 workload의 메모리 사용량에 포함되며, 다른 대기 중이거나 증가하는 allocations를 위해 메모리를 확보해야 하는 경우 필요에 따라 축출될 수 있습니다. 적절한 workload 마크업이 없는 쿼리는 메모리 예약 scheduling의 대상이 아니며 scheduler에 의해 축출될 수 없습니다.
쿼리에 비탄력적 메모리 예약을 제공하려면 reserve_memory와 max_memory_usage 쿼리 설정을 동일한 값으로 지정하십시오. 이 경우 쿼리는 고정된 양의 메모리를 예약하며, 할당량을 동적으로 늘릴 수 없습니다. 탄력적 메모리 예약은 메모리 압박이 없는 한 reserve_memory를 초과해 max_memory_usage까지 증가할 수 있으며, 이 때문에 종료되지 않습니다. 하지만 실제 메모리 사용량이 더 적더라도 reserve_memory 아래로 줄일 수는 없습니다.
구성 예시를 살펴보겠습니다:
쿼리 슬롯 스케줄링
max_concurrent_queries는 지정된 워크로드에서 동시에 실행할 수 있는 동시 쿼리 수를 제한합니다. 이는 쿼리 max_concurrent_queries_for_all_users 및 서버 max_concurrent_queries 설정에 해당합니다. async insert 쿼리와 KILL 같은 일부 특정 쿼리는 이 제한에 포함되지 않습니다.
워크로드 설정 max_queries_per_second와 max_burst_queries는 토큰 버킷 throttler를 사용해 해당 워크로드의 쿼리 수를 제한합니다. 이 설정은 임의의 시간 인터벌 T 동안 새로 실행을 시작하는 쿼리 수가 max_queries_per_second * T + max_burst_queries를 넘지 않도록 보장합니다.
워크로드 설정 max_waiting_queries는 해당 워크로드의 대기 중인 쿼리 수를 제한합니다. 이 한도에 도달하면 서버는 오류 SERVER_OVERLOADED를 반환합니다. max_waiting_queries는 하위 워크로드에 상속되지 않으며 리프 워크로드에서만 의미가 있습니다.
차단된 쿼리는 모든 제약 조건이 충족될 때까지 무기한 대기하며, 그전까지는
SHOW PROCESSLIST에 표시되지 않습니다.워크로드 및 리소스 저장
CREATE WORKLOAD 및 CREATE RESOURCE 쿼리로 정의된 모든 워크로드와 리소스는 workload_path의 디스크 또는 workload_zookeeper_path의 ZooKeeper에 영구적으로 저장됩니다. 노드 간 일관성을 확보하려면 ZooKeeper 저장소를 사용하는 것이 좋습니다. 또는 디스크 저장소와 함께 ON CLUSTER 절을 사용할 수도 있습니다.
구성 기반 워크로드와 리소스
구성 포맷
CREATE WORKLOAD 및 CREATE RESOURCE SQL 문과 동일한 구문을 사용합니다. 모든 쿼리는 유효해야 합니다.
사용 권장 사항
- 인프라 한도를 설정할 수 있도록 구성에서 루트 워크로드와 네트워크 IO 리소스를 정의합니다.
- 이러한 한도를 강제 적용하도록
throw_on_unknown_workload를 설정합니다. - 모든 쿼리에 한도가 자동으로 적용되도록
CREATE WORKLOAD default IN all을 생성합니다 (workload쿼리 설정의 기본값이 ‘default’이기 때문입니다). - 사용자가 구성된 계층 구조 안에서 추가 워크로드를 생성할 수 있도록 허용합니다.
엄격한 리소스 액세스
throw_on_unknown_workload가 있습니다. 이 값을 true로 설정하면 모든 쿼리에서 유효한 workload 쿼리 설정을 사용해야 하며, 그렇지 않으면 RESOURCE_ACCESS_DENIED 예외가 발생합니다. 이 값을 false로 설정하면 해당 쿼리는 리소스 스케줄러를 사용하지 않으며, 즉 모든 RESOURCE에 무제한으로 액세스하게 됩니다. 쿼리 설정 use_concurrency_control = 0을 사용하면 쿼리가 CPU 스케줄러를 우회하여 CPU에 무제한으로 액세스할 수 있습니다. CPU 스케줄링을 강제하려면 use_concurrency_control이 읽기 전용 상수 값으로 유지되도록 setting constraint를 생성하십시오.
CREATE WORKLOAD default를 실행하지 않았다면 throw_on_unknown_workload를 true로 설정하지 마십시오. 시작 중에 workload를 명시적으로 설정하지 않은 쿼리가 실행되면 server 시작에 문제가 발생할 수 있습니다.스케줄링 노드 계층 구조
inflight_limit(constraint) - 동시에 처리 중인 요청 수가max_requests를 초과하거나 총 비용이max_cost를 초과하면 차단합니다. 단일 자식이 있어야 합니다.bandwidth_limit(constraint) - 현재 대역폭이max_speed를 초과하거나(0은 무제한), 버스트가max_burst를 초과하면 차단합니다(max_burst의 기본값은max_speed). 단일 자식이 있어야 합니다.fair(policy) - max-min 공정성에 따라 자식 노드 중 하나에서 다음에 처리할 요청을 선택합니다. 자식 노드는weight를 지정할 수 있습니다(기본값은 1).priority(policy) - 정적 우선순위에 따라 자식 노드 중 하나에서 다음에 처리할 요청을 선택합니다(값이 낮을수록 우선순위가 높음). 자식 노드는priority를 지정해야 합니다(기본값은 0).fifo(queue) - 리소스 용량을 초과한 요청을 보관할 수 있는 계층 구조의 리프 노드입니다.
limit- 자식의 총 할당량이 한도를 넘지 않도록 보장하며, 필요하면 하위 트리에서 축출 절차를 시작합니다. 단일 자식이 있어야 합니다.fair_allocation- max-min 공정성에 따라 축출을 적용합니다. 대기 중인 할당은 실행 중인 할당을 축출하지 않습니다. 자식 노드는weight를 지정할 수 있습니다(기본값은 1).precedence_allocation- 정적 precedence에 따라 축출을 적용합니다(값이 낮을수록 precedence가 높음). precedence가 더 높은 대기 중인 할당은 precedence가 더 낮은 할당을 축출합니다. 자식 노드는precedence를 지정해야 합니다(기본값은 0).queue- 실행 중인 할당과 대기 중인 할당을 보관할 수 있는 계층 구조의 리프 노드입니다.
더 이상 권장되지 않는 XML 구성
storage_configuration입니다:
특정 디스크에 대해 IO 스케줄링을 활성화하려면 storage configuration에서 read_resource 및/또는 write_resource를 지정해야 합니다. 이렇게 하면 지정된 디스크의 각 읽기 및 쓰기 요청에 어떤 리소스를 사용할지 ClickHouse에 알려줄 수 있습니다. 읽기 리소스와 쓰기 리소스는 동일한 리소스 이름을 참조할 수 있으며, 이는 로컬 SSD 또는 HDD에서 유용합니다. 여러 서로 다른 디스크가 동일한 리소스를 참조할 수도 있으며, 이는 원격 디스크에서 유용합니다. 예를 들어 “production” 워크로드와 “development” 워크로드 간에 네트워크 대역폭을 공정하게 분배하려는 경우에 유용합니다.
예시:
관련 항목
- system.scheduler
- system.workloads
- system.resources
- merge_workload MergeTree 설정
- merge_workload 서버 전역 설정
- mutation_workload MergeTree 설정
- mutation_workload 서버 전역 설정
- workload_path 서버 전역 설정
- workload_zookeeper_path 서버 전역 설정
- cpu_slot_preemption 서버 전역 설정
- cpu_slot_quantum_ns 서버 전역 설정
- cpu_slot_preemption_timeout_ms 서버 전역 설정