husky

3 개의 포스트

Husky 쿼리 엔진 내부: 100조 개 이벤트에 대한 실시간 액세스 (새 탭에서 열림)

Datadog은 매일 100조 개 이상의 이벤트와 수십억 개의 쿼리를 처리하기 위해 3세대 이벤트 저장소인 'Husky'를 구축했습니다. Husky의 쿼리 엔진은 고정된 스키마가 없고 데이터 볼륨이 가변적인 대규모 멀티테넌트 환경에서도 오브젝트 스토리지에 저장된 페타바이트급 데이터를 실시간으로 조회할 수 있도록 설계되었습니다. 이를 위해 시스템은 쿼리 플래너, 오케스트레이터, 메타데이터 서비스, 리더 서비스로 역할을 분산하여 성능과 비용 효율성을 동시에 달성했습니다. ### Husky의 데이터 모델과 주요 쿼리 패턴 Husky는 로그나 네트워크 트래픽과 같이 타임스탬프와 다양한 속성을 가진 '이벤트' 데이터를 저장하며, 서비스별로 상이한 데이터 형태를 유연하게 수용합니다. * **가변적인 스키마 지원:** 테넌트나 사용 사례(로그 vs 네트워크 데이터)에 따라 속성의 종류와 데이터의 크기가 극명하게 달성하더라도 효율적으로 처리할 수 있습니다. * **Needle-in-a-haystack 검색:** 수많은 데이터 중 특정 IP나 에러 메시지, 트레이스 ID 등을 찾아내는 고도로 선택적인 필터링 쿼리를 지원합니다. * **분석형(Analytics-style) 검색:** 특정 기간 동안의 서비스 지연 시간 추이나 지역별 매출 분석과 같이 대규모 데이터를 집계하여 시각화하는 쿼리를 최적화합니다. ### 쿼리 실행의 4단계 아키텍처 Husky의 쿼리 경로는 네 가지 핵심 서비스로 나뉘어 멀티테넌트 환경에서 안정적으로 동작합니다. * **쿼리 플래너(Query Planner):** 모든 쿼리의 진입점으로, 쿼리 유효성 검사 및 최적화를 수행합니다. 통계 데이터를 기반으로 쿼리를 시간 단위의 여러 단계로 분할하고 실행 결과를 최종 병합합니다. * **쿼리 오케스트레이터(Query Orchestrator):** 데이터 저장소의 관문 역할을 하며 메타데이터 조회, 프래그먼트(데이터 파일) 할당, 집계 조율을 담당합니다. 특히 '존 맵(Zone-map) 프루닝'을 통해 불필요한 데이터를 걸러냄으로써 다운스트림 작업량을 평균 30~60% 절감합니다. * **메타데이터 서비스(Metadata Service):** FoundationDB의 프런트엔드로서 데이터 컴팩션 중에도 쿼리 결과의 원자성(Atomicity)을 보장합니다. DB 내부 로직을 추상화하여 전체 쿼리 경로와 분리하는 역할을 합니다. * **리더 서비스(Reader Service):** 실제 오브젝트 스토리지에 저장된 프래그먼트에서 데이터를 읽어 응답을 반환하는 핵심 실행 엔진입니다. ### 리더(Reader) 서비스의 데이터 스캔 최적화 오브젝트 스토리지는 비용이 저렴하지만 읽기 속도가 느리므로, 리더 서비스는 "읽지 않아도 되는 데이터를 스캔하지 않는 것"을 최우선 목표로 삼습니다. * **행 그룹(Row Groups) 구조:** 수백만 행을 가진 대용량 프래그먼트를 '행 그룹' 단위로 물리적으로 배치하여 관리합니다. 이는 쿼리 실행 시 전체 파일을 메모리에 올리는 부담을 줄이고 메모리 부족(OOM) 오류를 방지합니다. * **입출력(I/O) 최소화:** 오브젝트 스토리지에 대한 GET 요청은 비용이 많이 들기 때문에, 쿼리에 꼭 필요한 행 그룹만 선택적으로 가져와 비용 효율성과 응답 속도를 극대화합니다. * **반복자 기반 실행 모델:** Volcano 모델에서 영감을 받은 반복자(Iterator) 방식을 사용하여 데이터를 효율적으로 스트리밍하며 처리합니다. Husky의 사례는 대규모 시계열 이벤트를 처리할 때 고정된 인덱스에 의존하기보다, 메타데이터 기반의 프루닝과 물리적인 데이터 레이아웃 최적화를 통해 오브젝트 스토리지의 한계를 극복할 수 있음을 보여줍니다. 저비용 고성능의 로그 분석 시스템을 설계한다면 데이터의 물리적 구조화와 단계별 쿼리 분산 처리가 핵심이 될 것입니다.

