08. 카드 결제 플랫폼 — data 필드 unindexed 환경의 KPI / 관측성 전략¶
컨텍스트: 카드사 앱의 FE/BE 플랫폼 운영. 모든 인입 전문이 ES 에 저장되지만
data.header,data.body의 내부 필드는 indexing 안 됨 → 검색·필터·집계 직접 불가. 목표: 이 제약 하에서 SRE / 개발 / 운영 3 관점의 KPI 와 관측성(Observability) 을 단계적으로 확보. 선수: 05-kpi-scenarios.md · 06-index-management.md · 07-batch-transform.md 소요: 통독 30분 + Phase 별 실행은 각각 별도
Executive Summary — 한 화면¶
flowchart LR
Now["📍 현재<br/>data.header/body<br/>indexing X<br/>→ 거래 데이터<br/>검색 불가능"] --> P1
subgraph "단계적 회복"
P1["Phase 1 (Day 1)<br/>top-level 만으로<br/>SRE 기본 KPI"] --> P2
P2["Phase 2 (Week 1)<br/>Runtime fields<br/>핵심 필드 검색<br/>(매번 계산, 느림 OK)"] --> P3
P3["Phase 3 (Month 1)<br/>Ingest pipeline<br/>신규 데이터부터<br/>top-level promote"] --> P4
P4["Phase 4 (Quarter)<br/>앱 표준화<br/>전송 시점 일관"]
end
P4 --> Done["✅ 풀 관측성<br/>SRE+개발+운영<br/>모든 KPI 확보"]
classDef bad fill:#ffcdd2,stroke:#c62828
classDef step1 fill:#fff9c4,stroke:#f57f17
classDef step2 fill:#bbdefb,stroke:#1976d2
classDef step3 fill:#c5e1a5,stroke:#558b2f
classDef step4 fill:#ce93d8,stroke:#6a1b9a
classDef good fill:#c8e6c9,stroke:#2e7d32
class Now bad
class P1 step1
class P2 step2
class P3 step3
class P4 step4
class Done good
핵심 메시지: - Phase 1 (오늘부터 가능) — top-level 필드만으로 SRE 60% 확보 - Phase 2 (1주 내) — Runtime fields 로 매핑 변경 없이 핵심 필드 검색 가능. 현재 데이터에도 즉시 적용 - Phase 3 (1개월 내) — Ingest pipeline 으로 신규 인덱스부터 자동 promote. 영구 해결의 시작 - Phase 4 (분기 내) — 앱 단 전송 표준화. 가장 깨끗하지만 협업 시간 큼
학습 로드맵¶
flowchart TD
A([시작]) --> B[1. 환경 진단]
B --> C[2. 가능 vs 불가능 매트릭스]
C --> D[3. Phase 1: Top-level 만으로]
D --> E[4. Phase 2: Runtime fields]
E --> F[5. Phase 3: Ingest pipeline]
F --> G[6. Phase 4: 앱 표준화]
G --> H[7. 카드사 painPoint 시나리오 6선]
H --> I[8. KPI 매트릭스 — 3관점 × 4 Phase]
I --> J[9. 마이그 로드맵 + 의사결정]
style D fill:#fff9c4
style E fill:#bbdefb
style F fill:#c5e1a5
style G fill:#ce93d8
style J fill:#c8e6c9
1. 환경 진단 — 운영 데이터 구조¶
1.1 전형적 인입 전문 구조 (예시)¶
```json { "@timestamp": "2026-04-26T12:40:21.000Z", "service_name": "card-payment", "api_path": "/api/v1/payments/charge", "http_method": "POST", "log_type": "out", "trace_id": "TRC000000123456", "elapsed_ms": 412, "host": { "name": "api-server-01", "ip": "10.0.1.101" },
"data": { ← ⚠️ data 이하 indexing X "header": { ← header 내부 필드도 unindexed "resultCode": "0000", "resultMsg": "정상처리", "channel": "MOBILE", "deviceType": "iOS", "userAgent": "CardApp/3.2.0" }, "body": { ← body 내부도 동일 "merchantId": "M00012345", "cardLast4": "1234", "amount": 35000, "installments": 0, "approvedAt": "2026-04-26T12:40:21.456Z", "approvalNumber": "12345678" } } } ```
1.2 ES 매핑 확인 (직접)¶
GET api-logs-*/_mapping/field/data.*
응답 예시:
json
{
"data": { "type": "object", "enabled": false } ← 이게 원인
}
또는 dynamic mapping 으로 keyword 만 깊은 경로에 자동 생성됐을 수도 있음. enabled: false 면 저장만 되고 indexing/search 모두 X.
1.3 가능 vs 불가능¶
flowchart LR
subgraph 가능["✅ 가능 — top-level 필드"]
A1[api_path]
A2[service_name]
A3[http_method]
A4[log_type]
A5[@timestamp]
A6[trace_id]
A7[elapsed_ms]
A8[host.name]
end
subgraph 불가능["❌ 불가능 — data 이하"]
B1[data.header.resultCode]
B2[data.header.channel]
B3[data.body.amount]
B4[data.body.merchantId]
B5[data.body.cardLast4]
end
style 가능 fill:#c8e6c9
style 불가능 fill:#ffcdd2
| 시도 | 가능? |
|---|---|
data.header.resultCode : "9999" 로 KQL 검색 |
❌ |
terms aggregation on data.body.amount |
❌ |
range query on data.body.amount > 100000 |
❌ |
count(api_path) (그냥 호출량) |
✅ |
percentile of elapsed_ms (latency p95) |
✅ |
histogram of @timestamp (트래픽 trend) |
✅ |
| 에러율 = 비-success 응답 비율 | ❌ (resultCode 가 data 안에 있음) |
| 결제 성공률 = 비즈니스 KPI | ❌ |
📌 충격적 결론: SRE 의 가장 기본 KPI 인 에러율 도 직접은 못 함. 이게 본 문서의 출발점.
2. KPI 매트릭스 — 3관점 × 4 Phase¶
| KPI / 관측 | Phase 1 (top-level) |
Phase 2 (Runtime field) |
Phase 3 (Ingest pipeline) |
Phase 4 (App 표준화) |
|---|---|---|---|---|
| 🛠️ SRE — 가용성/SLO | ❌ | ✅ (느림) | ✅✅ | ✅✅ |
| 🛠️ SRE — Latency p95/p99 | ✅ | ✅ | ✅ | ✅ |
| 🛠️ SRE — 트래픽 anomaly | ✅ | ✅ | ✅ | ✅ |
| 🛠️ SRE — 에러 spike alert | ❌ | ✅ (느림) | ✅✅ | ✅✅ |
| 🔧 개발 — Dead/Shadow API | ✅ | ✅ | ✅ | ✅ |
| 🔧 개발 — 배포 회귀 감지 | △ (호출량/lat 만) | ✅ | ✅ | ✅ |
| 🔧 개발 — 신규 API 안정성 | △ | ✅ | ✅ | ✅ |
| 📊 운영 — 시간대 패턴 | ✅ | ✅ | ✅ | ✅ |
| 📊 운영 — 결제 성공률 | ❌ | ✅ (느림) | ✅✅ | ✅✅ |
| 📊 운영 — 채널별 분포 (MOBILE/PC) | ❌ | ✅ (느림) | ✅✅ | ✅✅ |
| 📊 운영 — 거래액 통계 (sum/avg) | ❌ | △ (script_score) | ✅✅ | ✅✅ |
| 📊 운영 — 가맹점 Top N | ❌ | △ | ✅ | ✅ |
| 📊 운영 — 사용자 retry/이탈 | ❌ | △ | ✅ | ✅ |
✅✅ = 빠르고 자연. ✅ = 가능 + 운영 부담 적음. △ = 가능하지만 제한 (느림/script). ❌ = 불가능.
한눈에: Phase 2 부터 기본기는 갖추고, Phase 3 부터 운영 dashboard 가 진짜 "쓸 만" 해진다.
3. Phase 1 — Top-level 만으로 (Day 1)¶
3.0 ⭐ 먼저 확인: top-level 에러 플래그 존재 여부¶
본격적으로 들어가기 전 5분 사전 점검. _source 의 top-level (data 와 동일 깊이) 에 isError / errorYn / error / hasError 같은 에러 플래그가 indexed 되어 있다면 Phase 2 까지 안 가도 에러 KPI 즉시 가능.
빠른 확인 (Dev Tools)¶
GET api-logs-*/_field_caps?fields=isError,errorYn,error,hasError,error_yn
또는 패턴 매칭:
GET api-logs-*/_mapping/field/*error*
판정:
| _field_caps 응답 | 결론 | 다음 단계 |
|---|---|---|
| searchable: true, aggregatable: true | ✅ 완전 indexed | §3.0.1 즉시 활용 — Phase 2 안 가도 됨 |
| searchable: true, aggregatable: false | △ text 만 | .keyword 시도 (다음 §3.0.2) |
| searchable: false 또는 응답 없음 | ❌ unindexed | Phase 2 (Runtime field) 필요 — §4 로 |
📌 Q&A 참고: 자세한 진단 4단계는 99-qna.md Q-01 참고.
3.0.1 indexed 면 — 즉시 KPI 확보¶
KQL: isError : true ← 모든 에러
KQL: not isError : true ← 정상만
Lens Formula: count(kql='isError:true')
/ count(kql='log_type:"out"') ← 에러율 %
이 한 줄로 SRE 4 golden signals 의 Errors 채움. 단계 도약: Phase 1 → Phase 1.5.
3.0.2 text 만 (aggregatable X) 면¶
GET api-logs-*/_field_caps?fields=isError.keyword
→ .keyword 보조 매핑 있으면 그걸로:
KQL: isError.keyword : "true"
3.0.3 unindexed 면¶
§4 (Phase 2 Runtime fields) 로. isError 도 함께 runtime 으로 정의:
json
PUT /api-logs-*/_mapping
{
"runtime": {
"is_error": {
"type": "boolean",
"script": { "source": """
if (params._source.isError != null) emit((boolean)params._source.isError);
""" }
}
}
}
⚠️ 한계 — indexed 라도 Phase 2+ 가 여전히 필요한 부분¶
isError 만으로는 다음 못 함:
- 어떤 에러 코드 가 많은가 (data.header.resultCode)
- 채널/가맹점/거래액 별 분포
- 에러 발생 trace_id 와 함께 메시지 추적
→ 운영 1차 신호 (얼마나 깨졌나) 는 §3.0.1 로 OK, 진단 깊이 (왜) 는 Phase 2+ 필요.
3.1 가능한 KPI 7종¶
A. 트래픽 (Traffic)¶
KQL: log_type : "out"
Lens: count(records) over @timestamp, breakdown by service_name
B. Latency (Golden signal)¶
KQL: log_type : "out"
Lens: percentiles(elapsed_ms, [50, 95, 99]) by api_path
C. API 호출 빈도 (Top N)¶
Lens: count(records) by api_path, Top 20
D. 시간대 패턴 (heatmap)¶
Lens: count(records) by @timestamp(hour) × @timestamp(day)
E. Dead API¶
Swagger 선언 paths - ES 호출 paths = Dead
F. Shadow API (문서 누락)¶
ES 호출 paths - Swagger 선언 paths = Shadow
G. 호스트별 부하¶
Lens: count(records) by host.name
3.2 Phase 1 의 한계¶
❌ "이번 주 결제 거절이 늘었나?" — resultCode 못 봄
❌ "어떤 에러 코드가 가장 많은가?" — 불가
❌ "MOBILE vs PC 채널 비중" — 불가
❌ "10만원 이상 결제 추세" — body.amount 못 봄
SRE 정의 (Google 의 4 golden signals): - Latency ✅ - Traffic ✅ - Errors ❌ ← 이게 안 되는 게 치명적 - Saturation (별도 metricbeat) — 본 환경 X
→ 에러율 못 보면 운영 못함. 즉시 Phase 2 로.
3.3 Phase 1 dashboard (D5)¶
┌─────────────────────────────────────────────────────────┐
│ 🕒 [Last 24h ▼] 🔎 [filter] │
├─────────────────────────────────────────────────────────┤
│ ┌─────┬─────┬─────┬─────┐ │
│ │호출수│ p95 │APIN │호스트│ ← KPI 4 (Phase 1 가능 항목) │
│ │ 9.2M│742ms│ 39 │ 4 │ │
│ └─────┴─────┴─────┴─────┘ │
├─────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────────────────────────────┐ │
│ │📈 트래픽 │ │⏱️ Top API p95 latency │ │
│ └─────────────┘ └─────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────┤
│ ┌─────────────────────────────────────────────────────┐ │
│ │🔥 시간대 heatmap │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
에러 KPI 가 빠진 dashboard 는 운영자에게 절반의 정보. Phase 2 가 필수.
4. Phase 2 — Runtime Fields (Week 1)¶
4.1 Runtime field 가 무엇인가¶
Search-time 에 정의되는 가상 필드. 매핑은 안 바뀌고, 쿼리할 때 _source 에서 값을 추출해 즉석 계산.
flowchart LR
Q[Query] --> RT["Runtime field<br/>script 실행"]
RT --> S["_source 파싱<br/>data.header.resultCode 추출"]
S --> Res[필드처럼 사용]
Res --> KQL[KQL 검색 ✅]
Res --> Agg[Aggregation ✅]
Res --> Sort[정렬 ✅]
style RT fill:#fff9c4
장점: - ✅ 매핑 변경 / reindex 불필요 → 현재 모든 데이터에 즉시 적용 - ✅ 영구 promote 결정 전 시범 단계로 완벽 - ✅ 매핑/index template 변경 권한 없어도 가능
단점: - ⚠️ 매 쿼리마다 _source 파싱 → 인덱싱된 필드보다 10~100x 느림 - ⚠️ doc_values 없어 정렬/aggregation 메모리 부담 - ⚠️ 캐싱 부족
→ PoC + 단기 운영용. 장기 데이터셋엔 Phase 3 로.
4.2 카드 결제 플랫폼 핵심 필드 promote — Runtime 정의¶
한 번에 정의 (Dev Tools)¶
json
PUT /api-logs-*/_mapping
{
"runtime": {
"result_code": {
"type": "keyword",
"script": {
"source": """
def hdr = params._source.data?.header;
if (hdr != null && hdr.resultCode != null) {
emit(hdr.resultCode.toString());
}
"""
}
},
"result_msg": {
"type": "keyword",
"script": { "source": """
def hdr = params._source.data?.header;
if (hdr != null && hdr.resultMsg != null) emit(hdr.resultMsg.toString());
""" }
},
"channel": {
"type": "keyword",
"script": { "source": """
def hdr = params._source.data?.header;
if (hdr != null && hdr.channel != null) emit(hdr.channel.toString());
""" }
},
"device_type": {
"type": "keyword",
"script": { "source": """
def hdr = params._source.data?.header;
if (hdr != null && hdr.deviceType != null) emit(hdr.deviceType.toString());
""" }
},
"amount": {
"type": "long",
"script": { "source": """
def body = params._source.data?.body;
if (body != null && body.amount != null) emit(((Number)body.amount).longValue());
""" }
},
"merchant_id": {
"type": "keyword",
"script": { "source": """
def body = params._source.data?.body;
if (body != null && body.merchantId != null) emit(body.merchantId.toString());
""" }
}
}
}
📌 포인트:
- params._source.data?.header — null-safe navigation
- emit() — 값 노출 (검색·정렬·집계 가능)
- type 명시 — keyword / long / double / date / boolean
4.3 즉시 효과 — KQL 한 줄로 에러 검색¶
result_code : "9999" ← 이전엔 불가능
not result_code : "0000" ← 모든 에러
channel : "MOBILE" and amount > 100000 ← 비즈니스 검색
result_code : ("E001" or "P001" or "9999") ← 다중 에러
4.4 에러율 KPI 즉시 확보¶
Lens Formula:
count(kql='log_type:"out" and not result_code:"0000"')
/ count(kql='log_type:"out"')
→ 이제 SRE 4 golden signals 의 Errors 가 채워짐.
4.5 운영 시 주의¶
4.5.1 성능 측정¶
json
GET api-logs-*/_search
{
"size": 0,
"query": { "term": { "result_code": "9999" } },
"profile": true
}
응답의 profile 섹션에서 script 시간 확인. 1000ms+ 면 부담.
4.5.2 큰 인덱스에선 fields parameter 활용¶
json
GET api-logs-*/_search
{
"size": 10,
"fields": ["result_code", "channel", "amount"],
"_source": false,
"query": { "term": { "service_name": "card-payment" } }
}
→ runtime field 만 받고 _source 전체는 안 받기 → 네트워크 절감.
4.5.3 limited time window 으로 부담 분산¶
7일치 전부 query 하지 말고 1일/시간 단위로 좁혀서 사용. 임시 대시보드는 OK, 상시 모니터링은 Phase 3 로 옮기기.
4.6 Kibana Discover / Lens 에서 사용¶
Discover¶
Data view 화면에서 Add field → "Runtime field" → 위 script 입력. 세션 한정 runtime field 만들 수도 있고, 인덱스 mapping 에 영구 등록 도 가능 (위 PUT 처럼).
Lens¶
위에서 정의한 runtime fields 가 자동으로 Lens 의 필드 목록에 등장. drag&drop 그대로.
4.7 D5 dashboard 보강 (Phase 2)¶
기존 4개 KPI 에 추가:
┌─────┬─────┬─────┬─────┐
│호출수│에러율│ p95 │채널 │ ← 에러율과 채널이 추가됨
│ 9.2M│ 17%🔴│742ms│MOB75%│
└─────┴─────┴─────┴─────┘
추가 패널: - Top 에러 코드 (B7 패턴) - 채널별 분포 (MOBILE/PC/WEB donut) - 거래액 분포 histogram
5. Phase 3 — Ingest Pipeline (Month 1)¶
5.1 Ingest Pipeline 이 무엇인가¶
ES 가 doc 을 받기 전에 수행하는 변환 chain. 매 doc 의 _source 를 읽어 새 필드 생성·기존 필드 변환·삭제 가능.
flowchart LR
App[App / Logstash] --> ES_in["ES 수신"]
ES_in --> PL["Ingest Pipeline<br/>processors 실행"]
PL --> Set["set processor:<br/>data.header.resultCode<br/>→ result_code"]
Set --> Set2["set processor:<br/>data.body.amount<br/>→ amount"]
Set2 --> Idx[(Index<br/>top-level 매핑됨)]
Idx --> Search["일반 검색·집계 ✅<br/>(빠름)"]
style PL fill:#fff9c4
style Search fill:#c8e6c9
장점: - ✅ 신규 데이터부터 top-level 영구 필드 → 빠른 검색·집계 - ✅ doc_values 활성화 (정렬·aggregation 비용 ↓) - ✅ Kibana 의 모든 기능 (Lens / Alerts / Anomaly) 사용 가능
단점: - ⚠️ 신규 데이터부터 적용 — 과거는 reindex 별도 필요 - ⚠️ 인덱스 매핑 변경 권한 필요 - ⚠️ pipeline 변경 시 신규 인덱스 생성 시점부터 반영
5.2 Pipeline 정의¶
json
PUT /_ingest/pipeline/card-promote-fields
{
"description": "카드 결제 플랫폼: data.header/body 핵심 필드를 top-level 로 promote",
"processors": [
{
"set": {
"field": "result_code",
"value": "{{data.header.resultCode}}",
"if": "ctx?.data?.header?.resultCode != null",
"ignore_failure": true
}
},
{
"set": {
"field": "result_msg",
"value": "{{data.header.resultMsg}}",
"if": "ctx?.data?.header?.resultMsg != null",
"ignore_failure": true
}
},
{
"set": {
"field": "channel",
"value": "{{data.header.channel}}",
"if": "ctx?.data?.header?.channel != null",
"ignore_failure": true
}
},
{
"set": {
"field": "device_type",
"value": "{{data.header.deviceType}}",
"if": "ctx?.data?.header?.deviceType != null",
"ignore_failure": true
}
},
{
"set": {
"field": "amount",
"value": "{{data.body.amount}}",
"if": "ctx?.data?.body?.amount != null",
"ignore_failure": true
}
},
{
"set": {
"field": "merchant_id",
"value": "{{data.body.merchantId}}",
"if": "ctx?.data?.body?.merchantId != null",
"ignore_failure": true
}
},
{
"convert": {
"field": "amount",
"type": "long",
"ignore_missing": true
}
},
{
"set": {
"field": "is_error",
"value": true,
"if": "ctx?.result_code != null && ctx.result_code != '0000'"
}
}
],
"on_failure": [
{
"set": {
"field": "_ingest.pipeline_error",
"value": "{{ _ingest.on_failure_message }}"
}
}
]
}
📌 on_failure: pipeline 실패 시 doc 자체는 살리고 에러 로그 필드 추가. 운영 안전.
5.3 Index Template 에 default pipeline 등록¶
json
PUT /_index_template/api-logs-template
{
"index_patterns": ["api-logs-*"],
"priority": 200,
"template": {
"settings": {
"index.default_pipeline": "card-promote-fields"
},
"mappings": {
"properties": {
"result_code": { "type": "keyword" },
"result_msg": { "type": "keyword" },
"channel": { "type": "keyword" },
"device_type": { "type": "keyword" },
"amount": { "type": "long" },
"merchant_id": { "type": "keyword" },
"is_error": { "type": "boolean" }
}
}
}
}
→ 이후 생성되는 신규 인덱스 (api-logs-2026.04.27 등) 부터 자동 적용.
5.4 검증¶
새 doc 인덱싱 후 매핑 확인¶
GET api-logs-2026.04.27/_mapping/field/result_code
응답:
json
{ "result_code": { "type": "keyword" } }
검색 (인덱싱된 필드 — 빠름)¶
GET api-logs-2026.04.27/_search
{
"query": { "term": { "result_code": "9999" } }
}
→ Phase 2 의 runtime field 와 결과 동일하지만 수십 배 빠름.
5.5 과거 데이터 reindex (선택)¶
신규 데이터부터만 적용되므로, 과거 데이터도 같이 보고 싶으면 reindex with pipeline:
json
POST _reindex?wait_for_completion=false
{
"source": { "index": "api-logs-2026.04.20" },
"dest": {
"index": "api-logs-2026.04.20-v2",
"pipeline": "card-promote-fields"
}
}
각 일자별 또는 7일치 한꺼번에 (대용량은 chunk 분할 권장).
flowchart LR
Old["api-logs-2026.04.20<br/>(unindexed)"] --> Reindex["_reindex<br/>+ pipeline"]
Reindex --> New["api-logs-2026.04.20-v2<br/>(indexed)"]
New --> Alias["alias 전환:<br/>api-logs-current"]
Alias --> App[App / Kibana]
Old -.delete after.-> X["삭제"]
style Reindex fill:#fff9c4
style New fill:#c8e6c9
📌 운영 안전: alias 패턴으로 zero-downtime. 과거 reindex 는 한가한 시간대에 천천히.
5.6 Phase 3 의 새로운 KPI¶
이제 영구 필드라서: - Alerts 가능 — "5분간 result_code != 0000 이 100건 초과" rule - Anomaly detection — channel 별 / merchant_id 별 ML 룰 - Transform — 일일 통계 (07-batch-transform.md) 가 모두 가능
→ Phase 3 부터 진짜 production observability.
6. Phase 4 — App 표준화 (Quarter 1)¶
6.1 왜 또 한 단계?¶
Phase 3 도 좋지만: - pipeline 이 복잡해질수록 운영 부담 ↑ - 새 필드 추가할 때마다 pipeline 갱신 - 다른 팀이 도입 시 동일 처리 또 함
가장 깨끗한 답: App 단에서 처음부터 top-level 로 보낸다.
6.2 표준 schema 합의 (예시)¶
```json { "@timestamp": "...", "service_name": "...", "api_path": "...", "http_method": "...", "log_type": "in" | "out", "trace_id": "...", "elapsed_ms": 412,
// ── 표준 promoted 필드 ── "result_code": "0000", ← 항상 top-level "result_msg": "...", "channel": "MOBILE", "device_type": "iOS",
// ── 비즈니스 필드 (선택) ── "amount": 35000, "merchant_id": "...", "card_last4": "1234", "user_id_hashed": "...", ← PII 는 hash
// ── 원본 (감사·법적 요구 대비) ── "data": { "header": {...}, "body": {...} } } ```
6.3 협업 흐름¶
flowchart LR
Spec["1. 플랫폼팀:<br/>표준 schema 정의<br/>+ wiki 문서"]
Spec --> Lib["2. 공통 logger 라이브러리<br/>(카드 결제 플랫폼 SDK)<br/>핵심 필드 자동 promote"]
Lib --> Adopt["3. 각 서비스 팀:<br/>SDK 도입"]
Adopt --> Verify["4. 신규 인덱스에<br/>top-level 필드 도착<br/>검증"]
Verify --> Deprecate["5. ingest pipeline<br/>점진적 deprecate<br/>(Phase 3 → 백업)"]
style Spec fill:#fff9c4
style Verify fill:#c8e6c9
6.4 의사결정 — Phase 3 만으로 끝낼까, Phase 4 까지 갈까?¶
| 시나리오 | 권장 |
|---|---|
| 단일 서비스 / 작은 팀 | Phase 3 으로 충분 |
| 다중 서비스 / 표준화 가치 큼 | Phase 4 가 ROI 좋음 |
| 외부 의존 (Logstash 등) | Phase 4 어려움, Phase 3 유지 |
| schema 자주 바뀜 | Phase 4 가 결국 답 |
7. 카드사 painPoint 시나리오 6선¶
각 시나리오: 어느 Phase 에서 가능한지 + 활용 KQL/DSL.
7.1 결제 거절 spike (운영 painPoint)¶
상황: "오후 2시부터 결제 거절(P001 한도초과 / P002 정지카드) 이 평소의 3배"
Phase 1: ❌ 불가능 (resultCode 못 봄, 호출량은 정상으로 보임)
Phase 2+: ✅
KQL: api_path : "/api/v1/payments/charge" and result_code : ("P001" or "P002")
시간 피커: Last 4 hours
가치 환산: 거절 1건 평균 30K원 × 추가 거절 N건 = 시간당 손실 액수
7.2 채널별 경험 차이 (사용자 painPoint)¶
상황: "MOBILE 사용자가 PC 보다 인증 실패율이 2배 높다"
Phase 1: ❌ (channel 못 봄)
Phase 2+:
log_type : "out" and api_path : "/api/v1/users/auth"
→ Lens 차트, Vertical = count() by result_code, Breakdown = channel
가치 환산: MOBILE 사용자 N명 × 인증 실패 retry 평균 3회 = 사용자 시간 손실
7.3 가맹점별 거래량 (비즈니스 painPoint)¶
상황: "어떤 가맹점이 가장 많은 거래?" 또는 "특정 가맹점에서 갑자기 거래 0"
Phase 1: ❌
Phase 2: △ (느림, 작은 시간대만)
Phase 3+: ✅
Lens: count() by merchant_id, Top 20
또는: Alert "merchant_id : <key> 의 거래가 30분간 0"
7.4 거래액 분포 (비즈니스)¶
상황: "10만원 이상 결제가 늘었는지"
Phase 1: ❌
Phase 2: △
Phase 3+:
KQL: amount >= 100000
→ Lens histogram of amount, breakdown by 시간
7.5 신규 배포 회귀 (개발 painPoint)¶
상황: "오늘 14:00 배포 후 에러율 회귀"
Phase 1: 호출량과 latency 만 비교 가능 — 에러율은 못 봄
Phase 2+: 완전한 회귀 감지
시간 피커: 어제 13~17 vs 오늘 13~17
KQL: not result_code : "0000"
Lens timeshift 로 비교
7.6 사용자 retry 후 이탈 (UX painPoint)¶
상황: "결제 실패 본 사용자가 30분 내 재시도 후 이탈하는 비율"
Phase 1, 2: 모두 어려움 (user_id 가 data.body 안) Phase 3+: user_id 도 promote 했으면 가능
KQL: not result_code : "0000" and user_id_hashed : *
Lens: count distinct user_id_hashed by hour
+ 사후 application 분석 (또는 transform 으로)
8. 카드 결제 플랫폼 권장 dashboard 5종¶
D-CP1. 카드사앱 — SRE 운영 현황 (운영자, 매시간)¶
- KPI: 가용성 / 에러율 / p95 latency / 활성 API
- 트래픽 trend + 에러율 trend + Top 에러 코드
- 선결조건: Phase 2+
D-CP2. 결제 거래 KPI (운영자, 일/주)¶
- KPI: 결제 성공률 / 평균 거래액 / 일 거래량 / 가맹점 수
- 시간대별 결제 분포 + 거래액 histogram + 채널별 비중
- 선결조건: Phase 3+
D-CP3. 인증/세션 (보안+운영)¶
- KPI: 인증 성공률 / 분당 시도 횟수 / 잠금 카운트
- 시간대 패턴 + 비정상 시간대 시도 alert
- 선결조건: Phase 2+
D-CP4. 신규 배포 모니터링 (개발자, 배포 day)¶
- 배포 시각 vertical line
- 에러율 / latency / 호출량 그 전후 비교 (timeshift)
- 선결조건: Phase 2+ (에러율)
D-CP5. 비즈니스 KPI (PM, 주/월)¶
- 거래액 누적 / Top 가맹점 / 이체 vs 결제 비중
- DAU 추이 (user_id_hashed cardinality)
- 선결조건: Phase 3+
9. 마이그레이션 로드맵 — 의사결정 가이드¶
flowchart TD
Start[현재: Phase 0<br/>data.* unindexed] --> Q1{시급한가?}
Q1 -->|운영 사고 가능성| Now[즉시 Phase 1+2<br/>병행 시작]
Q1 -->|여유 있음| Plan[Phase 1 먼저]
Now --> P12[1주: Phase 1 dashboard<br/>+ Phase 2 runtime fields]
P12 --> Q2{Phase 2 부담?}
Q2 -->|"검색 빈도 ↑"| P3[1개월: Phase 3<br/>Ingest pipeline]
Q2 -->|"가끔만 사용"| Stay2[Phase 2 로 운영]
P3 --> Q3{표준화 가치?}
Q3 -->|다중 팀| P4[1분기: Phase 4<br/>App 표준화]
Q3 -->|단일 팀| Stay3[Phase 3 로 운영]
Plan --> P12
style Now fill:#ffcdd2
style P12 fill:#fff9c4
style P3 fill:#c5e1a5
style P4 fill:#ce93d8
시간대별 산출물¶
| Phase | 기간 | 산출물 | 운영 효과 |
|---|---|---|---|
| 1 | Day 1~3 | top-level dashboard 1개 | 트래픽/latency/Dead API 감지 |
| 2 | Day 4~14 | runtime fields 6~10개 등록 | 에러 검색 가능, alert 설정 |
| 3 | Week 3~6 | ingest pipeline + new template | 신규 인덱스 자동 promote, 빠른 검색 |
| 3.5 | Week 4~8 | 과거 인덱스 reindex (선택) | 과거 데이터까지 통합 |
| 4 | Month 3~6 | App SDK + 표준 schema | 영구 깨끗한 매핑 |
단계별 ROI¶
flowchart LR
P1["Phase 1<br/>비용: 1일<br/>가치: 30%"]
P2["Phase 2<br/>비용: 1주<br/>누적 가치: 70%"]
P3["Phase 3<br/>비용: 1개월<br/>누적 가치: 90%"]
P4["Phase 4<br/>비용: 1분기<br/>누적 가치: 100%"]
P1 --> P2 --> P3 --> P4
style P1 fill:#fff9c4
style P2 fill:#bbdefb
style P3 fill:#c5e1a5
style P4 fill:#ce93d8
📌 추천 stop point: 대부분 팀은 Phase 3 까지 가 ROI 최대. Phase 4 는 다중 서비스 표준화 가치 큰 곳만.
10. 폐쇄망 / 사내 적용 시 추가 고려¶
10.1 권한 분리¶
| 작업 | 권한 |
|---|---|
| Runtime field 등록 | manage index privilege |
| Ingest pipeline 등록 | cluster: manage_pipeline |
| Index template 변경 | cluster: manage_index_templates |
| Reindex | read source + write dest |
→ DBA / 플랫폼 팀과 협업 필요. 미리 권한 단계별 합의.
10.2 사내 표준 logger SDK¶
카드 결제 플랫폼의 공통 logger 라이브러리에 promote 로직 박는 것 (Phase 4):
java
// Java 예시
NhPayLogger.builder()
.field("result_code", header.getResultCode()) // top-level
.field("amount", body.getAmount()) // top-level
.data(header, body) // 원본 보존
.send();
JSON 직렬화 시 자동으로 위 schema 따라감. 모든 카드 결제 플랫폼 서비스에 한 번 도입.
10.3 ES 9 마이그 시 영향¶
이 전략은 ES 8 / 9 모두 동일. Runtime field 와 ingest pipeline 은 양쪽 모두 안정 지원.
단 ES 9 의
semantic_text가 도입되면 자연어 검색 (예: 에러 메시지 텍스트 매칭) 도 가능 — 별도 활용.
11. ✅ 단계별 체크리스트¶
Phase 1 (Day 1)¶
- 현재 매핑 확인 (
GET /api-logs-*/_mapping) - D5 dashboard 만들기 (top-level KPI 4 + 트래픽 + latency + heatmap)
- Dead/Shadow API 1회 분석
Phase 2 (Week 1)¶
- Runtime field 6개 등록 (result_code, result_msg, channel, device_type, amount, merchant_id)
- D5 dashboard 에 에러 패널 3개 추가
- 에러 spike alert rule 1개 등록 (04-alerts.md 참고)
- 성능 측정 — slow query 가 있나 확인
Phase 3 (Month 1)¶
- Ingest pipeline
card-promote-fields등록 - Index template default_pipeline 적용
- 신규 인덱스 1일 후 top-level 매핑 검증
- (선택) 과거 인덱스 reindex 1주 분량
- Transform 으로 일일 KPI 통계 (07-batch-transform.md)
Phase 4 (Quarter 1)¶
- 표준 schema 문서화 + 팀 합의
- 공통 logger SDK 1차 버전
- 1개 서비스 파일럿 도입
- 전사 롤아웃 계획
❓ Self-check¶
-
Q. Phase 2 (Runtime field) 와 Phase 3 (Ingest pipeline) 의 본질 차이는?
A
Runtime field 는 **search-time**, ingest pipeline 은 **index-time**. 전자는 매핑 안 바꾸고 즉시 적용, 매번 _source 파싱 → 느림. 후자는 처음부터 top-level 매핑 → 빠르지만 신규 데이터부터. -
Q. Phase 2 의 runtime field 는 영구 등록 vs 세션 한정 어느 게 좋을까?
A
운영 표준 KPI 면 **mapping API 로 영구 등록** (`PUT /api-logs-*/_mapping`). 일회성 분석은 Discover 의 세션 limited runtime field. 영구 등록도 매핑 변경에 가깝지 않아 안전. -
Q. Phase 3 도입 후 과거 데이터 reindex 안 하면?
A
새 인덱스부터만 KPI 가능. 과거 7일치 dashboard 는 여전히 unindexed. 보통 (1) 신규부터만 운영 + 과거는 Phase 2 runtime 으로 일시 조회 / (2) 과거 1~2주만 reindex 후 정리. -
Q. ingest pipeline 에서
if조건 /ignore_failure가 왜 중요한가?
A
전문 구조가 100% 일관되지 않음. data.header 가 빠진 doc 도 있을 수 있어 그 경우 set processor 가 실패. `if`로 미리 가드 + `ignore_failure`로 doc 자체는 살림. on_failure block 으로 에러도 별도 필드로 캡처. -
Q. "에러율" KPI 를 Phase 1 만으로 (top-level 만으로) 어떻게든 흉내 낼 수 있나?
A
거의 불가. HTTP status code 가 top-level 에 있으면 부분적으로 (response 200 vs 4xx/5xx) — 하지만 카드 결제 플랫폼 처럼 비즈니스 응답이 항상 200 + 내부 result_code 로 표현되는 구조면 불가능. 즉 Phase 2+ 가 필수. -
Q. Phase 4 (App 표준화) 의 가장 큰 어려움?
A
기술 아닌 **협업·정책**. 표준 schema 합의 / SDK 채택 / 레거시 마이그 조율 / 변경 거버넌스. 그래서 1분기+ 걸림. Phase 3 으로도 ROI 충분한 케이스 많음.
12. 한 페이지 요약¶
``` 환경: 카드 결제 플랫폼, data.header/body indexing X 전략: 4-Phase 단계적 회복
Phase 1 (Day 1) top-level 만으로 SRE 60% (트래픽/latency/Dead API) ❌ 에러율 / 채널 / 거래 정보 모두 못 봄
Phase 2 (Week 1) Runtime fields 6~10 등록 (result_code, channel, amount, ...) ✅ 에러율 + 비즈니스 검색 ⚠️ 매번 _source 파싱 → 10~100x 느림
Phase 3 (Month 1) Ingest pipeline + index template ✅ 신규 인덱스 자동 top-level 매핑 + 빠름 (과거는 reindex 별도)
Phase 4 (Quarter) App SDK + 표준 schema ✅ 깨끗 + 영구 + 다중 팀 표준 ⚠️ 협업 시간 큼
권장 stop: 대부분 팀 Phase 3 에서 충분, Phase 4 는 옵션 ```
다음¶
- 실제 시작은 Phase 1 의 dashboard 부터 (위 §3 + 03-dashboards.md)
- Phase 2 시도 — 막히면 99-troubleshooting.md 의 "Lens·시각화 문제"
- Phase 3 시 매핑·index template 은 06-index-management.md
- Phase 3 의 일일 통계 인덱스는 07-batch-transform.md