사용 매뉴얼
이 설명서는 쿼리 최적화 앱이 어떤 상황에서 어떤 방식으로 로그프레소 쿼리를 최적화하는지 설명합니다.
1. Search Pushdown 최적화
SearchPushdownPlanner 는 search 조건을 가능한 한 앞쪽(데이터 소스 근처)으로 이동시켜 불필요한 데이터 처리량을 줄이는 최적화입니다.
1.1 개념 요약
-
search 조건을 뒤에서 앞으로 끌어올려서
- table 또는 fulltext 단계에서 바로 필터링 → 이후 연산량 감소
-
가능한 경우
- search 조건을 table/fulltext 인덱스 쿼리로 병합하여 속도 향상
-
지원 범위
- 비교 연산: ==, !=, >, >=, <, <=
- null 검사: isnull, isnotnull
- 논리 연산: and, or
- 일부 타입 변환: date, ip, 시간 필드(_time) 조건
1.2 시간 함수 자동 변환 (ago, now → date)
사용자가 자주 쓰는 ago, now 함수는 내부적으로 먼저 date 함수로 변환됩니다. 이 과정은 자동으로 수행되며, 쿼리 의미는 바뀌지 않습니다.
왜 변환하나요?
- 검색 pushdown 및 인덱스 병합은 단순 상수 형태의 시간 값에 대해 동작합니다.
- ago, now 는 함수이기 때문에 직접 병합이 어렵고, 실제 시간 문자열을 가지는 date 형태로 바꾸면 인덱스 최적화가 가능해집니다.
변환 예시
변경 전:
변경 후 (예: 현재 시각이 2025-11-08 23:00:00 인 경우):
table from=20251108 to=20251109 web_logs
| search _time >= date("2025-11-08 22:00:00", "yyyy-MM-dd HH:mm:ss")
변경 전:
변경 후 (예: 현재 시각이 2025-11-08 23:00:00 인 경우):
사용 시 유의사항
- ago("1h"), ago("30m") 처럼 문자열 상수 인자만 지원합니다.
- now 는 항상 변환 대상입니다.
- 내부적으로 시간 변환에 실패하는 특수 상황에서는, 안전하게 원본 쿼리를 유지합니다.
1.3 search Pushdown 동작 흐름
-
쿼리를 왼쪽에서 오른쪽으로 순회하며 search 명령들을 찾습니다.
-
각 search 표현식이 pushdown 가능한 형태인지 검사합니다.
-
해당 위치에서 뒤로(backward) 거슬러 올라가며 앞쪽 명령들을 검사합니다.
-
명령 타입에 따라
- 통과: search 를 더 앞으로 이동
- 차단: 해당 지점에서 더 이상 이동 불가
- 특수 처리: rename, dataset, join 등
-
pushdown 가능한 최대 지점까지 search 를 이동시키거나, 데이터 소스(table, fulltext, dataset, join) 내부로 밀어 넣어 최적화합니다.
여러 개의 search 가 있는 경우
예:
- 왼쪽에서 오른쪽 순으로 A → B → C 를 처리합니다.
- 각각 독립적으로 최대한 앞쪽으로 이동하지만, 원래 순서(A → B → C)는 보존됩니다.
1.4 Pushdown 가능한 search 조건
다음과 같은 단순한 필터 조건은 pushdown 대상입니다.
-
비교 연산자
- ==, !=, >, >=, <, <=
-
null 검사
- search isnotnull(user_id)
- search isnull(error_code)
-
논리 연산
- A and B, A or B
-
타입 변환
- date, ip 함수
-
특수 처리되는 != 연산
!= 연산 처리 규칙
- 숫자, IP, 날짜 타입에 대해서만 인덱스 병합 최적화 대상입니다. 예) search status_code != 500, search src_ip != ip("192.168.0.1")
인덱스로 변환 시에는 의미를 보정하기 위해 다음과 같이 해석합니다.
단순 != 500 비교는 원래 null 을 포함하지 않는 의미가 아니므로, 의미가 달라지지 않도록 != null 조건을 함께 붙입니다.
문자열 != 비교는 최적화하지 않음
예:
- 문자열 비교에서 인덱스는 대소문자를 구분하지 않기 때문에 의미가 달라질 수 있습니다.
- 이 경우는 pushdown / merge 최적화를 하지 않고 원본을 그대로 유지합니다.
1.5 주요 명령과 search 의 관계
1) order 와의 관계
변경 전:
order 는 정렬만 담당하므로, search 를 앞으로 이동할 수 있습니다.
변경 후:
2) fields 와의 관계
- fields 가 선택한 필드 안에 search 에서 사용하는 필드가 포함되어 있으면 통과 및 이동
- 포함되어 있지 않으면 해당 지점에서 더 이상 이동 불가
통과 가능한 경우:
→
차단되는 경우:
→ 변경 없음
3) rename 과의 관계
변경 전:
- search 에서 사용하는 필드명 code 를 rename 이전 이름 status_code 로 되돌린 뒤 pushdown 합니다.
변경 후:
4) dataset 서브쿼리로의 pushdown
변경 전:
변경 후:
dataset [
table web_logs
| fields status_code
| search status_code == 200
]
| # pushdown: search status_code == 200
사용자는 dataset ... | search ... 형태로 작성해도, 내부적으로는 데이터를 가져오는 단계에서 먼저 필터링되어 성능 이득을 얻습니다.
5) join 계열과의 관계
지원 범위 및 제약:
- 단일 키 join 만 지원 (복합 키 join 은 pushdown 대상 아님)
- join 키에 대한 == 비교만 pushdown
- cross join 은 pushdown 불가
변경 전:
변경 후 (개념):
table web_logs
| search user_id == 1000
| join type=inner user_id [
table users
| search user_id == 1000
]
join 키가 아닌 필드에 대한 search 는 기본적으로 이동하지 않습니다.
1.6 Search → 인덱스 병합 (Merge Phase)
Pushdown 이후에는 table 또는 fulltext 명령과 search 조건을 인덱스 쿼리로 합치는 작업이 수행됩니다.
1) Numeric (정수, IP, 날짜) 비교
다음과 같은 조건은 인덱스로 직접 변환되고, 원본 search 는 제거됩니다.
- 정수 비교: status_code == 200, bytes > 1024
- IP 비교: src_ip == ip("192.168.1.1")
- 날짜 비교: _time > date("2025-01-01", "yyyy-MM-dd")
변경 전:
변경 후:
부동소수(float, double) 비교는 아직 인덱스 병합 최적화 대상이 아닙니다. 예: response_time > 1.5 → 그대로 search 로 남습니다.
2) 문자열 == 비교
예:
- 인덱스 병합 + 원본 search 유지가 동시에 수행됩니다.
- 인덱스 검색은 대소문자를 구분하지 않기 때문에, 최종적으로 원본 search 로 한 번 더 필터링하여 대소문자까지 정확히 일치하도록 보장합니다.
개념적으로:
3) 문자열 != 비교
search method != "POST" 와 같은 문자열 != 비교는 앞서 설명한 이유로 최적화 대상에서 제외되며, 원본 쿼리를 변경하지 않습니다.
1.7 _time 조건과 기간(from/to) 병합
_time 필터는 가능한 경우 table 또는 fulltext 의 from, to 옵션으로 직접 병합됩니다.
지원되는 시간 함수
- date(value, format)
- ago("1h") 등 문자열 상수 인자
- now()
병합 규칙 (개념)
- 전체 시간 범위는 [from, to) (from 포함, to 미포함)
- 초 단위까지 사용하며, 밀리초는 범위 계산 시 적절히 내림 또는 올림 처리합니다.
예:
변경 전:
table from=20251108 to=20251109 web_logs
| search _time >= date("2025-11-08 10:00:00", "yyyy-MM-dd HH:mm:ss")
and _time < date("2025-11-08 18:00:00", "yyyy-MM-dd HH:mm:ss")
변경 후:
_time 조건만 있는 경우에는 table 을 fulltext 로 바꾸지 않고, from, to 만 조정하여 불필요한 데이터 스캔을 줄입니다.
빈 시간 범위
_time 조건으로 인해 최종 from 값이 to 값 이상이 되면 결과가 존재할 수 없으므로, 쿼리 전체가 result 0 으로 변환됩니다.
예:
변경 전:
변경 후:
밀리초 처리
밀리초가 포함된 시간 비교는 포괄 범위만 병합하고, 원본 search 는 유지합니다.
예:
table from=20251108 to=20251109 web_logs
| search _time >= date("2025-11-08 13:00:00.500", "yyyy-MM-dd HH:mm:ss.SSS")
- from 은 13:00:00 으로 내림(floor)
- search 조건은 그대로 유지하여 정확한 밀리초 필터링을 보장합니다.
1.8 Search Pushdown 이 중단되는 경우
다음 명령을 만나면 해당 지점에서 더 이상 search 를 앞쪽으로 이동시키지 않습니다.
- 새로운 데이터 소스를 정의하는 driver 명령 예: table, fulltext, dataset, json
- 아직 pushdown 을 지원하지 않는 변환 명령 예: eval, rex, parse, lookup, 일부 join / union 케이스 등
이 경우에도 쿼리 기능 자체는 그대로 유지되며, 단지 성능 최적화만 포기하는 형태입니다.
2. Stats Fields Pushdown 최적화
StatsFieldsPushdownPlanner 는 stats, pivot, rollup, cube 와 같은 집계 명령 앞에 자동으로 fields 를 삽입해, 집계에 정말 필요한 필드만 남기도록 만들어 줍니다.
2.1 동작 개요
-
쿼리에서 stats, pivot, rollup, cube 명령을 찾습니다.
-
해당 집계에 필요한 필드들을 계산합니다.
- group by 대상 (by 절, rows/cols)
- 집계 함수 인자 필드 (예: sum(bytes) → bytes)
-
집계 명령 바로 앞에 fields 를 삽입합니다.
-
앞쪽으로 거슬러 올라가며(backward walk)
- 기존 fields, rename, eval 등을 조정·정리
- 불필요한 필드는 제거
결과적으로 집계 이전 단계에서 불필요한 필드를 미리 제거하여, 메모리 사용량과 CPU 부담을 줄입니다.
2.2 예시
원본:
log schema="web_logs"
| rename status_code as code
| search method == "GET"
| eval kb = bytes / 1024
| stats sum(kb) by code
결과:
-
집계에 필요한 필드
- group by: code → 원본 필드 status_code
- 집계 인자: kb → 원본 필드 bytes
- search 에 필요한 필드: method
log schema="web_logs"
| fields method, bytes, status_code
| search method == "GET"
| rename status_code as code
| eval kb = bytes / 1024
| stats sum(kb) by code
3. Fulltext 조건 중복 제거
FulltextDeduplicationPlanner 는 fulltext 쿼리 내부에 완전히 동일한 조건이 중복된 경우 이를 제거합니다.
변경 전:
변경 후:
의미 변화 없이 쿼리를 단순화하여 내부 평가 비용을 줄이며, 사용자가 별도로 신경 쓸 필요는 없습니다.
4. 중복 order 제거
RedundantOrderRemovalPlanner 는 마지막 필드 정렬 상태에 영향을 주지 않는 불필요한 중간 order 명령을 제거합니다.
변경 전:
변경 후:
fields, rename, 마지막 order 등 최종 필드 순서를 결정하는 명령이 뒤에 있을 경우, 앞쪽의 order 는 결과에 의미 있는 영향을 주지 않는 것으로 보고 제거합니다.
5. explain 을 이용한 최적화 진단
쿼리가 의도한 대로 변환되지 않는 것 같을 때는 explain 명령으로 최적화 과정을 확인할 수 있습니다.
예:
explain [
log schema="session" alias=t
| search 시작 >= ago("10m")
| stats count by 출발지IP
| search is_changed
]
실행하면 다음과 같은 컬럼을 가진 테이블이 표시됩니다.
- step: 최적화 순서
- planner: 적용된 최적화 플래너 이름 (예: log-command-rewriter, search-pushdown-optimizer, fulltext-deduplication-optimizer, stats-fields-pushdown-optimizer 등)
- is_changed: 이전 단계와 비교했을 때 쿼리가 실제로 변경되었는지 여부
- query: 해당 단계에서의 쿼리 문자열
이를 통해
- 언제 어떤 플래너가 동작했는지
- search pushdown, _time 병합, stats fields 축소 등이 실제로 적용되었는지
를 단계별로 확인할 수 있습니다.
6. 의도한 대로 쿼리가 변환되지 않을 때
대부분의 경우 최적화는 성능만 바꾸고, 쿼리 결과(행 수, 값)는 동일해야 합니다. 만약 다음과 같은 절차를 거쳤을 때 쿼리 결과가 달라진다면 이는 버그일 가능성이 높습니다.
-
최적화 앱이 활성화된 상태에서 쿼리를 실행한다.
-
동일한 쿼리에 대해 최적화 관련 앱을 비활성화한 뒤 다시 실행한다.
-
두 실행 결과를 비교했을 때
- 행 수가 다르거나
- 통계 값이 달라지거나
- 특정 레코드가 사라지거나 추가되는 등 결과 데이터 자체가 달라진다.
이런 경우에는 아래 정보를 Logpresso 지원 포탈에 첨부해 문의해 주세요.
-
문제가 된 원본 쿼리 전체
-
동일 쿼리를
- 앱 활성화 상태에서 실행한 결과
- 앱 비활성화 상태에서 실행한 결과 를 비교할 수 있는 자료 (결과 화면 캡처, 결과 다운로드 파일 등)
-
앱 활성화 상태에서 실행한 쿼리를 explain 으로 감싼 결과
예)
- explain 화면 캡처 이미지
- 가능하면 우측 상단 다운로드 버튼으로 받은 결과 텍스트
-
기대한 동작과 실제 동작의 차이에 대한 짧은 설명
위와 같이 앱 활성화/비활성화 시 쿼리 결과가 달라지는 사례를 지원 포탈로 접수해 주시면, 개발팀에서 explain 결과를 기준으로 원인을 분석하고, 제약 사항에 대한 설명 또는 필요한 경우 패치를 통해 개선을 진행합니다.
7. 쿼리 작성 시 팁 요약
- 단순 비교 위주의 search 를 사용하면 최적화 효과가 크습니다.
- 시간 필터는 _time 기준으로 명시하고 from, to 와 함께 사용하는 것을 권장합니다.
- 복잡한 문자열 함수는 현재 pushdown 대상이 아니므로, 가능한 경우 전처리 search 와 조합해서 사용하는 것이 좋습니다.
- 의도대로 최적화가 일어나지 않는다면 explain 결과를 확인한 뒤, 필요 시 지원 포탈로 전달해 주시면 분석 및 패치를 진행합니다.