Druid를 혼합 워크로드(mixed workloads)용으로 구성하기
Druid를 혼합 워크로드(mixed workloads)용으로 구성하기
Apache Druid 클러스터에서 동시적이고 이질적인 워크로드를 자주 실행한다면, 전체 쿼리 성능을 최적화하도록 Druid가 클러스터 자원을 제대로 할당하게 구성해야 해요. 이 문서에서는 두 가지 자원 격리 전략인 쿼리 레인이(Query laning)과 서비스 티어링(Service tiering)을 설명드릴게요.
출처: 문서
본문
Apache Druid 클러스터에서 동시적이고 이질적인 워크로드를 자주 실행한다면, 전체 쿼리 성능을 최적화하도록 Druid가 클러스터 자원을 제대로 할당하게 구성해야 합니다. 각 Druid 쿼리는 처리 스레드, 중간 쿼리 결과용 메모리 버퍼, Broker와 데이터 서버 간 통신을 위한 HTTP 스레드 같은 일정량의 클러스터 자원을 소모해요.
큰 결과를 반환하는 "무거운(heavy)" 쿼리는 짧게 실행되는 "가벼운(light)" 쿼리보다 자원을 더 많이 소모합니다. 일반적으로는 이런 오래 걸리는 자원 집약적 쿼리가 짧은 대화형(interactive) 쿼리의 성능을 조절(throttle)하는 것을 원하지 않을 거예요. 예를 들어 두 쿼리 세트를 같은 Druid 클러스터에서 실행한다면, 무거운 쿼리가 모든 가용 HTTP 스레드를 점유할 수 있습니다. 이런 상황은 이후의 쿼리들—무겁든 가볍든—을 느려지게 하고, 그 쿼리들에 타임아웃 오류를 유발할 수 있어요.
적절한 자원 격리(resource isolation)를 사용하면, 짧게 실행되는 우선순위가 높은 쿼리들을 방해하지 않으면서 오래 걸리고 낮은 우선순위의 쿼리를 실행할 수 있어요. Druid는 자원을 격리하고 쿼리 동시성을 개선하기 위해 다음 전략을 제공합니다:
- 쿼리 레인이(Query laning): 각 Broker에서 실행되는 오래 걸리는 쿼리의 최대 개수에 상한을 둡니다.
- 서비스 티어링(Service tiering): 쿼리 우선순위에 따라 서로 다른 쿼리 할당을 받도록 별도의 Historical과 Broker 그룹을 정의합니다.
Druid 쿼리는 Druid 쿼리 메트릭 분석, JVM의 스레드 덤프(thread dump), 플레임 그래프(flame graph) 같은 일반적인 성능 프로파일링 기법으로 프로파일링해서 혼합 워크로드가 어떤 자원에 영향을 주는지 파악할 수 있어요. 가장 큰 병목은 Broker HTTP 스레드에 있을 가능성이 높아요. Broker HTTP 스레드의 자원 경쟁(resource contention)은 쿼리 레인으로 완화할 수 있습니다. 다만 혼합 워크로드는 처리 스레드와 병합 버퍼(merge buffer)를 포함한 다른 자원들에도 영향을 줘요. 이런 자원들의 부담을 줄이려면 서비스 티어링과 쿼리 레인을 모두 적용하세요.
쿼리 레인(Query laning)
이질적인 워크로드를 가진 많은 동시 쿼리를 실행해야 할 때는, 쿼리 성능을 최적화하려면 쿼리 레인부터 시작하세요. 쿼리 레인은 덜 시급한 쿼리의 자원 사용을 제한해서 우선순위가 높은 쿼리에 전용 자원이 확보되게 합니다.
쿼리 레인은 고속도로의 카풀(carpool) 레인과 일반 레인에 비유할 수 있어요. 쿼리 레인을 사용하면 Druid가 우선순위가 부여된 레인과 다른 일반 레인을 분리합니다. Druid는 낮은 우선순위 쿼리를 일반 레인으로 제한하고, 우선순위가 높은 쿼리는 VIP든 일반 레인이든 가능한 어디서든 실행되게 합니다.
Druid에서 쿼리 레인은 Broker HTTP 스레드를 위해 자원을 예약합니다. 각 Druid 쿼리는 Broker 스레드 하나를 필요로 해요. Broker의 스레드 수는 druid.server.http.numThreads 파라미터로 정의됩니다. Broker 스레드는 건강 검사(health check) 같은 쿼리 외의 태스크로도 점유될 수 있어요. 쿼리 레인을 사용하면 자원 집약적 쿼리에 지정된 HTTP 스레드 수를 제한해서, 나머지 스레드를 짧게 실행되는 쿼리와 다른 태스크에 남겨둘 수 있어요.
일반 속성(General properties)
broker/runtime.properties 파일에 다음 쿼리 레인 속성들을 설정하세요.
druid.query.scheduler.laning.strategy– 쿼리를 레인에 할당하는 데 사용하는 전략. 내장된 "high/low" 레인 전략을 사용하거나, 직접 레인 전략을 수동으로 정의할 수 있어요.druid.query.scheduler.numThreads– Broker당 서빙할 수 있는 총 쿼리 수. 이 값은druid.server.http.numThreads보다 1~2 적게 설정하는 것을 권장합니다.
쿼리 스케줄러는 기본적으로 Broker가 서빙할 수 있는 쿼리 수를 제한하지 않아요. 이 속성을 유계(bounded) 숫자로 설정하면 스레드 개수를 제한합니다. 할당된 스레드가 모두 점유되면, 대화형 쿼리를 포함한 들어오는 모든 쿼리는 broker에서 큐에 대기되고, 요청이 구성된 타임아웃보다 더 오래 큐에 남으면 타임아웃됩니다. 이 구성된 타임아웃은 MIN(Integer.MAX_VALUE, druid.server.http.maxQueryTimeout)과 같습니다. druid.server.http.maxQueryTimeout 값이 음수이면 요청은 영원히 큐에 대기합니다.
레인별 속성(Lane-specific properties)
high/low 레인 전략을 사용한다면 다음을 설정하세요:
druid.query.scheduler.laning.maxLowPercent– 낮은 우선순위 쿼리를 처리하는 쿼리 스레드의 최대 백분율. 나머지 쿼리 스레드는 높은 우선순위 쿼리에 전용됩니다.
쿼리를 높거나 낮은 우선순위로 표시하도록 Broker에 대한 우선순위화(prioritization) 전략도 정의하는 것을 고려하세요. 그렇지 않으면 쿼리 컨텍스트(query context)에서 들어오는 쿼리의 우선순위를 수동으로 설정하세요.
**수동 레인 전략(manual laning strategy)**을 사용한다면 다음을 설정하세요:
druid.query.scheduler.laning.lanes.{name}–name레인에서 실행할 수 있는 쿼리 수의 상한. 필요에 따라 많은 이름 붙은 레인을 정의할 수 있어요.druid.query.scheduler.laning.isLimitPercent– 레인 상한을 정확한 숫자로 취급할지,druid.server.http.numThreads또는druid.query.scheduler.numThreads중 최솟값의 백분율로 취급할지 여부.
수동 레인에서는 들어오는 쿼리를 쿼리 컨텍스트의 lane 파라미터에서 원하는 레인으로 표시할 수 있어요. 쿼리 레인 구성에 대한 추가 세부 사항은 Query prioritization and laning 문서를 참고하세요.
예시
high/low 레인 전략을 사용한 쿼리 레인 구성 예시:
# Laning strategy
druid.query.scheduler.laning.strategy=hilo
druid.query.scheduler.laning.maxLowPercent=20
# Limit the number of HTTP threads for query processing
# This value should be less than druid.server.http.numThreads
druid.query.scheduler.numThreads=40
서비스 티어링(Service tiering)
서비스 티어링에서는 쿼리의 세그먼트와 자원 요구 사항에 따라 쿼리를 관리하도록 별도의 Historical과 Broker 그룹을 정의합니다. 특정 유형의 쿼리를 위해 예약된 자원을 제한할 수 있어요.
복잡한 서브쿼리나 큰 결과 세트를 포함한 많은 무거운 쿼리는 우선순위가 높은 대화형 쿼리로부터 자원을 잡아챌(hog) 수 있어요. 이런 무거운 쿼리를 별도의 Broker 티어로 제한하면 그 영향을 최소화할 수 있어요. 무거운 쿼리를 위해 예약된 모든 Broker가 점유되면, 이후의 무거운 쿼리는 지정된 자원이 가용될 때까지 기다려야 합니다. 오래 기다리면 이후의 쿼리들이 타임아웃 오류로 실패하게 돼요.
참고로 별도의 Broker 티어 없이도 Historical 프로세스를 티어로 분리할 수 있어요. Historical 전용 티어링만으로는 Druid 클러스터의 혼합 워크로드 요구를 충족하기에 충분하지 않습니다. 다만 특정 세그먼트를 다른 것보다 더 자주 쿼리할 때, 예를 들어 최근 데이터를 자주 분석할 때는 유용해요. Historical 티어링은 특정 시간 간격의 데이터를 특정 티어에 할당해서 핫(hot) 데이터에 대한 더 높은 동시성을 지원합니다.
아래 예시들은 Historical과 Broker 모두에 대해 핫(hot)과 콜드(cold) 두 개의 티어를 보여줘요. Broker들은 오래 걸리는 무거운 쿼리보다 짧게 실행되는 가벼운 쿼리를 먼저 서빙합니다. 가벼운 쿼리는 핫 티어로 라우팅되고, 무거운 쿼리는 콜드 티어로 라우팅될 거예요.
Historical 티어링
이 섹션은 세그먼트 로딩을 구성하고 Historical 서비스를 티어에 할당하는 방법을 설명합니다.
세그먼트 로딩 구성
Coordinator 서비스는 로드 규칙(load rules)을 사용해 세그먼트를 서로 다른 Historical 티어에 할당해요. 세그먼트 리플리카가 서로 다른 Historical 티어에 어떻게 할당되어야 하는지를 나타내는 로드 규칙을 정의하세요. 예를 들어 더 최근 데이터의 세그먼트를 성능을 위해 더 강력한 하드웨어에 저장할 수 있어요.
로드 규칙에는 forever, interval, period의 여러 유형이 있습니다. 각 Historical에 대해 모든 세그먼트를 로드할지, 특정 시간 간격 안의 세그먼트를 로드할지, 특정 시간 기간 안의 세그먼트를 로드할지 등 사용 사례에 맞는 로드 규칙을 선택하세요. interval과 period 로드 규칙에는 대응하는 드롭 규칙(drop rules)이 함께 필요합니다.
로드 규칙에서는 tieredReplicants 속성에 티어를 정의하세요. 티어에 설명적인 이름을 제공하고, 각 티어가 몇 개의 리플리카를 가져야 하는지 지정하세요. 핫 티어에 더 많은 리플리카를 지정하면 쿼리 처리를 위한 동시성을 높일 수 있어요.
다음 예시는 "hot"과 "_default_tier"라는 이름의 두 Historical 티어가 있는 period 로드 규칙을 보여줍니다. 가장 최근 한 달의 데이터에 대해 Druid는 핫 티어에 3개의 리플리카, 기본 콜드 티어에 1개의 리플리카를 로드해요. 이 한 달의 데이터에 의존하는 들어오는 쿼리는 콜드 Historical 티어의 단일 리플리카나 핫 Historical 티어의 3개 리플리카 중 아무거나 사용할 수 있어요.
{
"type" : "loadByPeriod",
"period" : "P1M",
"includeFuture" : true,
"tieredReplicants": {
"hot": 3,
"_default_tier" : 1
}
}
세그먼트 로드 규칙에 대한 자세한 내용은 Load rules 문서를 참고하세요. Druid 웹 콘솔에서 보존 규칙(retention rules)을 설정하는 예시는 Tutorial: Configuring data retention 문서를 방문해 보세요.
Historical을 티어에 할당
Historical을 티어에 할당하려면, 그 Historical의 historical/runtime.properties에 티어 이름에 대한 레이블을 추가하고 우선순위 값을 설정하세요.
핫 티어의 Historical 예시:
druid.server.tier=hot
druid.server.priority=1
콜드 티어의 Historical 예시:
druid.server.tier=_default_tier
druid.server.priority=0
이 속성들에 대한 자세한 내용은 Historical general configuration 문서를 참고하세요.
Broker 티어링
Broker 티어링을 사용하려면 먼저 Historical 티어링을 설정해야 해요. Broker 티어링을 설정하려면 Broker를 티어에 할당하고, Router가 쿼리 라우팅을 구성하도록 하세요.
Broker를 티어에 할당
각 Broker에 대해 broker/runtime.properties 파일에서 Broker 그룹을 정의하세요.
핫 티어의 Broker 구성 예시:
druid.service=druid:broker-hot
콜드 티어의 Broker 구성 예시:
druid.service=druid:broker-cold
또한 broker/runtime.properties 파일에서 Broker가 우선순위로 Historical을 선택하도록 지시해서, Broker가 콜드 티어의 Historical보다 핫 티어의 Historical을 먼저 선택하게 하세요.
핫 티어 Historical을 우선시하는 Broker 구성 예시:
druid.broker.select.tier=highestPriority
이 속성들에 대한 자세한 내용은 Broker configuration 문서를 참고하세요.
Broker 가시성을 특정 티어로 제한
기본적으로 Broker는 세그먼트 가용성에 대해 모든 Historical 티어를 감시합니다. druid.broker.segment.watchedTiers를 사용해 Broker가 특정 티어의 세그먼트만 보도록 제한할 수 있어요. 이는 엄격한 쿼리 격리를 제공합니다 — 감시되지 않는 티어의 세그먼트는 Broker에 보이지 않으며 그 Broker로 쿼리할 수 없어요.
"hot"이라는 이름의 티어에 있는 Historical만 쿼리하는 Broker 구성 예시:
druid.broker.segment.watchedTiers=["hot"]
여러 티어를 쿼리하는 Broker 구성 예시:
druid.broker.segment.watchedTiers=["hot","_default_tier"]
druid.broker.select.tier와 druid.broker.segment.watchedTiers의 차이를 주목하세요:
druid.broker.select.tier는 보이는 티어 중에서의 **선호도(preference)**를 제어합니다 (어느 티어를 먼저 쿼리할지)druid.broker.segment.watchedTiers는 **가시성(visibility)**을 제어합니다 (Broker가 아예 볼 수 있는 티어가 무엇인지)
세그먼트가 Broker가 감시하는 티어에 존재하지 않으면, Broker는 그 세그먼트를 인식하지 못하며 그 데이터에 대한 쿼리는 부분적이거나 아예 없는 결과를 반환할 거예요.
쿼리 라우팅 구성
router/runtime.properties 파일에 기본 Broker 티어와 Historical 티어 대 Broker 티어 매핑을 설정해서 Router가 쿼리를 적절히 라우팅하도록 지시하세요.
핫/콜드 티어 Broker를 각각 핫/콜드 티어 Historical에 매핑하는 Router 구성 예시:
druid.router.defaultBrokerServiceName=druid:broker-cold
druid.router.tierToBrokerMap={"hot":"druid:broker-hot","_default_tier":"druid:broker-cold"}
Druid SQL 쿼리를 실행할 계획이라면 다음을 설정해 SQL 쿼리의 라우팅도 활성화하세요:
druid.router.sql.enable=true
프로덕션 구성 예시는 Router process 문서를 참고하세요.
더 알아보기 (Learn more)
Multitenancy considerations 문서에서 쿼리 동시성을 멀티테넌트 워크로드에 적용하는 방법을 참고하세요.