Husky: Datadog 규모의 효율적인 컴팩션 (새 탭에서 열림)

Husky는 대규모 관측(observability) 데이터를 처리하기 위해 객체 스토리지 위에 구축된 분산 저장 시스템으로, 매일 수조 개의 이벤트를 효율적으로 관리하는 데 최적화되어 있습니다. 이 시스템은 데이터를 '조각(fragment)' 단위로 저장하고 컴팩션(compaction) 과정을 통해 쿼리 성능과 스토리지 비용 사이의 최적의 균형을 맞추는 것을 핵심 전략으로 삼습니다. 특히 파운데이션DB(FoundationDB)를 활용한 원자적 메타데이터 관리와 병렬 워커 기반의 스캔 구조를 통해 데이터 가용성을 유지하면서도 대규모 분석 쿼리를 신속하게 처리합니다. ## Husky의 쿼리 실행 및 조각화 구조 * Husky는 유입된 이벤트를 조각(fragment)이라 불리는 파일로 묶어 객체 스토리지(S3, GCS 등)에 저장하며, 각 조각에 대한 메타데이터를 별도로 관리합니다. * 쿼리 실행 시 시스템은 메타데이터를 검색하여 관련 있는 조각들을 식별하고, 이를 워커(worker) 풀에 분산하여 병렬로 스캔합니다. * 전체 쿼리 비용은 객체 스토리지에서 가져와야 하는 조각의 수와 해당 파일 내에서 스캔해야 하는 이벤트 수에 비례합니다. * 따라서 효율적인 조회를 위해 파일 수를 제어하는 '스트리밍 머지' 방식의 컴팩션과 쿼리당 스캔 이벤트를 줄이는 데이터 조직화 전략을 사용합니다. ## 컴팩션의 "골디락스(Goldilocks)" 문제 컴팩션은 여러 작은 조각을 하나의 큰 조각으로 병합하는 과정으로, 시스템의 효율성을 결정하는 핵심 요소입니다. Husky는 다음 요소들 사이에서 최적의 균형점(Goldilocks)을 찾습니다. * **파일 크기의 상충 관계:** 파일이 너무 작으면 객체 스토리지 접근 지연 시간과 메타데이터 부하가 커지며, 반대로 너무 크면 쿼리 워커 간의 병렬 처리가 제한되어 대규모 쿼리 속도가 느려집니다. * **컴팩션 비용과 성능:** 컴팩션 작업 자체도 CPU와 객체 스토리지 I/O 비용을 발생시키므로, 작업을 최소화하면서도 쿼리 성능을 높일 수 있는 적정 수준의 병합이 필요합니다. * **데이터 레이아웃 최적화:** 컴팩션 시 시간적 혹은 공간적(태그 등) 유사성에 따라 데이터를 재배치하면 압축률이 향상되고 쿼리 시 스캔해야 할 데이터 범위를 좁힐 수 있습니다. * **벡터화 실행:** Husky 워커는 많은 행을 빠르게 스캔하기 위해 벡터화된 실행(vectorized execution) 방식을 사용하며, 이는 적절한 크기의 조각에서 가장 효율적으로 작동합니다. ## FoundationDB를 통한 원자적 상태 관리 * 데이터 유입이 빈번한 환경에서 사용자가 즉시 데이터를 조회할 수 있도록, Husky는 유입 경로에서 짧은 버퍼링 후 작은 조각들을 빠르게 생성합니다. * 수많은 조각의 메타데이터를 관리하기 위해 트랜잭션 보장이 강력한 FoundationDB를 메타데이터 저장소로 사용합니다. * 컴팩션이 완료되면 FoundationDB의 트랜잭션 기능을 이용해 이전 조각들을 새 조각으로 '원자적(atomic)으로 교체'합니다. * 이를 통해 쿼리 시스템은 컴팩션 진행 중에도 데이터 중복이나 누락 없이 항상 일관된 상태의 테이블을 조회할 수 있습니다. 대규모 시계열 및 관측 데이터를 다루는 시스템을 설계할 때는 무조건적인 데이터 병합보다는 쿼리 패턴과 객체 스토리지의 특성을 고려한 컴팩션 정책이 중요합니다. 특히 메타데이터 계층에서 원자성을 확보하여 데이터 일관성을 유지하고, 병렬 스캔의 이점을 극대화할 수 있는 '적정 크기'의 데이터 블록을 유지하는 설계가 권장됩니다.

