본 콘텐츠는 사용자의 편의를 고려해 자동 기계 번역 서비스를 사용하였습니다. 영어 원문과 다른 오류, 누락 또는 해석상의 미묘한 차이가 포함될 수 있습니다. 필요하시다면 영어 원문을 참조하시기를 바랍니다.
대량의 데이터를 처리할 때는 빠른 개요를 얻는 것이 도움이 되며, 이는 집계에서 SQL에서 제공하는 기능입니다. "GROUP BY 쿼리"로 알려진 집계는 조감도를 제공하므로 방대한 양의 데이터로부터 빠르게 인사이트를 얻을 수 있습니다.
이러한 이유로, Cloudflare에서는 R2 Data Catalog 에 저장된 데이터에 대해 SQL 쿼리를 실행할 수 있는 Cloudflare의 서버리스 분산 분석 쿼리 엔진인 R2 SQL 에서의 집계 지원을 발표하게 되어 기쁘게 생각합니다. R2 SQL 사용자는 집계를 통해 데이터의 중요한 동향과 변화를 파악하고, 보고서를 생성하며, 로그에서 이상 징후를 찾아낼 수 있습니다.
이번 릴리스에서는 분석 워크로드의 기반인 이미 지원되는 필터 쿼리를 기반으로 하며 사용자는 Apache Parquet 파일의 건초더미에서 바늘을 찾을 수 있습니다.
이 게시물에서는 집계의 유틸리티와 특징을 살펴보고 R2 데이터 카탈로그에 저장된 방대한 양의 데이터에 대해 이러한 쿼리를 실행할 수 있도록 R2 SQL을 확장한 방법을 자세히 알아봅니다.
집계 또는 “GROUP BY 쿼리”는 기본 데이터의 간단한 요약을 생성합니다.
일반적인 집계의 사용 사례는 보고서를 생성할 때입니다. 여러 국가와 일부 조직의 부서에 걸친 모든 매출에 대한 과거 데이터가 포함되어 있는 "매출"이라는 테이블을 생각해 보겠습니다. 이 집계 쿼리를 사용하면 부서별 매출액에 대한 보고서를 쉽게 생성할 수 있습니다.
SELECT department, sum(value)
FROM sales
GROUP BY department
"GROUP BY" 문을 사용하면 테이블 행을 버킷으로 분할할 수 있습니다. 각 버킷에는 특정 부서에 해당하는 레이블이 있습니다. 버킷이 가득 차면 각 버킷의 모든 행에 대한 "합(값)"을 계산하여 해당 부서에서 수행한 총 매출을 알 수 있습니다.
일부 보고서의 경우 가장 많은 볼륨을 가진 부서에만 관심이 있을 수 있습니다. 이런 경우에 “ORDER BY” 문이 유용합니다.
SELECT department, sum(value)
FROM sales
GROUP BY department
ORDER BY sum(value) DESC
LIMIT 10
여기에서는 쿼리 엔진에 모든 부서 버킷의 총량을 기준으로 내림차순으로 정렬하고 가장 큰 상위 10개만 반환하도록 지시합니다.
마지막으로, 이상을 필터링하는 데 관심이 있을 수 있습니다. 예를 들어, 보고서에 매출 합계가 5개보다 큰 부서만 포함시키고 싶을 수 있습니다. "HAVING" 문으로 이를 쉽게 수행할 수 있습니다.
SELECT department, sum(value), count(*)
FROM sales
GROUP BY department
HAVING count(*) > 5
ORDER BY sum(value) DESC
LIMIT 10
여기에서는 각 버킷에서 끝난 행 수를 계산하는 "count(*)"라는 새로운 집계 함수를 쿼리에 추가했습니다. 이는 각 부서의 매출 수와 직접적으로 일치하므로, 5개 이상의 행이 포함된 버킷만 남겨두도록 “HAVING” 문에 조건자를 추가했습니다.
집계에 대한 두 가지 접근 방식: 조만간 컴퓨팅
집계 쿼리에는 어디에도 저장되지 않은 열을 참조할 수 있다는 흥미로운 속성이 있습니다. "합(값)"을 고려해보세요. 이 열은 R2에 저장된 Parquet 파일에서 가져오는 "부서" 열과 달리 쿼리 엔진이 즉석에서 계산합니다. 이 미묘한 차이는 '합', '개수' 등의 집계를 참조하는 모든 쿼리를 두 단계로 분할해야 함을 의미합니다.
첫 번째 단계는 새 열을 계산하는 것입니다. "ORDER BY" 문을 사용하여 "count(*)" 열 기준으로 데이터를 정렬하거나 "HAVING" 문을 사용하여 데이터를 기준으로 행을 필터링하려면 이 열의 값을 알아야 합니다. "count(*)"와 같은 열의 값을 알면 나머지 쿼리 실행을 진행할 수 있습니다.
쿼리가 "HAVING" 또는 "ORDER BY"로 집계 함수를 참조하지 않지만 "SELECT"에서는 계속 사용하는 경우 트릭을 이용할 수 있습니다. 마지막까지 집계 함수의 값이 필요하지 않으므로 사용자에게 반환하기 직전에 부분적으로 집계 함수의 값을 계산하고 결과를 병합할 수 있습니다.
두 접근 방식의 주요 차이점은 집계 함수를 계산할 때입니다. 사용자가 필요로 하는 결과를 반복해서 구축합니다.
먼저, 저희가 "산란-수집 집계"라고 부르는 기술인 즉석 결과 작성에 대해 자세히 알아보겠습니다. 그런 다음 해당 기능을 기반으로 "HAVING" 및 "ORDER BY"와 같은 추가 계산을 집계 함수 위에서 실행할 수 있는 "집계 셔플링"을 도입합니다.
'HAVING' 및 'ORDER BY'가 없는 집계 쿼리도 필터링 쿼리와 유사한 방식으로 실행할 수 있습니다. 필터 쿼리의 경우 R2 SQL은 쿼리 실행에서 하나의 노드를 코디네이터로 선택합니다. 이 노드가 쿼리를 분석하고 R2 데이터 카탈로그를 참조하여 어떤 Parquet 행 그룹에 쿼리와 관련된 데이터가 포함될 수 있는지 파악합니다. 각 Parquet 행 그룹은 단일 컴퓨팅 노드가 처리할 수 있는 비교적 작은 작업을 나타냅니다. 코디네이터 노드는 작업을 여러 worker 노드에 분산하고 결과를 수집하여 사용자에게 반환합니다.
집계 쿼리를 실행하기 위해 동일한 단계를 수행하고 worker 노드 간에 작은 작업을 분산합니다. 그러나 이번에는 "WHERE" 문의 조건자를 기반으로 행을 필터링하는 대신 작업자 노드가 사전 집계도 계산합니다.
사전 집계는 집계의 중간 상태를 나타냅니다. 이는 데이터의 하위 집합에 대해 부분적으로 계산된 집계 함수를 나타내는 불완전한 데이터 조각입니다. 여러 사전 집계를 병합하여 집계 함수의 최종 값을 계산할 수 있습니다. 집계 함수를 사전 집계로 나누면 집계 계산을 수평적으로 확장할 수 있으므로 Cloudflare 네트워크에서 사용할 수 있는 방대한 계산 리소스를 사용할 수 있습니다.
예를 들어 'count(*)'의 사전 집계는 단순히 데이터 하위 집합의 행 수를 나타내는 숫자입니다. 최종 '개수(*)'를 계산하는 것은 이들 숫자를 더하는 것만큼 쉽습니다. 'avg(value)'의 사전 집계는 'sum(value)'와 'count(*)'라는 두 숫자로 구성됩니다. "avg(value)"의 값은 모든 "sum(value)" 값을 더하고, 모든 "count(*)" 값을 더한 다음, 마지막으로 한 숫자를 다른 숫자로 나누어 계산할 수 있습니다.
작업자 노드가 사전 집계 컴퓨팅을 완료하면, 결과를 코디네이터 노드로 스트리밍합니다. 코디네이터 노드는 모든 결과를 수집하고 사전 집계에서 집계 함수의 최종 값을 계산하여 그 결과를 사용자에게 반환합니다.
분산-수집은 코디네이터가 Workers의 작은 부분 상태를 병합하여 최종 결과를 계산할 수 있을 때 매우 효율적입니다. SELECT sum(sales) FROM orders와 같은 쿼리를 실행하면 코디네이터는 각 worker로부터 단일 숫자를 수신하여 합산합니다. 코디네이터의 메모리 풋프린트는 R2에 있는 데이터의 양과 관계없이 무시할 수 있습니다.
그러나 이 접근 방식은 쿼리가 집계 결과 를 기반으로 정렬하거나 필터링해야 하는 경우 비효율적입니다. 판매량 기준 상위 2개 부서를 찾는 다음 쿼리를 고려해보세요.
SELECT department, sum(sales)
FROM sales
GROUP BY department
ORDER BY sum(sales) DESC
LIMIT 2
글로벌 상위 2위를 올바르게 결정하려면 전체 데이터 세트에 걸쳐 모든 부서의 총 매출을 알아야 합니다. 데이터가 기본 Parquet 파일 간에 효과적으로 무작위로 분산되어 있으므로 특정 부서의 영업이 여러 직원에게 분산될 가능성이 높습니다. 부서에서 현지 상위 2개 목록에서 제외하면 모든 개별 worker의 매출이 낮지만, 총 매출을 보면 전 세계적으로 가장 높은 매출을 기록할 수 있습니다.
아래 다이어그램에는 이 쿼리에 대해 분산-수집 접근 방식이 작동하지 않는 이유가 나와 있습니다. "A 부서"는 글로벌 영업 리더이지만, 직원들에게 매출이 고르게 분포되어 있으므로 일부 현지 상위 2개 목록에 들지 못하며, 코디네이터가 폐기하게 됩니다.
따라서 글로벌 집계 기준으로 쿼리 결과를 정렬하는 경우, 코디네이터가 사전에 필터링된 Workers의 결과를 신뢰할 수 없습니다. 전역 합계를 계산하려면 먼저 모든 작업자에게 모든 부서에 대한 합계 개수를 요청해야 합니다. IP 주소나 사용자 ID와 같이 카디널리티가 높은 열을 기준으로 그룹화하면 코디네이터가 강제로 수백만 개의 행을 수집하고 병합해야 하므로 단일 노드에서 리소스 병목 현상이 발생합니다.
이를 해결하려면 최종 집계가 수행되기 전에 특정 그룹에 대한 데이터를 같은 위치에 배치하는 방법인 셔플링이 필요합니다.
무작위 데이터 분포 문제를 해결하기 위해 셔플링 단계를 도입합니다. 작업자들은 결과를 코디네이터에게 전송하는 대신 서로 직접 데이터를 교환하여 그룹화 키를 기반으로 행을 배치합니다.
이 라우팅은 결정론적 해시 파티셔닝에 의존합니다. 작업자가 행을 처리할 때 GROUP BY 열을 해시 처리하여 대상 작업자를 식별합니다. 이 해시는 결정론적이므로 클러스터의 모든 작업자는 특정 데이터를 보낼 위치에 독립적으로 합의합니다. "엔지니어링"이 Worker 5로 해시되면 모든 worker는 "엔지니어링" 행을 Worker 5로 라우팅한다는 것을 알게 됩니다. 중앙 레지스트리가 필요하지 않습니다.
아래 그림에 이러한 흐름이 나와 있습니다. Workers 1, 2, 3에서 "부서 A"가 어떻게 시작되는지 주목하세요. 해시 함수는 "부서 A"를 Worker 1에 매핑하므로 모든 Workers는 이러한 행을 동일한 대상으로 라우팅합니다.
집계를 무작위로 섞으면 올바른 결과가 생성됩니다. 그러나 이러한 포괄적인 교환으로 인해 타이밍 종속성이 발생합니다. 작업자 3이 데이터 점유율 전송을 완료하기 전에 작업자 1에서 "부서 A"의 최종 합계 계산을 시작하면 결과가 완전하지 않게 됩니다.
이 문제를 해결하기 위해 Cloudflare는 엄격한 동기화 장벽을 적용합니다. 작업자가 나가는 데이터를 버퍼링하고 gRPC 스트림을 통해 피어로 플러시하는 동안 코디네이터가 전체 클러스터의 진행 상황을 추적합니다. 모든 worker가 입력 파일 처리 및 셔플 버퍼 플러시가 완료되었음을 확인한 경우에만 코디네이터가 작업을 진행하라는 명령을 내립니다. 이 장벽은 다음 단계가 시작될 때 각 worker의 데이터 세트가 완전하고 정확함을 보장합니다.
동기화 장벽이 제거되면 모든 worker는 할당된 그룹에 대한 전체 데이터 세트를 보유하게 됩니다. 이제 Worker 1은 "부서 A"에 대한 100% 판매 레코드를 가지고 있으며 최종 합계를 확실하게 계산할 수 있습니다.
이를 통해 필터링 및 정렬과 같은 계산 논리를 코디네이터에게 부담을 주지 않고 worker에게 맡길 수 있습니다. 예를 들어, 쿼리에 HAVING count(*) > 5가 포함된 경우, 작업자는 집계 직후 이 기준을 충족하지 않는 그룹을 필터링할 수 있습니다.
이 단계가 끝나면 각 worker는 자신이 소유한 그룹에 대해 정렬되고 마무리된 결과 스트림을 생성합니다.
퍼즐의 마지막 조각은 코디네이터입니다. 분산-수집 모델에서는 코디네이터가 전체 데이터 세트를 집계하고 정렬하는 값비싼 작업을 담당했습니다. 셔플링 모델에서는 역할이 바뀝니다.
Workers는 이미 최종 집계를 계산하고 로컬에서 정렬했으므로, 코디네이터는 k-way 병합만 수행하면 됩니다. 모든 작업자에게 스트림을 열어주고 결과를 한 행씩 읽습니다. 각 worker에서 현재 행을 비교하고 정렬 순서에 따라 "승자"를 선택하고 사용자에게 전송되는 쿼리 결과에 추가합니다.
이 접근 방식은 특히 LIMIT 쿼리에 대해 강력합니다. 사용자가 상위 10개 부서를 요청하면 코디네이터는 상위 10개 항목을 찾을 때까지 스트림을 병합한 다음 즉시 처리를 중단합니다. 남은 수백만 개의 행을 로드하거나 병합할 필요가 없으므로 컴퓨팅 리소스를 과도하게 소비하지 않고도 작업 규모를 확장할 수 있습니다.
방대한 데이터세트를 처리할 수 있는 강력한 엔진
집계 기능이 추가된 R2 SQL 은 데이터 필터링에 적합한 도구에서 벗어나 대규모 데이터세트의 데이터를 처리할 수 있는 강력한 엔진으로 탈바꿈합니다. 이는 스캐터-게더링 및 셔플링과 같은 분산 실행 전략을 구현하여 가능하며, Cloudflare의 대규모 컴퓨팅 및 네트워크의 규모를 사용하여 데이터가 있는 곳으로 컴퓨팅을 푸시할 수 있습니다.
보고서를 생성하든, 대량의 로그에서 이상 징후를 모니터링하든, 단순히 데이터에서 트렌드를 파악하든, 복잡한 OLAP 인프라를 관리하거나 R2 외부로 데이터를 이동시키는 오버헤드 없이 Cloudflare의 개발자 플랫폼 내에서 모든 작업을 쉽게 수행할 수 있습니다.
R2 SQL에서 집계 지원은 오늘부터 제공됩니다. 여러분은 R2 Data Catalog의 데이터로 이러한 새로운 함수를 어떻게 사용할지 기대됩니다.