로깅
로깅 (Logging) 개요
DuckDB는 쿼리 실행, 성능 메트릭, 시스템 이벤트 같은 것에 대해 자세한 정보를 제공하는 로깅 메커니즘을 구현하고 있어요.
출처: 공식문서
로깅 켜고 끄기
DuckDB의 로깅 메커니즘은 enable_logging이라는 특수 함수로 켜거나 끌 수 있어요. 로그는 duckdb_logs라는 특별한 뷰에 저장되는데, 일반 테이블처럼 쿼리할 수 있어요.
예시:
CALL enable_logging();
-- Run some queries...
SELECT * FROM duckdb_logs;
로깅을 끄려면:
CALL disable_logging();
현재 로그를 비우려면:
CALL truncate_duckdb_logs();
로그 레벨
DuckDB는 로그의 상세 정도(verbosity)를 제어하는 여러 로그 레벨을 지원해요:
ERROR: 오류 메시지만 기록WARN: 경고와 오류를 기록INFO: 일반 정보, 경고, 오류를 기록 (기본값)DEBUG: 상세한 디버깅 정보를 기록TRACE: 매우 상세한 추적(tracing) 정보를 기록
로그 레벨은 다음처럼 설정해요:
CALL enable_logging(level = 'debug');
로그 타입
DuckDB에서 로그 메시지는 연관된 로그 타입(log type)을 가질 수 있어요. 로그 타입은 두 가지 주요 기능을 제공해요:
- 로그 메시지 생성을 세밀하게 제어
- 구조적 로깅(structured logging) 지원
특정 타입의 메시지만 기록하려면:
CALL enable_logging('HTTP');
위 함수는 올바른 로그 레벨을 자동으로 설정하고 HTTP 타입을 enabled_log_types 설정에 추가해요. 이렇게 하면 HTTP 타입의 로그 메시지만 로그에 기록되는 게 보장돼요.
여러 로그 타입을 켜려면 이렇게 전달해요:
CALL enable_logging(['HTTP', 'QueryLog']);
HTTP 같은 일부 로그 타입은 연관된 메시지 스키마가 있어요. DuckDB가 메시지를 자동으로 파싱하게 하려면 duckdb_logs_parsed() 매크로를 사용해요. 예를 들면:
SELECT request.headers FROM duckdb_logs_parsed('HTTP');
각 구조적 로그 타입의 스키마를 보려면:
DESCRIBE FROM duckdb_logs_parsed('HTTP');
로그 타입 목록
DuckDB에서 사용 가능한 로그 타입의 (완전하지 않은) 목록이에요.
| 로그 타입 | 설명 | 구조적(Structured) |
|---|---|---|
QueryLog |
DuckDB에서 실행되는 쿼리를 기록 | No |
FileSystem |
DuckDB 파일시스템과의 모든 상호작용 기록 | Yes |
HTTP |
DuckDB 내부 HTTP 클라이언트의 모든 HTTP 트래픽 기록 | Yes |
PhysicalOperator |
쿼리 실행 중 물리 연산자가 방출하는 이벤트 기록 | Yes |
Metrics |
쿼리 실행 중 수집된 프로파일링 메트릭 기록 | Yes |
구조적 로그 타입은 다음 스키마를 노출해요. 언제든 DESCRIBE FROM duckdb_logs_parsed(log_type)로 확인할 수 있어요:
| 로그 타입 | 스키마 |
|---|---|
FileSystem |
fs VARCHAR, path VARCHAR, op VARCHAR, bytes BIGINT, pos BIGINT |
HTTP |
request STRUCT(type, url, start_time, duration_ms, headers MAP), response STRUCT(status, reason, headers MAP) |
PhysicalOperator |
operator_type VARCHAR, parameters MAP(VARCHAR, VARCHAR), class VARCHAR, event VARCHAR, info MAP(VARCHAR, VARCHAR) |
Metrics |
metric VARCHAR, value VARCHAR |
로그 저장소 (Log Storage)
기본적으로 DuckDB는 인메모리 로그 저장소(memory)에 기록해요. DuckDB는 서로 다른 여러 종류의 로그 저장소를 지원해요. 현재 코어 DuckDB에 구현된 로그 저장소 타입은 다음과 같아요.
| 로그 저장소 | 설명 |
|---|---|
memory |
(기본값) 인메모리 버퍼에 기록 |
stdout |
현재 프로세스의 stdout에 기록 (CSV 형식) |
file |
CSV 파일(들)에 기록 |
참고로 duckdb_logs 뷰는 현재 활성화된 로그 저장소를 대상으로 자동으로 업데이트돼요. 즉 로그 저장소를 바꾸면 duckdb_logs 함수가 반환하는 내용에 영향을 줄 수 있어요.
CALL enable_logging(storage = 'stdout');
CALL enable_logging(storage = 'file', storage_config = {'path': 'path/to/store/logs'});
또는 동등한 축약형을 사용해요:
CALL enable_logging(storage_path = 'path/to/store/logs');
정규화(Normalized) vs 비정규화(Denormalized)
DuckDB의 로그 저장소는 두 가지 방식으로 기록할 수 있어요: 정규화(normalized) vs 비정규화(denormalized).
비정규화 로깅에서는 로그 컨텍스트 정보가 각 로그 항목에 직접 붙고, 정규화 로깅에서는 로그 항목이 컨텍스트 정보를 참조하는 context_ids와 함께 별도로 저장돼요.
| 로그 저장소 | 정규화(Normalized) |
|---|---|
memory |
yes |
file |
configurable |
stdout |
no |
파일 저장소의 경우 정규화와 비정규화 사이를 전환할 수 있어요. .csv로 끝나는 경로를 주면 정규화, .csv 없이 주면 비정규화예요. 파일 로깅에서는 일반적으로 비정규화가 권장돼요. 성능이 향상되고 로그 전체 크기도 줄어들기 때문이에요. file 로그 저장소의 정규화를 설정하려면:
-- normalized: `/tmp/duckdb_log_contexts.csv` 와 `/tmp/duckdb_log_entries.csv` 생성
CALL enable_logging(storage_path = '/tmp');
-- denormalized: `/tmp/logs.csv` 생성
CALL enable_logging(storage_path = '/tmp/logs.csv');
정규화와 비정규화의 차이는 보통 사용자에게는 duckdb_logs 함수를 통해 숨겨져 있어요. 이 함수는 정규화된 테이블을 자동으로 하나의 통합 결과로 조인해요. 예를 들어 위 두 설정 모두 FROM duckdb_logs;로 쿼리할 수 있고 동일한 결과를 만들어내요.
버퍼링
DuckDB의 로그 저장소는 로깅 성능을 최적화하는 버퍼링 메커니즘을 구현해요. 이 구현은 메시지 로깅과 저장소 쓰기 사이에 잠재적인 지연을 도입해요. 이 지연은 실제 메시지 작성 시점을 흐릿하게 만들 수 있는데, 특히 크래시(충돌) 디버깅 시 문제가 돼요. 크래시 직전에 생성된 메시지가 기록되지 않을 수 있기 때문이에요. 이 문제를 해결하기 위해 버퍼 크기를 다음과 같이 설정할 수 있어요:
CALL enable_logging(storage_config = {'buffer_size': 0});
또는 동등한 축약형:
CALL enable_logging(storage_buffer_size = 0);
기본 버퍼 크기는 로그 저장소마다 달라요:
| 로그 저장소 | 기본 버퍼 크기 |
|---|---|
memory |
STANDARD_VECTOR_SIZE (2048) |
file |
STANDARD_VECTOR_SIZE (2048) |
stdout |
비활성(Disabled) (0) |
예를 들어 stdout 로깅 성능을 높이고 싶다면 버퍼링을 켜기만 하면 로깅이 크게 (>10x) 빨라져요:
CALL enable_logging(storage = 'stdout', storage_buffer_size = 2048);
또는 DuckDB의 크래시를 디버깅하면서 file 로거로 무슨 일이 일어나는지 이해하고 싶다고 가정해볼게요. 버퍼링을 꺼서 해결할 수 있어요:
CALL enable_logging(storage_path = '/tmp/mylogs', storage_buffer_size = 0);
문법 설탕 (Syntactic Sugar)
DuckDB에는 흔한 경로를 더 쉽게 만들어주는 문법 설탕이 있어요. 예를 들어 다음 문들은 모두 같아요:
-- 일반적인 호출
CALL enable_logging(storage = 'file', storage_config = {'path': 'path/to/store/logs'});
-- 흔한 저장소 경로 config 파라미터의 축약형
CALL enable_logging(storage = 'file', storage_path = 'path/to/store/logs');
-- `storage = 'file'` 생략 -> `storage_config` 존재에서 추론됨
CALL enable_logging(storage_config = {'path': 'path/to/store/logs'});