Scan 쿼리
Scan 쿼리 (Scan query)
Scan 쿼리는 Apache Druid의 원시 행(raw rows)을 스트리밍 모드로 반환하는 native 쿼리 타입이에요. 대량의 데이터를 병렬로 가져오고 싶을 때 유용해요.
출처: 문서
본문
Apache Druid는 두 가지 쿼리 언어를 지원해요: Druid SQL 과 native 쿼리. 이 문서는 native 언어의 한 쿼리 타입을 설명해요. Druid SQL이 언제 이 쿼리 타입을 사용하는지에 대한 정보는 SQL 문서 를 참고하세요.
Scan 쿼리는 원시 Apache Druid 행을 스트리밍 모드로 반환해요.
Scan 쿼리를 Broker에 제출하는 단순한 용도에 더해, Scan 쿼리를 Historical 프로세스나 스트리밍 인제스트 태스크에 직접 제출할 수도 있어요. 대량의 데이터를 병렬로 검색하려 할 때 유용해요.
Scan 쿼리 객체의 예시는 아래와 같아요:
{
"queryType": "scan",
"dataSource": "wikipedia",
"resultFormat": "list",
"columns": [ "__time", "isRobot", "page", "added", "isAnonymous", "user", "deleted" ],
"intervals": [ "2016-01-01/2017-01-02" ],
"batchSize": 20480,
"limit": 2
}
Scan 쿼리의 주요 파라미터는 다음과 같아요:
| 속성 | 설명 | 필수? |
| queryType | 이 String은 항상 "scan"이어야 해요. Druid가 쿼리를 해석하는 방법을 알아내기 위해 가장 먼저 보는 값이에요. | 예 |
| dataSource | 쿼리할 데이터 소스를 정의하는 String 또는 Object. 관계형 데이터베이스의 테이블과 매우 유사해요. 자세한 내용은 DataSource 를 참고하세요. | 예 |
| intervals | ISO-8601 Intervals를 나타내는 JSON Object. 쿼리를 실행할 시간 범위를 정의해요. | 예 |
| resultFormat | 결과 표현 방식: list, compactedList, valueVector. 현재는 list와 compactedList만 지원해요. 기본값은 list예요. | 아니요 |
| filter | Filters 참고 | 아니요 |
| columns | 스캔할 차원과 메트릭의 String 배열. 비워 두면 모든 차원과 메트릭이 반환돼요. | 아니요 |
| batchSize | 클라이언트에 반환되기 전에 버퍼링되는 최대 행 수. 기본값은 20480 이에요. | 아니요 |
| limit | 반환할 행 수. 지정하지 않으면 모든 행이 반환돼요. | 아니요 |
| offset | 결과를 반환할 때 이만큼의 행을 건너뛰어요. 건너뛴 행은 내부적으로 여전히 생성된 뒤 폐기돼야 하므로, offset을 매우 높은 값으로 올리면 쿼리가 추가 리소스를 사용할 수 있어요. limit과 offset을 함께 사용하면 페이지네이션을 구현할 수 있어요. 다만 페이지를 가져오는 사이에 기본 datasource가 전체 쿼리 결과에 영향을 주는 방식으로 수정되면, 서로 다른 페이지가 반드시 정렬되지 않을 수 있다는 점에 유의하세요. | 아니요 |
| order | 타임스탬프 기준 반환 행의 정렬. "ascending", "descending", "none"(기본값)이 지원돼요. 현재 "ascending"과 "descending"은 columns 필드에 __time 컬럼이 포함되고 아래 time ordering 섹션의 요구사항을 충족하는 쿼리에서만 지원돼요. | none |
| context | 특정 플래그를 지정하는 데 사용할 수 있는 추가 JSON Object (아래 query context properties 섹션 참고). | 아니요 |
결과 예시 (Example results)
resultFormat이 list일 때의 결과 형식:
[
{
"segmentId": "wikipedia_2016-06-27T00:00:00.000Z_2016-06-28T00:00:00.000Z_2024-12-17T13:08:03.142Z",
"columns": [ "__time", "isRobot", "page", "added", "isAnonymous", "user", "deleted" ],
"events": [
{
"__time": 1466985611080,
"isRobot": "true",
"page": "Salo Toraut",
"added": 31,
"isAnonymous": "false",
"user": "Lsjbot",
"deleted": 0
},
{
"__time": 1466985634959,
"isRobot": "false",
"page": "Bailando 2015",
"added": 2,
"isAnonymous": "true",
"user": "181.230.118.178",
"deleted": 0
}
],
"rowSignature": [
{ "name": "__time", "type": "LONG" },
{ "name": "isRobot", "type": "STRING" },
{ "name": "page", "type": "STRING" },
{ "name": "added", "type": "LONG" },
{ "name": "isAnonymous", "type": "STRING" },
{ "name": "user", "type": "STRING" },
{ "name": "deleted", "type": "LONG" }
]
}
]
resultFormat이 compactedList일 때의 결과 형식:
[
{
"segmentId": "wikipedia_2016-06-27T00:00:00.000Z_2016-06-28T00:00:00.000Z_2024-12-17T13:08:03.142Z",
"columns": [ "__time", "isRobot", "isUnpatrolled", "page", "added", "isNew", "delta", "isAnonymous", "user", "deleted", "namespace" ],
"events": [
[ 1466985611080, "true", "Salo Toraut", 31, "false", "Lsjbot", 0 ],
[ 1466985634959, "false", "Bailando 2015", 2, "true", "181.230.118.178", 0 ]
],
"rowSignature": [
{ "name": "__time", "type": "LONG" },
{ "name": "isRobot", "type": "STRING" },
{ "name": "page", "type": "STRING" },
{ "name": "added", "type": "LONG" },
{ "name": "isAnonymous", "type": "STRING" },
{ "name": "user", "type": "STRING" },
{ "name": "deleted", "type": "LONG" }
]
}
]
시간 정렬 (Time ordering)
Scan 쿼리는 현재 타임스탬프 기반 정렬을 지원해요. 시간 정렬을 사용하면 결과가 어떤 세그먼트의 행인지 나타내지 않는다는 점을 유의하세요 (segmentId가 null 로 표시돼요). 또한 시간 정렬은 결과 집합 limit이 druid.query.scan.maxRowsQueuedForOrdering 행보다 작은 경우, 또는 스캔된 모든 세그먼트가 druid.query.scan.maxSegmentPartitionsOrderedInMemory보다 적은 파티션을 가진 경우에만 지원돼요. 또한 세그먼트 목록을 지정하지 않으면 Historical에 직접 제출된 쿼리에는 시간 정렬이 지원되지 않아요. 이러한 제한이 있는 이유는 시간 정렬 구현이 제한 없이 사용하면 힙 메모리를 너무 많이 소비할 수 있는 두 가지 전략을 사용하기 때문이에요. 이 전략들(아래 나열)은 쿼리 결과 집합 limit과 스캔되는 세그먼트 수에 따라 Historical별로 선택돼요.
- Priority Queue: 각 Historical의 각 세그먼트가 순차적으로 열려요. 모든 행이 타임스탬프 순으로 정렬되는 제한된 우선순위 큐에 추가돼요. 결과 집합 limit을 초과하는 모든 행에 대해 가장 이른(내림차순) 또는 가장 늦은(오름차순) 타임스탬프의 행이 큐에서 빠져나와요(dequeued). 모든 행이 처리된 뒤에는 정렬된 우선순위 큐 내용이 일괄(batches)로 Broker에 스트리밍돼요. 너무 많은 행을 메모리에 로드하려 하면 Historical 노드가 메모리를 소진할 위험이 있어요.
druid.query.scan.maxRowsQueuedForOrdering속성은 시간 정렬 사용 시 쿼리 결과 집합의 행 수를 제한해서 이를 방지해요. - N-Way Merge: 각 세그먼트에 대해 각 파티션이 병렬로 열려요. 각 파티션의 행은 이미 시간 순서로 정렬되어 있으므로, 각 파티션의 결과에 n-way merge를 수행할 수 있어요. 이 방식은 Priority Queue처럼 전체 결과 집합을 메모리에 유지하지 않아요. merge 함수에서 반환되는 대로 배치를 스트리밍하기 때문이에요. 하지만 너무 많은 파티션을 쿼리하면 각각에 대해 압축 해제·디코딩 버퍼를 열어야 해서 메모리 사용량이 높아질 수 있어요.
druid.query.scan.maxSegmentPartitionsOrderedInMemorylimit은 시간 정렬 사용 시 동시에 열리는 파티션 수를 제한해서 이를 방지해요.
druid.query.scan.maxRowsQueuedForOrdering와 druid.query.scan.maxSegmentPartitionsOrderedInMemory 둘 다 구성 가능하며, 하드웨어 사양과 쿼리되는 차원 수에 따라 조정할 수 있어요. 이 구성 속성들은 쿼리 context의 maxRowsQueuedForOrdering과 maxSegmentPartitionsOrderedInMemory 속성으로 재정의할 수도 있어요 (Query Context Properties 섹션 참고).
구성 속성 (Configuration Properties)
구성 속성:
| 속성 | 설명 | 값 | 기본값 | | druid.query.scan.maxRowsQueuedForOrdering | 시간 정렬 사용 시 반환되는 최대 행 수 | [1, 2147483647] 범위의 정수 | 100000 | | druid.query.scan.maxSegmentPartitionsOrderedInMemory | 시간 정렬 사용 시 historical당 스캔되는 최대 세그먼트 수 | [1, 2147483647] 범위의 정수 | 50 |
쿼리 context 속성 (Query context properties)
| 속성 | 설명 | 값 | 기본값 | | maxRowsQueuedForOrdering | 시간 정렬 사용 시 반환되는 최대 행 수. 같은 이름의 config를 재정의해요. | [1, 2147483647] 범위의 정수 | druid.query.scan.maxRowsQueuedForOrdering | | maxSegmentPartitionsOrderedInMemory | 시간 정렬 사용 시 historical당 스캔되는 최대 세그먼트 수. 같은 이름의 config를 재정의해요. | [1, 2147483647] 범위의 정수 | druid.query.scan.maxSegmentPartitionsOrderedInMemory |
샘플 쿼리 context JSON 객체:
{
"maxRowsQueuedForOrdering": 100001,
"maxSegmentPartitionsOrderedInMemory": 100
}
레거시 모드 (Legacy mode)
옛날 버전의 Druid에서 scan 쿼리는 0.11보다 오래된 Druid 버전의 기존 scan-query contrib 확장과의 프로토콜 호환을 위해 설계된 레거시 모드를 지원했어요. 이 모드는 제거되었어요.
더 알아보기 (Learn more)
- Select 쿼리의 대체 — Scan 쿼리가 옛 Select 쿼리를 대체한 이력을 알아보세요.
- Druid SQL — SQL에서 행을 읽는 방법을 살펴보세요.