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
문제: 날짜 형식 오류, 필드 타입 불일치, 매핑 미정의
결과: 매핑 사전 정의, 에러 응답 파싱으로 원인 파악
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++ 프로젝트에서 실제로 겪은 문제와 해결 과정을 바탕으로 작성되었습니다. 책이나 문서에서 다루지 않는 실전 함정과 디버깅 팁을 포함합니다.
1. 기본 개념
Elasticsearch 아키텍처
Elasticsearch는 역인덱스(Inverted Index) 기반 검색 엔진입니다. 관계형 DB의 “행” 대신 도큐먼트(Document) 단위로 저장하고, 텍스트를 토큰으로 분해해 빠른 검색을 지원합니다.
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로 호출할 때 사용하는 엔드포인트 패턴입니다.
# 기본 형식
PUT /<index>/_doc/<id> # 문서 인덱싱 (ID 지정)
POST /<index>/_doc # 문서 인덱싱 (ID 자동)
GET /<index>/_doc/<id> # 문서 조회
POST /<index>/_search # 검색
POST /_bulk # 벌크 작업
GET /_cluster/health # 클러스터 상태
인덱스 vs 관계형 DB 비교
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. 인덱스 매핑과 문서
매핑이 필요한 이유
동적 매핑에 의존하면 날짜 형식 오류, 숫자/문자열 혼동 등이 발생합니다. 로그·검색 시스템에서는 사전 매핑 정의를 권장합니다.
로그 인덱스 매핑 예제
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)용
문서 인덱싱 예제
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
}
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”이 포함된 로그 검색.
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”을 찾을 때.
POST /logs/_search
{
"query": {
"match_phrase": {
"message": "connection timeout"
}
}
}
Bool Query: 복합 조건
에러 레벨이면서 “timeout” 또는 “connection” 포함.
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에서 동시 검색.
POST /logs/_search
{
"query": {
"multi_match": {
"query": "api-gateway error",
"fields": ["message^2", "service"],
"type": "best_fields"
}
}
}
^2: message 필드에 가중치 2배.
Highlight: 검색어 하이라이트
POST /logs/_search
{
"query": {
"match": {
"message": "timeout"
}
},
"highlight": {
"fields": {
"message": {
"pre_tags": [<em>],
"post_tags": [</em>]
}
}
}
}
응답 예시:
{
"hits": {
"hits": [
{
"_source": { "message": "Connection timeout to database" },
"highlight": {
"message": ["Connection <em>timeout</em> to database"]
}
}
]
}
}
4. 집계(Aggregation) 완전 예제
Terms Aggregation: 레벨별 건수
POST /logs/_search
{
"size": 0,
"aggs": {
"by_level": {
"terms": {
"field": "level",
"size": 10,
"order": { "_count": "desc" }
}
}
}
}
응답:
{
"aggregations": {
"by_level": {
"buckets": [
{ "key": "info", "doc_count": 150 },
{ "key": "error", "doc_count": 23 },
{ "key": "warn", "doc_count": 12 }
]
}
}
}
Date Histogram: 시간별 집계
1분 단위 로그 건수.
POST /logs/_search
{
"size": 0,
"aggs": {
"over_time": {
"date_histogram": {
"field": "timestamp",
"calendar_interval": "1m",
"min_doc_count": 1
}
}
}
}
Stats Aggregation: 평균·합계·최소·최대
duration_ms 필드 통계.
POST /logs/_search
{
"size": 0,
"aggs": {
"duration_stats": {
"stats": {
"field": "duration_ms"
}
}
}
}
응답:
{
"aggregations": {
"duration_stats": {
"count": 1000,
"min": 5,
"max": 5000,
"avg": 234.5,
"sum": 234500
}
}
}
복합 집계: 서비스별·레벨별 + 평균 응답 시간
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에서 자주 사용.
POST /logs/_search
{
"size": 0,
"aggs": {
"latency_percentiles": {
"percentiles": {
"field": "duration_ms",
"percents": [50, 95, 99]
}
}
}
}
5. 벌크 인덱싱·실시간 업데이트
Bulk API 형식
한 줄에 메타데이터, 다음 줄에 본문. NDJSON(Newline Delimited 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건마다 한 번에 전송하는 패턴입니다.
// 개념: 버퍼에 문서 추가
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에 연결 안 됨 해결법:
# 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
증상:
{
"error": {
"type": "mapper_parsing_exception",
"reason": "failed to parse field [timestamp] of type [date]"
}
}
원인: 날짜 형식이 매핑과 맞지 않음. 해결법:
// ❌ 잘못된 형식
{"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 필드 집계)
증상:
{
"type": "illegal_argument_exception",
"reason": "Fielddata is disabled on text fields by default"
}
원인: text 타입 필드에 terms 집계 사용. text는 토큰화되어 집계에 부적합.
해결법:
- 집계용 필드는
keyword타입 사용 message에서 집계해야 하면message.keyword(keyword 서브필드) 사용
// 매핑에 keyword 서브필드 추가
"message": {
"type": "text",
"fields": {
"keyword": { "type": "keyword" }
}
}
// 집계 시
"terms": { "field": "message.keyword" }
에러 4: RequestTimeout / EsRejectedExecutionException
증상:
{
"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 파싱 실패
증상:
{
"error": {
"type": "json_parse_exception",
"reason": "Unexpected character..."
}
}
원인: JSON 이스케이프 누락, 줄바꿈·따옴표 미처리. 해결법:
// ❌ 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
증상:
{
"type": "index_not_found_exception",
"reason": "no such index [logs]"
}
해결법: 인덱스 생성 후 사용. 또는 인덱스 존재 여부 확인.
# 인덱스 목록 확인
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 조정
대량 인덱싱 시 검색 가능 시점을 늦추면 쓰기 성능 향상.
PUT /logs/_settings
{
"index": {
"refresh_interval": "30s"
}
}
인덱싱 완료 후 원복:
PUT /logs/_settings
{
"index": {
"refresh_interval": "1s"
}
}
스크롤 API (대용량 검색)
POST /logs/_search?scroll=2m
{
"size": 1000,
"query": { "match_all": {} }
}
응답의 _scroll_id로 다음 배치 요청:
POST /_search/scroll
{
"scroll": "2m",
"scroll_id": "<scroll_id>"
}
Search After (권장)
스크롤 대신 search_after로 페이지네이션. 7.10+ 권장.
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)
오래된 로그는 삭제하거나 콜드 스토리지로 이동.
PUT _ilm/policy/logs_policy
{
"policy": {
"phases": {
"hot": {
"actions": {
"rollover": {
"max_size": "50gb",
"max_age": "7d"
}
}
},
"delete": {
"min_age": "30d",
"actions": { "delete": {} }
}
}
}
}
인덱스 템플릿
새 인덱스 생성 시 자동으로 매핑·설정 적용.
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 형태로 일별 인덱스. 삭제·압축 관리 용이.
# 인덱스 명명 규칙
logs-2024-01-15
logs-2024-01-16
# 검색 시 와일드카드
POST /logs-*/_search
헬스 체크
C++ 서버 시작 시 Elasticsearch 연결 확인.
GET /_cluster/health?pretty
{
"status": "green",
"number_of_nodes": 1
}
status: green(정상), yellow(레플리카 미할당), red(일부 샤드 미할당).
재시도 전략
// 개념: 지수 백오프 재시도
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++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.
Q. 더 깊이 공부하려면?
A. Elasticsearch 공식 문서, Query DSL, Aggregations 가이드를 참고하세요. C++에서는 REST API로 모든 기능을 호출합니다. 한 줄 요약: Elasticsearch 역인덱스·전문 검색·집계·벌크 인덱싱 개념을 익히면 C++에서 REST API 연동 시 설계와 에러 해결이 수월해집니다.
관련 글
심화 부록: 구현·운영 관점
이 부록은 앞선 본문에서 다룬 주제(「C++ Elasticsearch 통합 | 전문 검색·집계·실시간 인덱싱 [#52-5]」)를 구현·런타임·운영 관점에서 다시 압축합니다. 도메인별 세부 구현은 글마다 다르지만, 입력 검증 → 핵심 연산 → 부작용(I/O·네트워크·동시성) → 관측의 흐름으로 장애를 나누면 원인 추적이 빨라집니다.
내부 동작과 핵심 메커니즘
flowchart TD A[입력·요청·이벤트] --> B[파싱·검증·디코딩] B --> C[핵심 연산·상태 전이] C --> D[부작용: I/O·네트워크·동시성] D --> E[결과·관측·저장]
sequenceDiagram participant C as 클라이언트/호출자 participant B as 경계(런타임·게이트웨이·프로세스) participant D as 의존성(API·DB·큐·파일) C->>B: 요청/이벤트 B->>D: 조회·쓰기·RPC D-->>B: 지연·부분 실패·재시도 가능 B-->>C: 응답 또는 오류(코드·상관 ID)
- 불변 조건(Invariant): 버퍼 경계, 프로토콜 상태, 트랜잭션 격리, FD 상한 등 단계별로 문장으로 적어 두면 디버깅 비용이 줄어듭니다.
- 결정성: 순수 층과 시간·네트워크·스케줄에 의존하는 층을 분리해야 테스트와 장애 분석이 쉬워집니다.
- 경계 비용: 직렬화, 인코딩, syscall 횟수, 락 경합, 할당·GC, 캐시 미스를 의심 목록에 둡니다.
- 백프레셔: 생산자가 소비자보다 빠를 때 버퍼·큐·스트림에서 속도를 줄이는 신호를 어디에 둘지 정의합니다.
프로덕션 운영 패턴
| 영역 | 운영 관점 질문 |
|---|---|
| 관측성 | 요청 단위 상관 ID, 에러율·지연 p95/p99, 의존성 타임아웃·재시도가 대시보드에 보이는가 |
| 안전성 | 입력 검증·권한·비밀·감사 로그가 코드 경로마다 일관적인가 |
| 신뢰성 | 재시도는 멱등 연산에만 적용되는가, 서킷 브레이커·백오프·DLQ가 있는가 |
| 성능 | 캐시·배치 크기·커넥션 풀·인덱스·백프레셔가 데이터 규모에 맞는가 |
| 배포 | 롤백 룬북, 카나리/블루그린, 마이그레이션·피처 플래그가 문서화되어 있는가 |
| 용량 | 피크 트래픽·디스크·FD·스레드 풀 상한을 주기적으로 검증하는가 |
스테이징은 데이터 양·네트워크 RTT·동시성을 프로덕션에 가깝게 맞출수록 재현율이 올라갑니다.
확장 예시: 엔드투엔드 미니 시나리오
앞선 본문 주제(「C++ Elasticsearch 통합 | 전문 검색·집계·실시간 인덱싱 [#52-5]」)를 배포·운영 흐름에 맞춰 옮긴 체크리스트입니다. 도메인에 맞게 단계 이름만 바꿔 적용할 수 있습니다.
- 입력 계약 고정: 스키마·버전·최대 페이로드·타임아웃·에러 코드를 경계에 둔다.
- 핵심 경로 계측: 요청 ID, 단계별 지연, 외부 호출 결과 코드를 로그·메트릭·트레이스에서 한 흐름으로 본다.
- 실패 주입: 의존성 타임아웃·5xx·부분 데이터·락 대기를 스테이징에서 재현한다.
- 호환·롤백: 설정/마이그레이션/클라이언트 버전을 되돌릴 수 있는지 확인한다.
- 부하 후 검증: 피크 대비 p95/p99, 에러율, 리소스 상한, 알림 임계값을 점검한다.
handle(request):
ctx = newCorrelationId()
validated = validateSchema(request)
authorize(validated, ctx)
result = domainCore(validated)
persistOrEmit(result, idempotentKey)
recordMetrics(ctx, latency, outcome)
return result
문제 해결(Troubleshooting)
| 증상 | 가능 원인 | 조치 |
|---|---|---|
| 간헐적 실패 | 레이스, 타임아웃, 외부 의존성, DNS | 최소 재현 스크립트, 분산 트레이스·로그 상관관계, 재시도·서킷 설정 점검 |
| 성능 저하 | N+1, 동기 I/O, 락 경합, 과도한 직렬화, 캐시 미스 | 프로파일러·APM으로 핫스팟 확인 후 한 가지씩 제거 |
| 메모리 증가 | 캐시 무제한, 구독/리스너 누수, 대용량 버퍼, 커넥션 미반납 | 상한·TTL·힙/FD 스냅샷 비교 |
| 빌드·배포만 실패 | 환경 변수, 권한, 플랫폼 차이, lockfile | CI 로그와 로컬 diff, 런타임·이미지 버전 핀 |
| 설정 불일치 | 프로필·시크릿·기본값, 리전 | 스키마 검증된 설정 단일 소스와 배포 매트릭스 표준화 |
| 데이터 불일치 | 비멱등 재시도, 부분 쓰기, 캐시 무효화 누락 | 멱등 키·아웃박스·트랜잭션 경계 재검토 |
권장 순서: (1) 최소 재현 (2) 최근 변경 범위 축소 (3) 환경·의존성 차이 (4) 관측으로 가설 검증 (5) 수정 후 회귀·부하 테스트.
배포 전에는 git add → git commit → git push 후 npm run deploy 순서를 권장합니다.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ Elasticsearch 완전 실전 가이드 | Elasticlient·REST API
- C++ Elasticsearch 완벽 가이드 | Elasticlient·REST API
- C++ MongoDB 실전 완벽 가이드 | mongocxx CRUD·집계·인덱싱·레플리카셋·프로덕션
이 글에서 다루는 키워드 (관련 검색어)
C++, Elasticsearch, 검색, 집계, 인덱싱, 전문검색, 역인덱스 등으로 검색하시면 이 글이 도움이 됩니다.