Husky: Exactly-once ingestion and multi-tenancy at scale (새 탭에서 열림)

Datadog의 3세대 이벤트 저장소인 Husky는 대규모 멀티테넌트 환경에서 데이터 중복 없는 '정확히 한 번(Exactly-once)'의 인입을 보장하기 위해 데이터 지역성(Locality) 기반의 라우팅 아키텍처를 도입했습니다. 스캔과 집계에 최적화된 Husky의 특성상 고성능 포인트 조회가 어렵다는 점을 극복하기 위해, 결정론적 샤딩을 통해 중복 제거의 범위를 샤드 단위로 한정하여 시스템 복잡도를 낮췄습니다. 이를 통해 테넌트별 데이터 격리 비용을 최소화하고, 가변적인 트래픽 상황에서도 효율적으로 스토리지와 컴퓨팅 자원을 확장할 수 있는 기반을 마련했습니다. ## 샤드 라우터를 통한 데이터 지역성 확보 * **결정론적 매핑**: 업스트림 서비스인 'Shard Router'를 사용하여 이벤트의 ID와 타임스탬프를 기반으로 특정 샤드(파티션 그룹)에 이벤트를 할당합니다. * **샤드 할당 전략**: 각 테넌트에게 고정된 리스트의 샤드를 할당하고, 해당 리스트 내에서 이벤트 ID를 해싱하여 샤드를 선택함으로써 무작위 라우팅을 방지합니다. * **테넌트 격리**: 개별 워커 노드가 노출되는 테넌트와 인덱스의 수를 최소화하여, 시스템 전체의 복잡도를 관리 가능한 수준으로 유지합니다. ## 데이터 지역성 도입의 기술적 이점 * **중복 제거(Deduplication) 효율화**: 동일한 ID를 가진 이벤트는 항상 같은 샤드로 라우팅되므로, 전체 시스템이 아닌 샤드 내부에서만 중복 여부를 확인하면 됩니다. 이벤트 ID 세트가 메모리에 수용 가능한 크기로 유지되어 처리 속도가 비약적으로 향상됩니다. * **스토리지 비용 절감**: Husky는 테넌트별로 데이터를 전용 테이블에 격리하며 파일 단위로 저장합니다. 지역성을 통해 워커당 처리하는 테넌트 수를 제한하면 생성되는 파일 수가 줄어들어, 클라우드 스토리지 비용과 후속 컴팩션(Compaction) 작업의 부하를 동시에 낮출 수 있습니다. * **성능 최적화**: 워커 노드가 처리해야 할 논리적 네임스페이스의 카디널리티(Cardinality)가 낮아짐에 따라 쓰기 성능이 개선되고 리소스 효율성이 높아집니다. ## 동적 환경에서의 라우팅 도전 과제 * **할당 변경 관리**: 특정 테넌트의 트래픽이 갑자기 수십 배 증가하거나 전체 샤드 수가 변경될 때, 기존의 결정론적 규칙을 유지하면서도 유연하게 샤드 배정을 변경해야 합니다. * **분산 노드 간 합의**: 모든 샤드 라우터 노드가 동일한 라우팅 규칙을 공유해야 중복 데이터 유입을 방지할 수 있으며, 이를 위해 노드 간의 일관성 있는 결정 메커니즘이 필수적입니다. * **부하 분산(Load Balancing)**: 모든 샤드에 인입 트래픽이 균등하게 배분되도록 설계하여 특정 워커 노드에 부하가 집중되는 '핫스팟' 현상을 방지해야 합니다. 대규모 분산 시스템에서 데이터 일관성을 유지하며 비용을 최적화하려면, 무상태(Stateless) 라우팅보다는 데이터의 특성에 맞춘 지역성 설계를 우선 고려해야 합니다. 특히 테넌트 수가 많은 SaaS 환경에서는 워커가 처리하는 테넌트의 카디널리티를 물리적으로 제한하는 것이 스토리지 관리 비용을 결정짓는 핵심 요소가 됩니다.