Sentry 의 내부 인프라에서 대규모 이벤트 데이터를 처리하고 검색하는 데 사용되는 핵심 기술은 Snuba 입니다. Snuba 는 자체적으로 정의한 쿼리 언어인 SnQL 을 통해 사용자가 직관적으로 데이터 모델에 접근할 수 있게 합니다. 본 장에서는 SnQL 의 문법적 구조와 각 구절 (clause) 의 역할, 그리고 실제 API 호출 시 발생하는 요청 형식을 깊이 있게 살펴봅니다.
SnQL 기본 구문 및 전송 형태
SnQL 쿼리는 텍스트 문자열 형태로 구성되며, 이를 Snuba 서버가 해석하여 ClickHouse 등의 백엔드 저장소로 변환합니다. 일반적인 쿼리 흐름은 다음과 같은 단계로 이루어집니다.
MATCH <entity_selection>
[SELECT <expression_list> | <aggregation>]
[ARRAY JOIN <column>]
[WHERE <condition>]
[HAVING <condition>]
[ORDER BY <sort_expression>]
[LIMIT <value> | OFFSET <value>]
[GRANULARITY <interval>]
[TOTALS]
이러한 쿼리 문자열은 HTTP POST 요청의 본문으로 전달됩니다. 엔드포인트는 보통 /:dataset/snql 로 지정되며, 요청 본문은 다음 형태의 JSON 객체로 인코딩됩니다.
{
"statement": "<SnQL 쿼리 문자열>",
"target_dataset": "<데이터셋 이름>",
"is_consistent": false,
"turbo_mode": false,
"enable_debug": true
}
여기서 target_dataset 은 종종 URL 경로에 의해 결정되기도 하지만, 명시적으로 포함될 수도 있습니다. 주요 필드는 statement 이며 나머지 옵션들은 쿼리의 실행 전략이나 디버깅 정보를 제어하기 위한 선택지입니다.
엔티티 매칭 (MATCH)
Snuba 의 데이터 모델은 그래프 형태의 엔티티 관계도로 표현됩니다. MATCH 구절은 쿼리가 참조할 특정 부분 그래프를 정의하는 역할을 합니다. 크게 세 가지 방식으로 작성할 수 있습니다.
1. 단일 엔티티 조회 (Simple)
가장 기본적인 형태로, 하나의 엔티티 노드를 대상으로 데이터를 선택합니다. 이때 임의 샘플링을 적용하고자 할 경우 SAMPLE 키워드를 병기할 수 있습니다.
MATCH (error_logs [SAMPLE 0.05])
위 예시는 error_logs 엔티티 전체 중 5% 만을 무작위로 추출하여 쿼리를 수행하도록 지시합니다.
2. 서브쿼리 활용 (Subquery)
중첩된 쿼리가 필요한 경우 MATCH 내부에 완전한 쿼리 블록을 대괄호로 묶어 사용할 수 있습니다. 내부 쿼리의 결과가 외부 스코프에서 별칭을 사용하여 노출됩니다.
MATCH {
MATCH (request_traces)
SELECT mean(response_time) AS avg_latency BY service_name
}
SELECT max(avg_latency)
이 경우 외부 쿼리는 서브쿼리 결과 집합인 avg_latency 를 대상으로 최댓값을 계산하게 됩니다.
3. 관계형 조인 (Join)
두 개 이상의 엔티티 간의 관계를 통해 데이터를 결합할 때 사용합니다. -> 연산자는 엔티티 간의 연결 방향성을 나타내며, 현재는 1:n, n:1, 1:1 관계만 지원됩니다. 각 엔티티는 고유한 별칭을 가져야 하며, 조인 조건은 시스템 레벨에 정의되어 있어 사용자가 직접 명시하지 않습니다.
MATCH
(t: traces) -[release]-> (r: releases),
(t: traces) -[owner]-> (u: users)
SELECT count() AS total_count BY t.project_id, r.version
WHERE u.name = "admin_user"
조인 결과는 전통적인 RDBMS 와 유사하게 튜플 리스트 형태로 반환되며, 각 행에는 조인에 참여한 모든 엔티티의 컬럼이 병합되어 있습니다.
프로젝션 및 그룹핑 (SELECT .. BY)
SELECT 는 최종 출력에 포함되어야 할 열들을 정의합니다. 만약 집계 함수 (예: count, sum) 가 포함된 경우, BY 뒤에 오는 값들은 그룹화 기준 키로 간주됩니다. 집계 없이 전체 데이터에 대해 계산을 원한다면 BY 를 생략할 수 있으나, 이 경우 선택 항목은 오직 집계식이어야 합니다.
MATCH (transactions)
SELECT sum(duration), max(status_code) AS status_max BY http_method
조인이 발생한 쿼리에서는 컬럼 참조 시 반드시 별칭.column 형식을 사용해야 에러를 방지할 수 있습니다.
필터링 조건 (WHERE, HAVING)
데이터 필터링은 타이밍에 따라 두 단계로 나뉩니다.
WHERE: 집계 전에 행 단위 필터링을 수행합니다. 논리 연산자 (=,LIKE,IN등) 를 사용하여 복잡한 조건식을 구성할 수 있습니다. 양변은 리터럴 또는 식으로 표현 가능합니다.HAVING:SELECT단계에서 산출된 집계 결과를 기준으로 추가 필터링을 가할 때 사용합니다. 예를 들어 평균 지속 시간이 1 초 이상인 그룹만 남기고 싶다면HAVING을 활용합니다.
정렬 및 페이징 (ORDER BY, LIMIT, OFFSET)
결과집합의 순서를 조절하려면 ORDER BY 를 활용하며, 정렬 방향은 ASC 또는 DESC 로 설정할 수 있습니다. 대규모 데이터 처리 시 전량 조회보다는 페이지네이션이 필수적이므로 LIMIT 와 OFFSET 파라미터를 지원합니다. 미지정 시 기본값은 각각 1000 과 0 으로 설정되어 있습니다.
고급 옵션 (TOTALS, GRANULARITY, SAMPLE)
추가적인 통계 정보나 성능 최적화를 위한 키워드들이 존재합니다.
- TOTALS: 활성화되면 응답 객체에 모든 결과 행의 합계 값을 포함하는
"totals"필드가 추가됩니다. - GRANULARITY: 시간 기반 데이터 분할 시 사용되며, 정수 단위의 기간을 지정하여 결과集的 그룹화 해상도를 조정합니다.
- SAMPLE:
MATCH수준에서 명시하지 않은 경우, 쿼리 말미에 샘플링 비율 (0~1 사이의 실수) 또는 샘플 크기 (정수) 를 정의하여 실행 속도를 높일 수 있습니다. 이는 읽기 부하를 줄이기 위해 일부 행만 대표 샘플링하는 전략입니다.