[2026] C++ Elasticsearch 통합 | 전문 검색·집계·실시간 인덱싱 [#52-5]
이 글의 핵심
Elasticsearch 핵심 개념: 역인덱스, 전문 검색, 집계, 벌크 인덱싱. 실무 문제 시나리오, 완전한 REST API 예제, 자주 발생하는 에러, 성능 최적화, 프로덕션 패턴까지 C++ 연동 전 필수 지식.
들어가며: “로그 검색이 10초 넘게 걸려요”
실제 겪는 문제 시나리오
C++ 서버에서 로그를 저장하고 검색하는 기능을 구현할 때, 관계형 DB나 단순 파일 검색의 한계에 부딪힙니다. 시나리오 1: 수백만 건 로그에서 키워드 검색이 너무 느림
상황: MySQL에 로그를 저장하고 LIKE '%error%' 또는 LIKE '%timeout%'로 검색
문제: 풀 테이블 스캔 발생, 인덱스가 와일드카드 앞부분 검색에 무력함
결과: 500만 건에서 10~30초 소요, 실시간 모니터링 불가
시나리오 2: 전문 검색(Full-Text Search) 필요
상황: 제품 설명에서 "무선 이어폰 블루투스 노이즈캔슬링"으로 검색
문제: 단어 분리, 유사어 매칭, 점수 기반 정렬이 관계형 DB에서 어려움
결과: Elasticsearch 역인덱스 + 분석기로 밀리초 단위 검색
시나리오 3: 실시간 집계·대시보드
상황: API 호출 수, 에러율, 평균 응답 시간을 1분 단위로 집계해 대시보드 표시
문제: 매분마다 COUNT, AVG 쿼리를 실행하면 DB 부하 급증
결과: Elasticsearch date_histogram, terms 집계로 실시간 집계
시나리오 4: 대량 로그 인덱싱 시 DB 부하
상황: 초당 1만 건 로그를 C++ 서버에서 저장
문제: INSERT 1만 번/초는 관계형 DB에 과부하, 커넥션 풀 고갈
결과: Elasticsearch _bulk API로 배치 인덱싱, 초당 수만 건 처리
시나리오 5: JSON 파싱·매핑 에러
상황: C++에서 JSON을 만들어 Elasticsearch에 전송했는데 400 Bad Request
문제: 날짜 형식 오류, 필드 타입 불일치, 매핑 미정의
결과: 매핑 사전 정의, 에러 응답 파싱으로 원인 파악
아래 코드는 mermaid를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
flowchart TB
subgraph 문제[실무 문제]
P1[느린 로그 검색] --> S1[역인덱스 전문 검색]
P2[복합 키워드 검색] --> S2[분석기·쿼리 DSL]
P3[실시간 집계] --> S3[집계 API]
P4[대량 인덱싱] --> S4[벌크 API]
end
이 글에서 다루는 것:
- Elasticsearch 핵심 개념 (인덱스, 도큐먼트, 역인덱스)
- 전문 검색 Query DSL 완전 예제
- 집계(Aggregation) 완전 예제
- 벌크 인덱싱·실시간 업데이트 패턴
- 자주 발생하는 에러와 해결법
- 성능 최적화·프로덕션 패턴 요구 환경: Elasticsearch 7.x/8.x, C++17 이상 (REST API 호출 시) 이 글을 읽으면:
- Elasticsearch API 구조를 이해하고 C++에서 REST로 호출할 준비가 됩니다.
- C++ Elasticsearch 완벽 가이드(#52-6)에서 libcurl·nlohmann/json으로 구현할 때 이 개념이 기반이 됩니다.
실무 적용 경험: 이 글은 대규모 C++ 프로젝트에서 실제로 겪은 문제와 해결 과정을 바탕으로 작성되었습니다. 책이나 문서에서 다루지 않는 실전 함정과 디버깅 팁을 포함합니다.
목차
- 기본 개념
- 인덱스 매핑과 문서
- 전문 검색 완전 예제
- 집계(Aggregation) 완전 예제
- 벌크 인덱싱·실시간 업데이트
- 자주 발생하는 에러와 해결법
- 성능 최적화
- 프로덕션 패턴
- 구현 체크리스트
- 정리
1. 기본 개념
Elasticsearch 아키텍처
Elasticsearch는 역인덱스(Inverted Index) 기반 검색 엔진입니다. 관계형 DB의 “행” 대신 도큐먼트(Document) 단위로 저장하고, 텍스트를 토큰으로 분해해 빠른 검색을 지원합니다. 다음은 mermaid를 활용한 상세한 구현 코드입니다. 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
flowchart TB
subgraph Input[입력]
D1[도큐먼트 1: "error timeout"]
D2[도큐먼트 2: "connection error"]
end
subgraph Analysis[분석기]
A[토크나이저·필터]
end
subgraph Index[역인덱스]
I1[error → doc1, doc2]
I2[timeout → doc1]
I3[connection → doc2]
end
D1 --> A
D2 --> A
A --> I1
A --> I2
A --> I3
핵심 용어
| 용어 | 설명 | 관계형 DB 대응 |
|---|---|---|
| 인덱스(Index) | 도큐먼트 모음 | 데이터베이스/테이블 |
| 도큐먼트(Document) | JSON 객체 | 행(Row) |
| 매핑(Mapping) | 필드 타입 정의 | 스키마 |
| 샤드(Shard) | 인덱스 분할 단위 | 파티션 |
| 역인덱스 | 토큰 → 도큐먼트 ID 매핑 | B-Tree 인덱스 |
REST API 기본 구조
C++에서 libcurl로 호출할 때 사용하는 엔드포인트 패턴입니다. 아래 코드는 text를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
# 기본 형식
PUT /<index>/_doc/<id> # 문서 인덱싱 (ID 지정)
POST /<index>/_doc # 문서 인덱싱 (ID 자동)
GET /<index>/_doc/<id> # 문서 조회
POST /<index>/_search # 검색
POST /_bulk # 벌크 작업
GET /_cluster/health # 클러스터 상태
인덱스 vs 관계형 DB 비교
다음은 mermaid를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
flowchart LR
subgraph RDB[관계형 DB]
T[(테이블)]
R[행]
I[B-Tree 인덱스]
end
subgraph ES[Elasticsearch]
IDX[(인덱스)]
DOC[도큐먼트]
INV[역인덱스]
end
T --> R
R --> I
IDX --> DOC
DOC --> INV
차이점:
- 스키마리스: 매핑 없이 도큐먼트를 넣으면 자동 추론 (동적 매핑)
- 전문 검색:
text타입은 토큰화되어 역인덱스에 저장 - 정확 일치:
keyword타입은 분석 없이 그대로 저장 (필터링·집계용)
2. 인덱스 매핑과 문서
매핑이 필요한 이유
동적 매핑에 의존하면 날짜 형식 오류, 숫자/문자열 혼동 등이 발생합니다. 로그·검색 시스템에서는 사전 매핑 정의를 권장합니다.
로그 인덱스 매핑 예제
다음은 json를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
PUT /logs
{
"settings": {
"number_of_shards": 1,
"number_of_replicas": 0,
"refresh_interval": "5s"
},
"mappings": {
"properties": {
"message": {
"type": "text",
"analyzer": "standard"
},
"level": {
"type": "keyword"
},
"service": {
"type": "keyword"
},
"timestamp": {
"type": "date",
"format": "strict_date_optional_time||epoch_millis"
},
"duration_ms": {
"type": "long"
},
"user_id": {
"type": "keyword"
}
}
}
}
필드 설명:
message: 전문 검색용,text타입 → 토큰화level,service: 필터·집계용,keyword→ 분석 없음timestamp:date타입, 정렬·날짜 히스토그램에 필수duration_ms: 숫자 집계(AVG, SUM)용
문서 인덱싱 예제
아래 코드는 json를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
PUT /logs/_doc/1
{
"message": "Application started successfully",
"level": "info",
"service": "api-gateway",
"timestamp": "2024-01-15T10:00:00.000Z",
"duration_ms": 0,
"user_id": null
}
아래 코드는 json를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
PUT /logs/_doc/2
{
"message": "Connection timeout to database",
"level": "error",
"service": "order-service",
"timestamp": "2024-01-15T10:01:23.456Z",
"duration_ms": 5000,
"user_id": "user-123"
}
C++에서 호출 시 참고
C++에서 위 요청을 보낼 때는 PUT 메서드와 JSON 본문을 그대로 사용합니다.
// libcurl 예시 (개념)
// PUT http://localhost:9200/logs/_doc/1
// Body: {"message":"Application started...", ...}
3. 전문 검색 완전 예제
Match Query: 기본 전문 검색
“timeout” 또는 “connection”이 포함된 로그 검색. 아래 코드는 json를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
POST /logs/_search
{
"query": {
"match": {
"message": "connection timeout"
}
},
"size": 10,
"from": 0,
"_source": ["message", "level", "timestamp"]
}
동작: message 필드가 standard 분석기로 토큰화되어 “connection”, “timeout” 각각 매칭. OR 조건 (기본).
Match Phrase: 구문 검색
정확한 구문 “connection timeout”을 찾을 때. 아래 코드는 json를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
POST /logs/_search
{
"query": {
"match_phrase": {
"message": "connection timeout"
}
}
}
Bool Query: 복합 조건
에러 레벨이면서 “timeout” 또는 “connection” 포함. 다음은 json를 활용한 상세한 구현 코드입니다. 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
POST /logs/_search
{
"query": {
"bool": {
"must": [
{
"term": {
"level": "error"
}
},
{
"match": {
"message": "timeout connection"
}
}
],
"filter": [
{
"range": {
"timestamp": {
"gte": "2024-01-15T00:00:00Z",
"lte": "2024-01-15T23:59:59Z"
}
}
}
]
}
},
"sort": [
{ "timestamp": "desc" }
],
"size": 20
}
구성:
must: 점수에 반영, 모두 만족filter: 점수 무관, 캐시 가능, 성능 좋음term:keyword필드 정확 일치range: 날짜·숫자 범위
Multi-Match: 여러 필드 검색
message와 service에서 동시 검색.
아래 코드는 json를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
POST /logs/_search
{
"query": {
"multi_match": {
"query": "api-gateway error",
"fields": ["message^2", "service"],
"type": "best_fields"
}
}
}
^2: message 필드에 가중치 2배.
Highlight: 검색어 하이라이트
다음은 json를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
POST /logs/_search
{
"query": {
"match": {
"message": "timeout"
}
},
"highlight": {
"fields": {
"message": {
"pre_tags": [<em>],
"post_tags": [</em>]
}
}
}
}
응답 예시: 아래 코드는 json를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
{
"hits": {
"hits": [
{
"_source": { "message": "Connection timeout to database" },
"highlight": {
"message": ["Connection <em>timeout</em> to database"]
}
}
]
}
}
4. 집계(Aggregation) 완전 예제
Terms Aggregation: 레벨별 건수
아래 코드는 json를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
POST /logs/_search
{
"size": 0,
"aggs": {
"by_level": {
"terms": {
"field": "level",
"size": 10,
"order": { "_count": "desc" }
}
}
}
}
응답: 아래 코드는 json를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
{
"aggregations": {
"by_level": {
"buckets": [
{ "key": "info", "doc_count": 150 },
{ "key": "error", "doc_count": 23 },
{ "key": "warn", "doc_count": 12 }
]
}
}
}
Date Histogram: 시간별 집계
1분 단위 로그 건수. 아래 코드는 json를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
POST /logs/_search
{
"size": 0,
"aggs": {
"over_time": {
"date_histogram": {
"field": "timestamp",
"calendar_interval": "1m",
"min_doc_count": 1
}
}
}
}
Stats Aggregation: 평균·합계·최소·최대
duration_ms 필드 통계.
아래 코드는 json를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
POST /logs/_search
{
"size": 0,
"aggs": {
"duration_stats": {
"stats": {
"field": "duration_ms"
}
}
}
}
응답: 아래 코드는 json를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
{
"aggregations": {
"duration_stats": {
"count": 1000,
"min": 5,
"max": 5000,
"avg": 234.5,
"sum": 234500
}
}
}
복합 집계: 서비스별·레벨별 + 평균 응답 시간
다음은 json를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
POST /logs/_search
{
"size": 0,
"aggs": {
"by_service": {
"terms": {
"field": "service",
"size": 5
},
"aggs": {
"by_level": {
"terms": {
"field": "level"
}
},
"avg_duration": {
"avg": {
"field": "duration_ms"
}
}
}
}
}
}
Percentiles: 응답 시간 백분위수
P95, P99 등 APM에서 자주 사용. 아래 코드는 json를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
POST /logs/_search
{
"size": 0,
"aggs": {
"latency_percentiles": {
"percentiles": {
"field": "duration_ms",
"percents": [50, 95, 99]
}
}
}
}
5. 벌크 인덱싱·실시간 업데이트
Bulk API 형식
한 줄에 메타데이터, 다음 줄에 본문. NDJSON(Newline Delimited JSON) 형식. 아래 코드는 json를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
POST /_bulk
{"index":{"_index":"logs","_id":"1"}}
{"message":"Log 1","level":"info","timestamp":"2024-01-15T10:00:00Z"}
{"index":{"_index":"logs","_id":"2"}}
{"message":"Log 2","level":"error","timestamp":"2024-01-15T10:01:00Z"}
{"index":{"_index":"logs"}}
{"message":"Log 3","level":"info","timestamp":"2024-01-15T10:02:00Z"}
메타데이터 액션:
index: 없으면 생성, 있으면 덮어쓰기create: 없을 때만 생성, 있으면 에러update: 부분 업데이트delete: 삭제 (본문 없음)
Bulk Update 예제
POST /_bulk
{"update":{"_index":"logs","_id":"1"}}
{"doc":{"level":"warn"},"doc_as_upsert":true}
doc_as_upsert: 없으면 doc 내용으로 새 문서 생성.
C++ 벌크 버퍼링 패턴
C++에서 로그를 수집해 1000건마다 한 번에 전송하는 패턴입니다. 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// 개념: 버퍼에 문서 추가
std::vector<std::string> buffer;
buffer.push_back(R"({"index":{"_index":"logs"}})");
buffer.push_back(R"({"message":"...","level":"info",...})");
// 1000건 도달 시
std::string bulk_body = join(buffer, "\n") + "\n";
// POST /_bulk
6. 자주 발생하는 에러와 해결법
에러 1: Connection refused
증상:
curl: (7) Failed to connect to localhost port 9200: Connection refused
원인:
- Elasticsearch 서버 미실행
- 잘못된 호스트/포트
- Docker 네트워크: 컨테이너 내부에서
localhost사용 시 호스트 ES에 연결 안 됨 해결법: 아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
# Elasticsearch 실행 확인
curl -X GET "http://localhost:9200/"
# Docker 내부에서 호스트 접근 (macOS/Windows)
# localhost 대신 host.docker.internal 사용
# Linux: --add-host=host.docker.internal:host-gateway
// C++에서: 환경 변수로 URL 외부화
std::string es_url = std::getenv("ELASTICSEARCH_URL");
// 기본값: "http://localhost:9200"
에러 2: mapper_parsing_exception
증상: 아래 코드는 json를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
{
"error": {
"type": "mapper_parsing_exception",
"reason": "failed to parse field [timestamp] of type [date]"
}
}
원인: 날짜 형식이 매핑과 맞지 않음. 해결법: 아래 코드는 json를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// ❌ 잘못된 형식
{"timestamp": "2024/01/15 10:00:00"}
// ✅ 올바른 형식 (ISO 8601)
{"timestamp": "2024-01-15T10:00:00.000Z"}
{"timestamp": "2024-01-15T10:00:00+09:00"}
{"timestamp": 1705312800000}
에러 3: illegal_argument_exception (text 필드 집계)
증상: 다음은 간단한 json 코드 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
{
"type": "illegal_argument_exception",
"reason": "Fielddata is disabled on text fields by default"
}
원인: text 타입 필드에 terms 집계 사용. text는 토큰화되어 집계에 부적합.
해결법:
- 집계용 필드는
keyword타입 사용 message에서 집계해야 하면message.keyword(keyword 서브필드) 사용 아래 코드는 json를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// 매핑에 keyword 서브필드 추가
"message": {
"type": "text",
"fields": {
"keyword": { "type": "keyword" }
}
}
// 집계 시
"terms": { "field": "message.keyword" }
에러 4: RequestTimeout / EsRejectedExecutionException
증상: 다음은 간단한 json 코드 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
{
"type": "request_timeout_exception",
"reason": "Bulk request timed out"
}
원인: 벌크 크기가 너무 크거나, 클러스터 부하로 처리 지연. 해결법:
- 벌크 배치 크기 축소 (1000~5000 권장)
timeout파라미터 증가 (기본 30초)
POST /_bulk?timeout=60s
// C++: 배치 크기 1000~5000으로 제한
const size_t BULK_BATCH_SIZE = 2000;
에러 5: 400 Bad Request - JSON 파싱 실패
증상: 아래 코드는 json를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
{
"error": {
"type": "json_parse_exception",
"reason": "Unexpected character..."
}
}
원인: JSON 이스케이프 누락, 줄바꿈·따옴표 미처리. 해결법: 아래 코드는 cpp를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// ❌ C++에서 잘못된 JSON
std::string doc = "{\"message\":\"error: \"timeout\"\"}"; // 따옴표 충돌
// ✅ nlohmann/json 사용
nlohmann::json j;
j[message] = "error: \"timeout\"";
std::string doc = j.dump();
에러 6: index_not_found_exception
증상: 다음은 간단한 json 코드 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
{
"type": "index_not_found_exception",
"reason": "no such index [logs]"
}
해결법: 인덱스 생성 후 사용. 또는 인덱스 존재 여부 확인. 아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
# 인덱스 목록 확인
curl -X GET "localhost:9200/_cat/indices?v"
# 인덱스 생성 (매핑 포함)
curl -X PUT "localhost:9200/logs" -H "Content-Type: application/json" -d @mapping.json
7. 성능 최적화
인덱싱 성능
| 항목 | 권장 | 비고 |
|---|---|---|
| 벌크 배치 크기 | 1000~5000 | 너무 크면 메모리·타임아웃 |
| refresh_interval | 5s~30s | 실시간 검색 필요 시 1s |
| number_of_shards | 노드 수 이하 | 과도한 샤드는 오버헤드 |
검색 성능
| 항목 | 권장 | 비고 |
|---|---|---|
| _source | 필요한 필드만 | _source: ["field1","field2"] |
| size | 기본 10, 필요 시 확대 | 10000 초과 시 스크롤 사용 |
| filter 컨텍스트 | 캐시 활용 | bool.filter 사용 |
| 스크롤 | 대용량 결과 | scroll 또는 search_after |
refresh_interval 조정
대량 인덱싱 시 검색 가능 시점을 늦추면 쓰기 성능 향상. 아래 코드는 json를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
PUT /logs/_settings
{
"index": {
"refresh_interval": "30s"
}
}
인덱싱 완료 후 원복: 아래 코드는 json를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
PUT /logs/_settings
{
"index": {
"refresh_interval": "1s"
}
}
스크롤 API (대용량 검색)
아래 코드는 json를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
POST /logs/_search?scroll=2m
{
"size": 1000,
"query": { "match_all": {} }
}
응답의 _scroll_id로 다음 배치 요청:
아래 코드는 json를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
POST /_search/scroll
{
"scroll": "2m",
"scroll_id": "<scroll_id>"
}
Search After (권장)
스크롤 대신 search_after로 페이지네이션. 7.10+ 권장.
아래 코드는 json를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
POST /logs/_search
{
"size": 100,
"query": { "match_all": {} },
"sort": [
{ "timestamp": "desc" },
{ "_id": "asc" }
],
"search_after": ["2024-01-15T10:00:00Z", "doc_id_123"]
}
8. 프로덕션 패턴
인덱스 라이프사이클 (ILM)
오래된 로그는 삭제하거나 콜드 스토리지로 이동. 다음은 json를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
PUT _ilm/policy/logs_policy
{
"policy": {
"phases": {
"hot": {
"actions": {
"rollover": {
"max_size": "50gb",
"max_age": "7d"
}
}
},
"delete": {
"min_age": "30d",
"actions": { "delete": {} }
}
}
}
}
인덱스 템플릿
새 인덱스 생성 시 자동으로 매핑·설정 적용. 다음은 json를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
PUT _index_template/logs_template
{
"index_patterns": [logs-*],
"template": {
"settings": {
"number_of_shards": 2,
"refresh_interval": "5s"
},
"mappings": {
"properties": {
"message": { "type": "text" },
"level": { "type": "keyword" },
"timestamp": { "type": "date" }
}
}
}
}
날짜 기반 인덱스
logs-2024-01-15 형태로 일별 인덱스. 삭제·압축 관리 용이.
아래 코드는 text를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
# 인덱스 명명 규칙
logs-2024-01-15
logs-2024-01-16
# 검색 시 와일드카드
POST /logs-*/_search
헬스 체크
C++ 서버 시작 시 Elasticsearch 연결 확인.
GET /_cluster/health?pretty
다음은 간단한 json 코드 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
{
"status": "green",
"number_of_nodes": 1
}
status: green(정상), yellow(레플리카 미할당), red(일부 샤드 미할당).
재시도 전략
아래 코드는 cpp를 사용한 구현 예제입니다. 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// 개념: 지수 백오프 재시도
int max_retries = 3;
for (int i = 0; i < max_retries; ++i) {
auto result = es_client.post("/logs/_doc", doc);
if (result.success) break;
if (result.status == 503 || result.status == 429) {
sleep(1 << i); // 1s, 2s, 4s
} else {
break; // 4xx 등은 재시도 무의미
}
}
환경 변수로 설정 외부화
ELASTICSEARCH_URL=http://es-host:9200
ELASTICSEARCH_TIMEOUT=30
ELASTICSEARCH_BULK_SIZE=2000
9. 구현 체크리스트
인덱스 설계
- 매핑 사전 정의 (text vs keyword, date 형식)
- refresh_interval 설정 (쓰기 부하에 따라)
- 날짜 기반 인덱스 또는 ILM 정책 검토
검색
-
_source필드 제한 (필요한 필드만) - size 제한 (기본 10, 최대 10000)
- 대용량 결과 시 스크롤 또는 Search After
에러 처리
- Connection refused 재시도
- 400/500 응답 파싱 및 로깅
- JSON 파싱 실패 처리
프로덕션
- 연결 재사용 (클라이언트 풀/싱글톤)
- 헬스 체크 (
/_cluster/health) - 환경 변수로 URL·타임아웃 외부화
- TLS/SSL (HTTPS)
10. 정리
| 항목 | 요약 |
|---|---|
| 역인덱스 | 토큰 → 도큐먼트 매핑, 전문 검색 핵심 |
| 매핑 | text(검색), keyword(필터·집계), date(정렬·히스토그램) |
| 검색 | match, match_phrase, bool, multi_match |
| 집계 | terms, date_histogram, stats, percentiles |
| 벌크 | NDJSON 형식, 배치 1000~5000 권장 |
| 에러 | mapper_parsing, text 필드 집계, RequestTimeout |
| 성능 | 벌크, _source 제한, refresh_interval, search_after |
| 프로덕션 | ILM, 인덱스 템플릿, 헬스 체크, 재시도 |
| 핵심 원칙: |
- 매핑 우선: 동적 매핑 의존 최소화
- 벌크 사용: 단건 인덱싱 대신 _bulk API
- 필드 타입 구분: text vs keyword, 집계 시 keyword
- 에러 파싱: HTTP 상태·JSON error.reason 확인
자주 묻는 질문 (FAQ)
Q. 이 내용을 실무에서 언제 쓰나요?
A. 로그 분석, 전문 검색, 실시간 대시보드, APM 시스템 등에 활용합니다. C++에서 Elasticsearch REST API를 호출하기 전에 이 글에서 개념과 API 구조를 익히면 C++ Elasticsearch 완벽 가이드(#52-6) 구현이 수월합니다.
Q. 선행으로 읽으면 좋은 글은?
A. 각 글 하단의 이전 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.