moai-adk 0.5.4__py3-none-any.whl → 0.5.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of moai-adk might be problematic. Click here for more details.
- moai_adk/__init__.py +1 -1
- moai_adk/cli/commands/init.py +4 -2
- moai_adk/cli/commands/status.py +4 -2
- moai_adk/cli/commands/update.py +4 -5
- moai_adk/core/project/initializer.py +13 -11
- moai_adk/core/project/phase_executor.py +7 -3
- moai_adk/core/template/processor.py +60 -1
- moai_adk/templates/.claude/agents/alfred/cc-manager.md +8 -0
- moai_adk/templates/.claude/agents/alfred/debug-helper.md +18 -0
- moai_adk/templates/.claude/agents/alfred/doc-syncer.md +18 -0
- moai_adk/templates/.claude/agents/alfred/git-manager.md +38 -2
- moai_adk/templates/.claude/agents/alfred/implementation-planner.md +18 -0
- moai_adk/templates/.claude/agents/alfred/project-manager.md +6 -0
- moai_adk/templates/.claude/agents/alfred/quality-gate.md +6 -0
- moai_adk/templates/.claude/agents/alfred/skill-factory.md +8 -0
- moai_adk/templates/.claude/agents/alfred/spec-builder.md +17 -0
- moai_adk/templates/.claude/agents/alfred/tag-agent.md +7 -1
- moai_adk/templates/.claude/agents/alfred/tdd-implementer.md +18 -0
- moai_adk/templates/.claude/agents/alfred/trust-checker.md +6 -0
- moai_adk/templates/.claude/commands/alfred/0-project.md +5 -1
- moai_adk/templates/.claude/commands/alfred/1-plan.md +5 -1
- moai_adk/templates/.claude/commands/alfred/2-run.md +6 -2
- moai_adk/templates/.claude/commands/alfred/3-sync.md +5 -1
- moai_adk/templates/.claude/hooks/alfred/alfred_hooks.py +5 -1
- moai_adk/templates/.claude/output-styles/alfred/agentic-coding.md +5 -1
- moai_adk/templates/.claude/output-styles/alfred/moai-adk-learning.md +5 -1
- moai_adk/templates/.claude/output-styles/alfred/study-with-alfred.md +5 -1
- moai_adk/templates/.claude/skills/moai-alfred-interactive-questions/SKILL.md +30 -273
- moai_adk/templates/.claude/skills/moai-alfred-interactive-questions/examples.md +487 -129
- moai_adk/templates/.claude/skills/moai-alfred-interactive-questions/reference.md +603 -70
- moai_adk/templates/.claude/skills/moai-cc-agents/SKILL.md +22 -2
- moai_adk/templates/.claude/skills/moai-cc-claude-md/SKILL.md +22 -2
- moai_adk/templates/.claude/skills/moai-cc-commands/SKILL.md +22 -2
- moai_adk/templates/.claude/skills/moai-cc-hooks/SKILL.md +22 -2
- moai_adk/templates/.claude/skills/moai-cc-mcp-plugins/SKILL.md +22 -2
- moai_adk/templates/.claude/skills/moai-cc-memory/SKILL.md +22 -2
- moai_adk/templates/.claude/skills/moai-cc-settings/SKILL.md +22 -2
- moai_adk/templates/.claude/skills/moai-cc-skills/SKILL.md +25 -5
- moai_adk/templates/.claude/skills/moai-essentials-debug/SKILL.md +152 -547
- moai_adk/templates/.claude/skills/moai-essentials-debug/examples.md +835 -878
- moai_adk/templates/.claude/skills/moai-essentials-debug/reference.md +665 -1151
- moai_adk/templates/.claude/skills/moai-skill-factory/SKILL.md +138 -427
- moai_adk/templates/.claude/skills/moai-spec-authoring/README.md +61 -53
- moai_adk/templates/.claude/skills/moai-spec-authoring/SKILL.md +99 -1181
- moai_adk/templates/.claude/skills/moai-spec-authoring/examples.md +541 -0
- moai_adk/templates/.claude/skills/moai-spec-authoring/reference.md +622 -0
- moai_adk/templates/.moai/config.json +5 -5
- moai_adk/templates/.moai/memory/CLAUDE-AGENTS-GUIDE.md +208 -0
- moai_adk/templates/.moai/memory/CLAUDE-PRACTICES.md +369 -0
- moai_adk/templates/.moai/memory/CLAUDE-RULES.md +539 -0
- moai_adk/templates/.moai/memory/{development-guide.md → DEVELOPMENT-GUIDE.md} +3 -3
- moai_adk/templates/.moai/memory/SKILLS-DESCRIPTION-POLICY.md +218 -0
- moai_adk/templates/.moai/memory/config-schema.md +444 -0
- moai_adk/templates/CLAUDE.md +149 -845
- {moai_adk-0.5.4.dist-info → moai_adk-0.5.6.dist-info}/METADATA +293 -335
- {moai_adk-0.5.4.dist-info → moai_adk-0.5.6.dist-info}/RECORD +61 -54
- /moai_adk/templates/.moai/memory/{gitflow-protection-policy.md → GITFLOW-PROTECTION-POLICY.md} +0 -0
- /moai_adk/templates/.moai/memory/{spec-metadata.md → SPEC-METADATA.md} +0 -0
- {moai_adk-0.5.4.dist-info → moai_adk-0.5.6.dist-info}/WHEEL +0 -0
- {moai_adk-0.5.4.dist-info → moai_adk-0.5.6.dist-info}/entry_points.txt +0 -0
- {moai_adk-0.5.4.dist-info → moai_adk-0.5.6.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,1107 +1,1064 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
4. [Kubernetes Pod Crash Debugging](#kubernetes-pod-crash-debugging)
|
|
8
|
-
5. [Memory Leak Diagnosis (Rust)](#memory-leak-diagnosis-rust)
|
|
9
|
-
6. [Race Condition Detection (Go)](#race-condition-detection-go)
|
|
10
|
-
7. [Null Pointer Debugging (TypeScript)](#null-pointer-debugging-typescript)
|
|
11
|
-
8. [Database Query Performance (SQL)](#database-query-performance-sql)
|
|
1
|
+
# moai-essentials-debug — 실전 예제
|
|
2
|
+
|
|
3
|
+
> **Version**: 2.1.0
|
|
4
|
+
> **Last Updated**: 2025-10-27
|
|
5
|
+
|
|
6
|
+
이 문서는 언어별 스택 트레이스 분석 실전 예제와 디버깅 시나리오를 제공합니다.
|
|
12
7
|
|
|
13
8
|
---
|
|
14
9
|
|
|
15
|
-
##
|
|
10
|
+
## Python 디버깅 예제
|
|
16
11
|
|
|
17
|
-
###
|
|
18
|
-
FastAPI application experiencing intermittent timeouts and "Event loop is closed" errors.
|
|
12
|
+
### 예제 1: TypeError — 타입 불일치
|
|
19
13
|
|
|
20
|
-
|
|
21
|
-
```
|
|
14
|
+
**에러 메시지**:
|
|
15
|
+
```
|
|
22
16
|
Traceback (most recent call last):
|
|
23
|
-
File "app.py", line
|
|
24
|
-
|
|
25
|
-
File "app.py", line
|
|
26
|
-
|
|
27
|
-
|
|
17
|
+
File "app.py", line 45, in <module>
|
|
18
|
+
main()
|
|
19
|
+
File "app.py", line 38, in main
|
|
20
|
+
result = process_data(data)
|
|
21
|
+
File "app.py", line 15, in process_data
|
|
22
|
+
total = sum(items)
|
|
23
|
+
TypeError: unsupported operand type(s) for +: 'int' and 'str'
|
|
28
24
|
```
|
|
29
25
|
|
|
30
|
-
|
|
26
|
+
**분석**:
|
|
27
|
+
1. **에러 위치**: `app.py:15` (`sum(items)`)
|
|
28
|
+
2. **에러 타입**: `TypeError` — 정수와 문자열을 더하려고 시도
|
|
29
|
+
3. **실행 경로**: `main()` → `process_data()` → `sum()`
|
|
30
|
+
4. **근본 원인**: `items` 리스트에 문자열이 포함됨
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
**디버깅 단계**:
|
|
33
33
|
```python
|
|
34
|
-
#
|
|
35
|
-
import
|
|
36
|
-
import debugpy
|
|
34
|
+
# 1. 브레이크포인트 설정
|
|
35
|
+
import pdb; pdb.set_trace()
|
|
37
36
|
|
|
38
|
-
#
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
debugpy.wait_for_client()
|
|
37
|
+
# 2. items 내용 확인
|
|
38
|
+
(Pdb) print(items)
|
|
39
|
+
[1, 2, '3', 4, 5] # '3'이 문자열!
|
|
42
40
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
async with session.get(url) as response:
|
|
47
|
-
return await response.json()
|
|
41
|
+
# 3. 타입 검증
|
|
42
|
+
(Pdb) [type(x) for x in items]
|
|
43
|
+
[<class 'int'>, <class 'int'>, <class 'str'>, <class 'int'>, <class 'int'>]
|
|
48
44
|
```
|
|
49
45
|
|
|
50
|
-
|
|
51
|
-
```
|
|
52
|
-
#
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
46
|
+
**해결 방법**:
|
|
47
|
+
```python
|
|
48
|
+
# Option 1: 입력 데이터 검증
|
|
49
|
+
def process_data(items):
|
|
50
|
+
# 타입 체크 및 변환
|
|
51
|
+
items = [int(x) if isinstance(x, str) else x for x in items]
|
|
52
|
+
total = sum(items)
|
|
53
|
+
return total
|
|
54
|
+
|
|
55
|
+
# Option 2: 타입 힌트 + mypy
|
|
56
|
+
def process_data(items: list[int]) -> int:
|
|
57
|
+
total = sum(items)
|
|
58
|
+
return total
|
|
57
59
|
```
|
|
58
60
|
|
|
59
|
-
|
|
60
|
-
```json
|
|
61
|
-
{
|
|
62
|
-
"version": "0.2.0",
|
|
63
|
-
"configurations": [
|
|
64
|
-
{
|
|
65
|
-
"name": "Python: Async Debug",
|
|
66
|
-
"type": "debugpy",
|
|
67
|
-
"request": "attach",
|
|
68
|
-
"connect": {
|
|
69
|
-
"host": "localhost",
|
|
70
|
-
"port": 5678
|
|
71
|
-
},
|
|
72
|
-
"justMyCode": false,
|
|
73
|
-
"pathMappings": [
|
|
74
|
-
{
|
|
75
|
-
"localRoot": "${workspaceFolder}",
|
|
76
|
-
"remoteRoot": "."
|
|
77
|
-
}
|
|
78
|
-
]
|
|
79
|
-
}
|
|
80
|
-
]
|
|
81
|
-
}
|
|
82
|
-
```
|
|
61
|
+
---
|
|
83
62
|
|
|
84
|
-
|
|
85
|
-
```python
|
|
86
|
-
# In debugger console
|
|
87
|
-
import asyncio
|
|
88
|
-
loop = asyncio.get_event_loop()
|
|
89
|
-
print(f"Loop running: {loop.is_running()}")
|
|
90
|
-
print(f"Loop closed: {loop.is_closed()}")
|
|
63
|
+
### 예제 2: ImportError — 모듈 미설치
|
|
91
64
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
65
|
+
**에러 메시지**:
|
|
66
|
+
```
|
|
67
|
+
Traceback (most recent call last):
|
|
68
|
+
File "script.py", line 3, in <module>
|
|
69
|
+
import requests
|
|
70
|
+
ModuleNotFoundError: No module named 'requests'
|
|
97
71
|
```
|
|
98
72
|
|
|
99
|
-
|
|
100
|
-
|
|
73
|
+
**분석**:
|
|
74
|
+
1. **에러 위치**: `script.py:3`
|
|
75
|
+
2. **에러 타입**: `ModuleNotFoundError` — requests 모듈이 설치되지 않음
|
|
76
|
+
3. **근본 원인**: 가상환경 활성화 안 됨 또는 의존성 미설치
|
|
101
77
|
|
|
102
|
-
|
|
103
|
-
```
|
|
104
|
-
#
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
asyncio.set_event_loop(loop)
|
|
108
|
-
loop.run_until_complete(fetch_data())
|
|
109
|
-
loop.close() # ❌ Closes loop prematurely
|
|
78
|
+
**디버깅 단계**:
|
|
79
|
+
```bash
|
|
80
|
+
# 1. 가상환경 확인
|
|
81
|
+
which python
|
|
82
|
+
# /usr/bin/python (시스템 Python — 잘못됨!)
|
|
110
83
|
|
|
111
|
-
#
|
|
112
|
-
|
|
113
|
-
|
|
84
|
+
# 2. 가상환경 활성화
|
|
85
|
+
source venv/bin/activate
|
|
86
|
+
which python
|
|
87
|
+
# /path/to/venv/bin/python (올바름)
|
|
114
88
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
89
|
+
# 3. 패키지 설치 확인
|
|
90
|
+
pip list | grep requests
|
|
91
|
+
# (없음)
|
|
118
92
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
import pytest
|
|
123
|
-
import asyncio
|
|
93
|
+
# 4. 의존성 설치
|
|
94
|
+
pip install requests
|
|
95
|
+
```
|
|
124
96
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
97
|
+
**해결 방법**:
|
|
98
|
+
```bash
|
|
99
|
+
# pyproject.toml 또는 requirements.txt에 의존성 명시
|
|
100
|
+
# requirements.txt
|
|
101
|
+
requests==2.31.0
|
|
130
102
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
assert not loop.is_closed()
|
|
103
|
+
# 설치
|
|
104
|
+
pip install -r requirements.txt
|
|
134
105
|
```
|
|
135
106
|
|
|
136
107
|
---
|
|
137
108
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
### Problem
|
|
141
|
-
Go service memory usage growing indefinitely over time.
|
|
109
|
+
### 예제 3: AttributeError — 속성 없음
|
|
142
110
|
|
|
143
|
-
|
|
111
|
+
**에러 메시지**:
|
|
144
112
|
```
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
/app/worker.go:25 +0x80
|
|
150
|
-
|
|
151
|
-
goroutine 1544 [chan receive]:
|
|
152
|
-
main.processMessages()
|
|
153
|
-
/app/worker.go:42 +0x120
|
|
154
|
-
created by main.startWorker
|
|
155
|
-
/app/worker.go:25 +0x80
|
|
156
|
-
... (1542 more goroutines)
|
|
113
|
+
Traceback (most recent call last):
|
|
114
|
+
File "app.py", line 28, in <module>
|
|
115
|
+
result = user.get_profile()
|
|
116
|
+
AttributeError: 'NoneType' object has no attribute 'get_profile'
|
|
157
117
|
```
|
|
158
118
|
|
|
159
|
-
|
|
119
|
+
**분석**:
|
|
120
|
+
1. **에러 위치**: `app.py:28`
|
|
121
|
+
2. **에러 타입**: `AttributeError` — `user`가 `None`임
|
|
122
|
+
3. **근본 원인**: `user` 객체가 생성되지 않았거나 None 반환
|
|
160
123
|
|
|
161
|
-
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
)
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
124
|
+
**디버깅 단계**:
|
|
125
|
+
```python
|
|
126
|
+
# 1. 브레이크포인트 설정
|
|
127
|
+
breakpoint()
|
|
128
|
+
|
|
129
|
+
# 2. user 확인
|
|
130
|
+
(Pdb) print(user)
|
|
131
|
+
None
|
|
132
|
+
|
|
133
|
+
# 3. user가 None이 된 이유 추적
|
|
134
|
+
(Pdb) where
|
|
135
|
+
app.py(20)main()
|
|
136
|
+
app.py(15)get_user()
|
|
137
|
+
-> return None # 여기서 None 반환!
|
|
138
|
+
|
|
139
|
+
# 4. get_user() 함수 확인
|
|
140
|
+
def get_user(user_id):
|
|
141
|
+
user = database.find_user(user_id)
|
|
142
|
+
if not user:
|
|
143
|
+
return None # 문제 발견!
|
|
144
|
+
return user
|
|
145
|
+
```
|
|
174
146
|
|
|
175
|
-
|
|
176
|
-
|
|
147
|
+
**해결 방법**:
|
|
148
|
+
```python
|
|
149
|
+
# Option 1: None 체크
|
|
150
|
+
user = get_user(user_id)
|
|
151
|
+
if user is None:
|
|
152
|
+
print("User not found")
|
|
153
|
+
return
|
|
154
|
+
result = user.get_profile()
|
|
155
|
+
|
|
156
|
+
# Option 2: Optional 타입 힌트
|
|
157
|
+
from typing import Optional
|
|
158
|
+
|
|
159
|
+
def get_user(user_id: int) -> Optional[User]:
|
|
160
|
+
user = database.find_user(user_id)
|
|
161
|
+
return user
|
|
162
|
+
|
|
163
|
+
# Option 3: 예외 처리
|
|
164
|
+
try:
|
|
165
|
+
result = user.get_profile()
|
|
166
|
+
except AttributeError:
|
|
167
|
+
print("User is None or has no get_profile method")
|
|
177
168
|
```
|
|
178
169
|
|
|
179
|
-
|
|
180
|
-
```bash
|
|
181
|
-
# Get goroutine count
|
|
182
|
-
curl http://localhost:6060/debug/pprof/goroutine?debug=1 > goroutines.txt
|
|
170
|
+
---
|
|
183
171
|
|
|
184
|
-
|
|
185
|
-
go tool pprof http://localhost:6060/debug/pprof/goroutine
|
|
186
|
-
(pprof) top
|
|
187
|
-
(pprof) list processMessages
|
|
188
|
-
```
|
|
172
|
+
## TypeScript 디버깅 예제
|
|
189
173
|
|
|
190
|
-
|
|
191
|
-
```bash
|
|
192
|
-
# Attach to running process
|
|
193
|
-
dlv attach $(pgrep myapp)
|
|
174
|
+
### 예제 1: undefined 접근
|
|
194
175
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
(
|
|
201
|
-
|
|
202
|
-
at /usr/local/go/src/runtime/proc.go:363
|
|
203
|
-
1 0x000000000040b5b6 in runtime.chanrecv
|
|
204
|
-
at /usr/local/go/src/runtime/chan.go:583
|
|
205
|
-
2 0x00000000010a5c30 in main.processMessages
|
|
206
|
-
at /app/worker.go:42
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
#### 4. VSCode launch.json
|
|
210
|
-
```json
|
|
211
|
-
{
|
|
212
|
-
"version": "0.2.0",
|
|
213
|
-
"configurations": [
|
|
214
|
-
{
|
|
215
|
-
"name": "Debug with Goroutine Inspection",
|
|
216
|
-
"type": "go",
|
|
217
|
-
"request": "launch",
|
|
218
|
-
"mode": "debug",
|
|
219
|
-
"program": "${workspaceFolder}",
|
|
220
|
-
"showLog": true,
|
|
221
|
-
"logOutput": "debugger",
|
|
222
|
-
"dlvToolPath": "/usr/local/bin/dlv"
|
|
223
|
-
},
|
|
224
|
-
{
|
|
225
|
-
"name": "Attach to Process",
|
|
226
|
-
"type": "go",
|
|
227
|
-
"request": "attach",
|
|
228
|
-
"mode": "local",
|
|
229
|
-
"processId": "${command:pickProcess}"
|
|
230
|
-
}
|
|
231
|
-
]
|
|
232
|
-
}
|
|
176
|
+
**에러 메시지**:
|
|
177
|
+
```
|
|
178
|
+
TypeError: Cannot read properties of undefined (reading 'name')
|
|
179
|
+
at processUser (app.ts:42:28)
|
|
180
|
+
at Array.map (<anonymous>)
|
|
181
|
+
at getUserNames (app.ts:35:18)
|
|
182
|
+
at main (app.ts:10:5)
|
|
233
183
|
```
|
|
234
184
|
|
|
235
|
-
|
|
236
|
-
|
|
185
|
+
**분석**:
|
|
186
|
+
1. **에러 위치**: `app.ts:42` (`user.name` 접근)
|
|
187
|
+
2. **에러 타입**: `TypeError` — `user`가 `undefined`
|
|
188
|
+
3. **실행 경로**: `main()` → `getUserNames()` → `map()` → `processUser()`
|
|
189
|
+
4. **근본 원인**: 배열에 `undefined` 요소가 포함됨
|
|
237
190
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
for msg := range msgChan { // ❌ Never exits if channel not closed
|
|
244
|
-
processMessage(msg)
|
|
245
|
-
}
|
|
246
|
-
}()
|
|
247
|
-
// ... no channel close on ctx.Done()
|
|
191
|
+
**코드**:
|
|
192
|
+
```typescript
|
|
193
|
+
// app.ts
|
|
194
|
+
function processUser(user: User) {
|
|
195
|
+
return user.name.toUpperCase(); // 여기서 에러!
|
|
248
196
|
}
|
|
249
197
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
msgChan := make(chan Message)
|
|
253
|
-
go func() {
|
|
254
|
-
defer close(msgChan)
|
|
255
|
-
for {
|
|
256
|
-
select {
|
|
257
|
-
case msg := <-msgChan:
|
|
258
|
-
processMessage(msg)
|
|
259
|
-
case <-ctx.Done():
|
|
260
|
-
return // ✅ Proper cleanup
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
}()
|
|
198
|
+
function getUserNames(users: User[]): string[] {
|
|
199
|
+
return users.map(processUser);
|
|
264
200
|
}
|
|
201
|
+
|
|
202
|
+
const users = [
|
|
203
|
+
{ id: 1, name: 'Alice' },
|
|
204
|
+
undefined, // 문제 발견!
|
|
205
|
+
{ id: 2, name: 'Bob' },
|
|
206
|
+
];
|
|
265
207
|
```
|
|
266
208
|
|
|
267
|
-
|
|
268
|
-
```
|
|
269
|
-
//
|
|
270
|
-
|
|
271
|
-
|
|
209
|
+
**디버깅 단계**:
|
|
210
|
+
```typescript
|
|
211
|
+
// 1. 브레이크포인트 설정 (debugger 키워드)
|
|
212
|
+
function processUser(user: User) {
|
|
213
|
+
debugger; // 여기서 중단
|
|
214
|
+
return user.name.toUpperCase();
|
|
215
|
+
}
|
|
272
216
|
|
|
273
|
-
|
|
274
|
-
|
|
217
|
+
// Chrome DevTools에서:
|
|
218
|
+
// user: undefined
|
|
219
|
+
```
|
|
275
220
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
221
|
+
**해결 방법**:
|
|
222
|
+
```typescript
|
|
223
|
+
// Option 1: 타입 가드
|
|
224
|
+
function processUser(user: User | undefined): string {
|
|
225
|
+
if (!user) {
|
|
226
|
+
return 'Unknown';
|
|
227
|
+
}
|
|
228
|
+
return user.name.toUpperCase();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Option 2: Optional 체이닝
|
|
232
|
+
function processUser(user?: User): string {
|
|
233
|
+
return user?.name?.toUpperCase() ?? 'Unknown';
|
|
234
|
+
}
|
|
279
235
|
|
|
280
|
-
|
|
281
|
-
|
|
236
|
+
// Option 3: 필터링
|
|
237
|
+
function getUserNames(users: (User | undefined)[]): string[] {
|
|
238
|
+
return users
|
|
239
|
+
.filter((user): user is User => user !== undefined)
|
|
240
|
+
.map(user => user.name.toUpperCase());
|
|
282
241
|
}
|
|
283
242
|
```
|
|
284
243
|
|
|
285
244
|
---
|
|
286
245
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
### Problem
|
|
290
|
-
Request slow across microservices; need to identify bottleneck.
|
|
246
|
+
### 예제 2: Promise rejection
|
|
291
247
|
|
|
292
|
-
|
|
248
|
+
**에러 메시지**:
|
|
293
249
|
```
|
|
294
|
-
|
|
295
|
-
|
|
250
|
+
UnhandledPromiseRejectionWarning: Error: Network request failed
|
|
251
|
+
at fetchData (api.ts:15:11)
|
|
252
|
+
at async processRequest (handler.ts:28:18)
|
|
253
|
+
at async main (app.ts:12:3)
|
|
296
254
|
```
|
|
297
255
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
**API Gateway (Python/FastAPI)**
|
|
303
|
-
```python
|
|
304
|
-
# gateway/main.py
|
|
305
|
-
from opentelemetry import trace
|
|
306
|
-
from opentelemetry.sdk.trace import TracerProvider
|
|
307
|
-
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
|
308
|
-
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
|
|
309
|
-
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
|
|
310
|
-
|
|
311
|
-
# Setup tracing
|
|
312
|
-
provider = TracerProvider()
|
|
313
|
-
processor = BatchSpanProcessor(OTLPSpanExporter(endpoint="http://jaeger:4317"))
|
|
314
|
-
provider.add_span_processor(processor)
|
|
315
|
-
trace.set_tracer_provider(provider)
|
|
316
|
-
|
|
317
|
-
app = FastAPI()
|
|
318
|
-
FastAPIInstrumentor.instrument_app(app)
|
|
319
|
-
|
|
320
|
-
@app.get("/users/{user_id}")
|
|
321
|
-
async def get_user(user_id: int):
|
|
322
|
-
tracer = trace.get_tracer(__name__)
|
|
323
|
-
|
|
324
|
-
with tracer.start_as_current_span("gateway.get_user") as span:
|
|
325
|
-
span.set_attribute("user.id", user_id)
|
|
256
|
+
**분석**:
|
|
257
|
+
1. **에러 위치**: `api.ts:15`
|
|
258
|
+
2. **에러 타입**: `UnhandledPromiseRejectionWarning` — Promise 거부가 처리되지 않음
|
|
259
|
+
3. **근본 원인**: `fetchData()`에서 발생한 에러가 catch되지 않음
|
|
326
260
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
261
|
+
**코드**:
|
|
262
|
+
```typescript
|
|
263
|
+
// api.ts
|
|
264
|
+
async function fetchData(url: string): Promise<Data> {
|
|
265
|
+
const response = await fetch(url);
|
|
266
|
+
if (!response.ok) {
|
|
267
|
+
throw new Error('Network request failed'); // 여기서 에러 발생!
|
|
268
|
+
}
|
|
269
|
+
return response.json();
|
|
270
|
+
}
|
|
334
271
|
|
|
335
|
-
|
|
272
|
+
// handler.ts
|
|
273
|
+
async function processRequest(url: string) {
|
|
274
|
+
const data = await fetchData(url); // 에러 처리 없음!
|
|
275
|
+
return data;
|
|
276
|
+
}
|
|
336
277
|
```
|
|
337
278
|
|
|
338
|
-
|
|
339
|
-
```
|
|
340
|
-
//
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
otlptracegrpc.WithInsecure(),
|
|
352
|
-
)
|
|
353
|
-
|
|
354
|
-
tp := trace.NewTracerProvider(
|
|
355
|
-
trace.WithBatcher(exporter),
|
|
356
|
-
)
|
|
357
|
-
otel.SetTracerProvider(tp)
|
|
279
|
+
**디버깅 단계**:
|
|
280
|
+
```typescript
|
|
281
|
+
// 1. 브레이크포인트 설정
|
|
282
|
+
async function fetchData(url: string): Promise<Data> {
|
|
283
|
+
debugger;
|
|
284
|
+
const response = await fetch(url);
|
|
285
|
+
debugger; // response 확인
|
|
286
|
+
// response.ok: false
|
|
287
|
+
// response.status: 404
|
|
288
|
+
if (!response.ok) {
|
|
289
|
+
throw new Error('Network request failed');
|
|
290
|
+
}
|
|
291
|
+
return response.json();
|
|
358
292
|
}
|
|
293
|
+
```
|
|
359
294
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
295
|
+
**해결 방법**:
|
|
296
|
+
```typescript
|
|
297
|
+
// Option 1: try-catch
|
|
298
|
+
async function processRequest(url: string): Promise<Data | null> {
|
|
299
|
+
try {
|
|
300
|
+
const data = await fetchData(url);
|
|
301
|
+
return data;
|
|
302
|
+
} catch (error) {
|
|
303
|
+
console.error('Failed to fetch data:', error);
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
369
307
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
user, err := db.GetUser(ctx, userID)
|
|
373
|
-
dbSpan.End()
|
|
308
|
+
// Option 2: Result 타입
|
|
309
|
+
type Result<T> = { success: true; data: T } | { success: false; error: Error };
|
|
374
310
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
311
|
+
async function fetchData(url: string): Promise<Result<Data>> {
|
|
312
|
+
try {
|
|
313
|
+
const response = await fetch(url);
|
|
314
|
+
if (!response.ok) {
|
|
315
|
+
return { success: false, error: new Error('Network request failed') };
|
|
379
316
|
}
|
|
380
|
-
|
|
381
|
-
|
|
317
|
+
const data = await response.json();
|
|
318
|
+
return { success: true, data };
|
|
319
|
+
} catch (error) {
|
|
320
|
+
return { success: false, error: error as Error };
|
|
321
|
+
}
|
|
382
322
|
}
|
|
383
323
|
```
|
|
384
324
|
|
|
385
|
-
|
|
386
|
-
```yaml
|
|
387
|
-
# docker-compose.yml
|
|
388
|
-
version: '3.8'
|
|
389
|
-
services:
|
|
390
|
-
jaeger:
|
|
391
|
-
image: jaegertracing/all-in-one:1.50
|
|
392
|
-
ports:
|
|
393
|
-
- "16686:16686" # Jaeger UI
|
|
394
|
-
- "4317:4317" # OTLP gRPC
|
|
395
|
-
- "4318:4318" # OTLP HTTP
|
|
396
|
-
environment:
|
|
397
|
-
- COLLECTOR_OTLP_ENABLED=true
|
|
398
|
-
```
|
|
325
|
+
---
|
|
399
326
|
|
|
400
|
-
|
|
401
|
-
```bash
|
|
402
|
-
# Start services
|
|
403
|
-
docker-compose up -d
|
|
327
|
+
## Java 디버깅 예제
|
|
404
328
|
|
|
405
|
-
|
|
406
|
-
for i in {1..100}; do
|
|
407
|
-
curl http://localhost:8000/users/123
|
|
408
|
-
sleep 0.1
|
|
409
|
-
done
|
|
329
|
+
### 예제 1: NullPointerException
|
|
410
330
|
|
|
411
|
-
|
|
412
|
-
open http://localhost:16686
|
|
331
|
+
**에러 메시지**:
|
|
413
332
|
```
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
3. Find slow traces (> 500ms)
|
|
419
|
-
4. Click trace to see waterfall view
|
|
420
|
-
5. Identify bottleneck span
|
|
421
|
-
|
|
422
|
-
**Example Trace Timeline**
|
|
333
|
+
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "User.getName()" because "user" is null
|
|
334
|
+
at com.example.UserService.processUser(UserService.java:42)
|
|
335
|
+
at com.example.UserService.processAllUsers(UserService.java:28)
|
|
336
|
+
at com.example.Main.main(Main.java:15)
|
|
423
337
|
```
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
338
|
+
|
|
339
|
+
**분석**:
|
|
340
|
+
1. **에러 위치**: `UserService.java:42` (`user.getName()` 호출)
|
|
341
|
+
2. **에러 타입**: `NullPointerException` — `user`가 `null`
|
|
342
|
+
3. **실행 경로**: `Main.main()` → `processAllUsers()` → `processUser()`
|
|
343
|
+
4. **근본 원인**: `user` 객체가 `null`인 채로 메서드 호출 시도
|
|
344
|
+
|
|
345
|
+
**코드**:
|
|
346
|
+
```java
|
|
347
|
+
// UserService.java
|
|
348
|
+
public class UserService {
|
|
349
|
+
public void processUser(User user) {
|
|
350
|
+
String name = user.getName(); // 여기서 NPE 발생!
|
|
351
|
+
System.out.println("Processing: " + name);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
public void processAllUsers(List<User> users) {
|
|
355
|
+
for (User user : users) {
|
|
356
|
+
processUser(user);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
433
360
|
```
|
|
434
361
|
|
|
435
|
-
|
|
436
|
-
|
|
362
|
+
**디버깅 단계**:
|
|
363
|
+
```java
|
|
364
|
+
// 1. 브레이크포인트 설정 (IntelliJ/Eclipse)
|
|
365
|
+
// UserService.java:42에 브레이크포인트
|
|
437
366
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
367
|
+
// 2. 변수 확인
|
|
368
|
+
// user: null
|
|
369
|
+
|
|
370
|
+
// 3. 호출 스택 확인
|
|
371
|
+
// processUser() ← processAllUsers() ← main()
|
|
442
372
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
-- Before: Seq Scan on users (cost=0.00..1245.00 rows=1 width=100) (actual time=1160.234..1160.235 rows=1 loops=1)
|
|
446
|
-
-- After: Index Scan using idx_users_id on users (cost=0.42..8.44 rows=1 width=100) (actual time=0.023..0.024 rows=1 loops=1)
|
|
373
|
+
// 4. users 리스트 확인
|
|
374
|
+
// users: [User@123, null, User@456] // null 발견!
|
|
447
375
|
```
|
|
448
376
|
|
|
449
|
-
|
|
450
|
-
```
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
377
|
+
**해결 방법**:
|
|
378
|
+
```java
|
|
379
|
+
// Option 1: Null 체크
|
|
380
|
+
public void processUser(User user) {
|
|
381
|
+
if (user == null) {
|
|
382
|
+
System.out.println("User is null");
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
String name = user.getName();
|
|
386
|
+
System.out.println("Processing: " + name);
|
|
387
|
+
}
|
|
454
388
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
389
|
+
// Option 2: Optional<T> 사용 (Java 8+)
|
|
390
|
+
public void processUser(Optional<User> userOpt) {
|
|
391
|
+
userOpt.ifPresent(user -> {
|
|
392
|
+
String name = user.getName();
|
|
393
|
+
System.out.println("Processing: " + name);
|
|
394
|
+
});
|
|
395
|
+
}
|
|
459
396
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
397
|
+
public void processAllUsers(List<User> users) {
|
|
398
|
+
users.stream()
|
|
399
|
+
.filter(Objects::nonNull) // null 필터링
|
|
400
|
+
.forEach(this::processUser);
|
|
401
|
+
}
|
|
464
402
|
|
|
465
|
-
|
|
403
|
+
// Option 3: @NonNull 어노테이션 (Lombok, Checker Framework)
|
|
404
|
+
import lombok.NonNull;
|
|
466
405
|
|
|
467
|
-
|
|
468
|
-
|
|
406
|
+
public void processUser(@NonNull User user) {
|
|
407
|
+
String name = user.getName();
|
|
408
|
+
System.out.println("Processing: " + name);
|
|
409
|
+
}
|
|
469
410
|
```
|
|
470
411
|
|
|
471
412
|
---
|
|
472
413
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
### Problem
|
|
476
|
-
Pod enters `CrashLoopBackOff` state with OOMKilled status.
|
|
477
|
-
|
|
478
|
-
### Symptoms
|
|
479
|
-
```bash
|
|
480
|
-
kubectl get pods
|
|
481
|
-
NAME READY STATUS RESTARTS AGE
|
|
482
|
-
myapp-7d4f8c9b5-xyz 0/1 CrashLoopBackOff 5 10m
|
|
414
|
+
### 예제 2: ClassNotFoundException
|
|
483
415
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
416
|
+
**에러 메시지**:
|
|
417
|
+
```
|
|
418
|
+
Exception in thread "main" java.lang.ClassNotFoundException: com.example.database.DatabaseDriver
|
|
419
|
+
at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:445)
|
|
420
|
+
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:587)
|
|
421
|
+
at java.base/java.lang.Class.forName0(Native Method)
|
|
422
|
+
at java.base/java.lang.Class.forName(Class.java:467)
|
|
423
|
+
at com.example.Main.main(Main.java:12)
|
|
489
424
|
```
|
|
490
425
|
|
|
491
|
-
|
|
426
|
+
**분석**:
|
|
427
|
+
1. **에러 위치**: `Main.java:12` (`Class.forName()` 호출)
|
|
428
|
+
2. **에러 타입**: `ClassNotFoundException` — 클래스를 찾을 수 없음
|
|
429
|
+
3. **근본 원인**: JDBC 드라이버 JAR이 classpath에 없음
|
|
492
430
|
|
|
493
|
-
|
|
431
|
+
**디버깅 단계**:
|
|
494
432
|
```bash
|
|
495
|
-
#
|
|
496
|
-
|
|
433
|
+
# 1. Classpath 확인
|
|
434
|
+
echo $CLASSPATH
|
|
497
435
|
|
|
498
|
-
#
|
|
499
|
-
|
|
436
|
+
# 2. JAR 파일 확인
|
|
437
|
+
ls lib/
|
|
438
|
+
# mysql-connector-java-8.0.33.jar (있음)
|
|
500
439
|
|
|
501
|
-
#
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
[WARNING] Memory usage: 450MB
|
|
505
|
-
[WARNING] Memory usage: 750MB
|
|
506
|
-
[ERROR] Out of memory
|
|
440
|
+
# 3. 컴파일 및 실행 명령 확인
|
|
441
|
+
javac -cp ".:lib/*" Main.java
|
|
442
|
+
java -cp ".:lib/*" Main # classpath에 lib/* 포함 필요!
|
|
507
443
|
```
|
|
508
444
|
|
|
509
|
-
|
|
445
|
+
**해결 방법**:
|
|
510
446
|
```bash
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
447
|
+
# Option 1: Classpath 명시
|
|
448
|
+
java -cp ".:lib/*" com.example.Main
|
|
449
|
+
|
|
450
|
+
# Option 2: Maven/Gradle 사용
|
|
451
|
+
# pom.xml (Maven)
|
|
452
|
+
<dependency>
|
|
453
|
+
<groupId>mysql</groupId>
|
|
454
|
+
<artifactId>mysql-connector-java</artifactId>
|
|
455
|
+
<version>8.0.33</version>
|
|
456
|
+
</dependency>
|
|
457
|
+
|
|
458
|
+
# build.gradle (Gradle)
|
|
459
|
+
dependencies {
|
|
460
|
+
implementation 'mysql:mysql-connector-java:8.0.33'
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
# Option 3: Manifest에 Class-Path 추가
|
|
464
|
+
# MANIFEST.MF
|
|
465
|
+
Class-Path: lib/mysql-connector-java-8.0.33.jar
|
|
517
466
|
```
|
|
518
467
|
|
|
519
|
-
|
|
520
|
-
```bash
|
|
521
|
-
# Add debug container to running pod
|
|
522
|
-
kubectl debug -it myapp-7d4f8c9b5-xyz --image=ubuntu --target=myapp
|
|
468
|
+
---
|
|
523
469
|
|
|
524
|
-
|
|
525
|
-
apt-get update && apt-get install -y htop
|
|
526
|
-
htop
|
|
470
|
+
## Go 디버깅 예제
|
|
527
471
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
472
|
+
### 예제 1: nil pointer dereference
|
|
473
|
+
|
|
474
|
+
**에러 메시지**:
|
|
531
475
|
```
|
|
476
|
+
panic: runtime error: invalid memory address or nil pointer dereference
|
|
477
|
+
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x10a4f20]
|
|
532
478
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
479
|
+
goroutine 1 [running]:
|
|
480
|
+
main.processUser(...)
|
|
481
|
+
/Users/dev/project/main.go:42
|
|
482
|
+
main.main()
|
|
483
|
+
/Users/dev/project/main.go:15 +0x120
|
|
484
|
+
```
|
|
537
485
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
486
|
+
**분석**:
|
|
487
|
+
1. **에러 위치**: `main.go:42` (`processUser` 함수)
|
|
488
|
+
2. **에러 타입**: `panic: nil pointer dereference`
|
|
489
|
+
3. **근본 원인**: `nil` 포인터에 접근 시도
|
|
490
|
+
|
|
491
|
+
**코드**:
|
|
492
|
+
```go
|
|
493
|
+
// main.go
|
|
494
|
+
type User struct {
|
|
495
|
+
Name string
|
|
496
|
+
Age int
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
func processUser(user *User) {
|
|
500
|
+
fmt.Printf("Name: %s\n", user.Name) // 여기서 panic!
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
func main() {
|
|
504
|
+
var user *User // nil 포인터
|
|
505
|
+
processUser(user)
|
|
506
|
+
}
|
|
543
507
|
```
|
|
544
508
|
|
|
509
|
+
**디버깅 단계**:
|
|
545
510
|
```bash
|
|
546
|
-
#
|
|
547
|
-
|
|
511
|
+
# 1. Delve로 디버깅
|
|
512
|
+
dlv debug main.go
|
|
513
|
+
(dlv) break main.go:42
|
|
514
|
+
(dlv) continue
|
|
548
515
|
|
|
549
|
-
#
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
516
|
+
# 2. 변수 확인
|
|
517
|
+
(dlv) print user
|
|
518
|
+
nil
|
|
519
|
+
|
|
520
|
+
# 3. 호출 스택 확인
|
|
521
|
+
(dlv) stack
|
|
522
|
+
0 0x00000000010a4f20 in main.processUser
|
|
523
|
+
at main.go:42
|
|
524
|
+
1 0x00000000010a5040 in main.main
|
|
525
|
+
at main.go:15
|
|
556
526
|
```
|
|
557
527
|
|
|
558
|
-
|
|
559
|
-
|
|
528
|
+
**해결 방법**:
|
|
529
|
+
```go
|
|
530
|
+
// Option 1: Nil 체크
|
|
531
|
+
func processUser(user *User) {
|
|
532
|
+
if user == nil {
|
|
533
|
+
fmt.Println("User is nil")
|
|
534
|
+
return
|
|
535
|
+
}
|
|
536
|
+
fmt.Printf("Name: %s\n", user.Name)
|
|
537
|
+
}
|
|
560
538
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
data = [process_record(r) for r in fetch_records()] # ❌ 750MB
|
|
566
|
-
return data
|
|
539
|
+
// Option 2: 값 타입 사용
|
|
540
|
+
func processUser(user User) { // 포인터 아님
|
|
541
|
+
fmt.Printf("Name: %s\n", user.Name)
|
|
542
|
+
}
|
|
567
543
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
544
|
+
// Option 3: 생성자 패턴
|
|
545
|
+
func NewUser(name string, age int) *User {
|
|
546
|
+
return &User{Name: name, Age: age}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
func main() {
|
|
550
|
+
user := NewUser("Alice", 30) // 항상 non-nil
|
|
551
|
+
processUser(user)
|
|
552
|
+
}
|
|
572
553
|
```
|
|
573
554
|
|
|
574
|
-
|
|
575
|
-
```yaml
|
|
576
|
-
# deployment.yaml
|
|
577
|
-
apiVersion: apps/v1
|
|
578
|
-
kind: Deployment
|
|
579
|
-
metadata:
|
|
580
|
-
name: myapp
|
|
581
|
-
spec:
|
|
582
|
-
template:
|
|
583
|
-
spec:
|
|
584
|
-
containers:
|
|
585
|
-
- name: myapp
|
|
586
|
-
resources:
|
|
587
|
-
limits:
|
|
588
|
-
memory: 1Gi # ✅ Increased limit
|
|
589
|
-
requests:
|
|
590
|
-
memory: 512Mi
|
|
591
|
-
```
|
|
592
|
-
|
|
593
|
-
### Test Case
|
|
594
|
-
```python
|
|
595
|
-
# tests/test_memory.py
|
|
596
|
-
import tracemalloc
|
|
555
|
+
---
|
|
597
556
|
|
|
598
|
-
|
|
599
|
-
"""Ensure load_dataset uses < 100MB memory."""
|
|
600
|
-
tracemalloc.start()
|
|
557
|
+
### 예제 2: Goroutine leak
|
|
601
558
|
|
|
602
|
-
|
|
559
|
+
**문제 설명**: 고루틴이 종료되지 않고 계속 실행되어 메모리 누수 발생
|
|
603
560
|
|
|
604
|
-
|
|
605
|
-
|
|
561
|
+
**코드**:
|
|
562
|
+
```go
|
|
563
|
+
// main.go
|
|
564
|
+
func worker(ch chan int) {
|
|
565
|
+
for {
|
|
566
|
+
val := <-ch // 채널이 닫히지 않으면 영원히 대기
|
|
567
|
+
fmt.Println(val)
|
|
568
|
+
}
|
|
569
|
+
}
|
|
606
570
|
|
|
607
|
-
|
|
571
|
+
func main() {
|
|
572
|
+
ch := make(chan int)
|
|
573
|
+
go worker(ch)
|
|
574
|
+
|
|
575
|
+
ch <- 1
|
|
576
|
+
ch <- 2
|
|
577
|
+
// ch를 닫지 않고 종료 → goroutine leak!
|
|
578
|
+
}
|
|
608
579
|
```
|
|
609
580
|
|
|
610
|
-
|
|
581
|
+
**디버깅 단계**:
|
|
582
|
+
```bash
|
|
583
|
+
# 1. pprof로 고루틴 확인
|
|
584
|
+
go tool pprof http://localhost:6060/debug/pprof/goroutine
|
|
611
585
|
|
|
612
|
-
|
|
586
|
+
# 2. 고루틴 수 확인
|
|
587
|
+
(pprof) top
|
|
588
|
+
Showing nodes accounting for 1000 goroutines
|
|
589
|
+
flat flat% sum% cum cum%
|
|
590
|
+
1000 100% 100% 1000 100% main.worker
|
|
613
591
|
|
|
614
|
-
|
|
615
|
-
|
|
592
|
+
# 3. Delve로 고루틴 확인
|
|
593
|
+
dlv debug main.go
|
|
594
|
+
(dlv) goroutines
|
|
595
|
+
[1000 goroutines]
|
|
616
596
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
597
|
+
(dlv) goroutine 5
|
|
598
|
+
Goroutine 5 - User: main.worker
|
|
599
|
+
main.go:10 (0x10a4f20) (Waiting)
|
|
620
600
|
```
|
|
621
601
|
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
602
|
+
**해결 방법**:
|
|
603
|
+
```go
|
|
604
|
+
// Option 1: 채널 닫기
|
|
605
|
+
func main() {
|
|
606
|
+
ch := make(chan int)
|
|
607
|
+
go worker(ch)
|
|
608
|
+
|
|
609
|
+
ch <- 1
|
|
610
|
+
ch <- 2
|
|
611
|
+
close(ch) // 채널 닫기
|
|
612
|
+
time.Sleep(time.Second) // worker가 종료될 시간
|
|
613
|
+
}
|
|
628
614
|
|
|
629
|
-
|
|
630
|
-
|
|
615
|
+
func worker(ch chan int) {
|
|
616
|
+
for val := range ch { // 채널이 닫히면 종료
|
|
617
|
+
fmt.Println(val)
|
|
618
|
+
}
|
|
619
|
+
}
|
|
631
620
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
621
|
+
// Option 2: Context 사용
|
|
622
|
+
func worker(ctx context.Context, ch chan int) {
|
|
623
|
+
for {
|
|
624
|
+
select {
|
|
625
|
+
case val := <-ch:
|
|
626
|
+
fmt.Println(val)
|
|
627
|
+
case <-ctx.Done():
|
|
628
|
+
return // Context 취소 시 종료
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
636
632
|
|
|
637
|
-
|
|
638
|
-
|
|
633
|
+
func main() {
|
|
634
|
+
ctx, cancel := context.WithCancel(context.Background())
|
|
635
|
+
defer cancel()
|
|
636
|
+
|
|
637
|
+
ch := make(chan int)
|
|
638
|
+
go worker(ctx, ch)
|
|
639
|
+
|
|
640
|
+
ch <- 1
|
|
641
|
+
ch <- 2
|
|
642
|
+
cancel() // 고루틴 종료
|
|
643
|
+
time.Sleep(time.Second)
|
|
644
|
+
}
|
|
639
645
|
```
|
|
640
646
|
|
|
641
|
-
|
|
642
|
-
```bash
|
|
643
|
-
# Install cargo-flamegraph
|
|
644
|
-
cargo install flamegraph
|
|
647
|
+
---
|
|
645
648
|
|
|
646
|
-
|
|
647
|
-
cargo flamegraph --bin myapp
|
|
649
|
+
## Rust 디버깅 예제
|
|
648
650
|
|
|
649
|
-
|
|
651
|
+
### 예제 1: Borrow checker 에러
|
|
652
|
+
|
|
653
|
+
**에러 메시지**:
|
|
654
|
+
```
|
|
655
|
+
error[E0502]: cannot borrow `data` as mutable because it is also borrowed as immutable
|
|
656
|
+
--> src/main.rs:8:5
|
|
657
|
+
|
|
|
658
|
+
6 | let first = &data[0];
|
|
659
|
+
| -------- immutable borrow occurs here
|
|
660
|
+
7 |
|
|
661
|
+
8 | data.push(4);
|
|
662
|
+
| ^^^^^^^^^^^^ mutable borrow occurs here
|
|
663
|
+
9 |
|
|
664
|
+
10 | println!("First: {}", first);
|
|
665
|
+
| ----- immutable borrow later used here
|
|
650
666
|
```
|
|
651
667
|
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
668
|
+
**분석**:
|
|
669
|
+
1. **에러 위치**: `main.rs:8` (`data.push(4)`)
|
|
670
|
+
2. **에러 타입**: Borrow checker 위반 — 불변 참조가 있는데 가변 참조 시도
|
|
671
|
+
3. **근본 원인**: `first`가 `data`의 불변 참조를 보유한 상태에서 `data`를 변경하려 함
|
|
656
672
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
673
|
+
**코드**:
|
|
674
|
+
```rust
|
|
675
|
+
// main.rs
|
|
676
|
+
fn main() {
|
|
677
|
+
let mut data = vec![1, 2, 3];
|
|
678
|
+
let first = &data[0]; // 불변 참조
|
|
679
|
+
|
|
680
|
+
data.push(4); // 에러! 가변 참조 시도
|
|
681
|
+
|
|
682
|
+
println!("First: {}", first);
|
|
683
|
+
}
|
|
660
684
|
```
|
|
661
685
|
|
|
662
|
-
|
|
686
|
+
**디버깅 단계**:
|
|
663
687
|
```bash
|
|
664
|
-
#
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
# Run
|
|
668
|
-
./target/x86_64-unknown-linux-gnu/debug/myapp
|
|
688
|
+
# 1. rust-analyzer로 에러 확인 (VSCode)
|
|
689
|
+
# 에러 메시지에 lifetime 정보 포함
|
|
669
690
|
|
|
670
|
-
#
|
|
691
|
+
# 2. 컴파일러 설명 확인
|
|
692
|
+
cargo build --explain E0502
|
|
671
693
|
```
|
|
672
694
|
|
|
673
|
-
|
|
674
|
-
`Rc<RefCell<T>>` circular reference preventing drop.
|
|
675
|
-
|
|
695
|
+
**해결 방법**:
|
|
676
696
|
```rust
|
|
677
|
-
//
|
|
678
|
-
|
|
679
|
-
|
|
697
|
+
// Option 1: 참조 사용 범위 조정
|
|
698
|
+
fn main() {
|
|
699
|
+
let mut data = vec![1, 2, 3];
|
|
700
|
+
let first = data[0]; // 값 복사
|
|
701
|
+
|
|
702
|
+
data.push(4); // OK
|
|
703
|
+
|
|
704
|
+
println!("First: {}", first);
|
|
705
|
+
}
|
|
680
706
|
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
707
|
+
// Option 2: 참조를 먼저 해제
|
|
708
|
+
fn main() {
|
|
709
|
+
let mut data = vec![1, 2, 3];
|
|
710
|
+
{
|
|
711
|
+
let first = &data[0];
|
|
712
|
+
println!("First: {}", first);
|
|
713
|
+
} // first가 여기서 드롭됨
|
|
714
|
+
|
|
715
|
+
data.push(4); // OK
|
|
684
716
|
}
|
|
685
717
|
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
718
|
+
// Option 3: Clone
|
|
719
|
+
fn main() {
|
|
720
|
+
let mut data = vec![1, 2, 3];
|
|
721
|
+
let first = data.get(0).cloned(); // Option<i32>
|
|
722
|
+
|
|
723
|
+
data.push(4); // OK
|
|
724
|
+
|
|
725
|
+
if let Some(first) = first {
|
|
726
|
+
println!("First: {}", first);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
```
|
|
691
730
|
|
|
692
|
-
|
|
693
|
-
data: "Node 2".to_string(),
|
|
694
|
-
next: Some(Rc::clone(&node1)),
|
|
695
|
-
}));
|
|
731
|
+
---
|
|
696
732
|
|
|
697
|
-
|
|
698
|
-
} // node1 and node2 never dropped
|
|
733
|
+
### 예제 2: Panic in thread
|
|
699
734
|
|
|
700
|
-
|
|
701
|
-
|
|
735
|
+
**에러 메시지**:
|
|
736
|
+
```
|
|
737
|
+
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 5', src/main.rs:5:23
|
|
738
|
+
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
|
|
739
|
+
```
|
|
702
740
|
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
741
|
+
**분석**:
|
|
742
|
+
1. **에러 위치**: `main.rs:5` (인덱스 접근)
|
|
743
|
+
2. **에러 타입**: `panic: index out of bounds`
|
|
744
|
+
3. **근본 원인**: 벡터 범위를 벗어난 인덱스 접근
|
|
707
745
|
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
746
|
+
**코드**:
|
|
747
|
+
```rust
|
|
748
|
+
// main.rs
|
|
749
|
+
fn main() {
|
|
750
|
+
let data = vec![1, 2, 3];
|
|
751
|
+
let value = data[5]; // panic!
|
|
752
|
+
println!("Value: {}", value);
|
|
753
|
+
}
|
|
754
|
+
```
|
|
713
755
|
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
756
|
+
**디버깅 단계**:
|
|
757
|
+
```bash
|
|
758
|
+
# 1. RUST_BACKTRACE로 전체 스택 트레이스 확인
|
|
759
|
+
RUST_BACKTRACE=1 cargo run
|
|
760
|
+
# 또는
|
|
761
|
+
RUST_BACKTRACE=full cargo run
|
|
718
762
|
|
|
719
|
-
|
|
720
|
-
|
|
763
|
+
# 2. rust-lldb로 디버깅
|
|
764
|
+
rust-lldb target/debug/myapp
|
|
765
|
+
(lldb) breakpoint set --file main.rs --line 5
|
|
766
|
+
(lldb) run
|
|
767
|
+
(lldb) frame variable
|
|
768
|
+
(Vec<i32>) data = vec![1, 2, 3]
|
|
769
|
+
(lldb) print data.len()
|
|
770
|
+
3
|
|
721
771
|
```
|
|
722
772
|
|
|
723
|
-
|
|
773
|
+
**해결 방법**:
|
|
724
774
|
```rust
|
|
725
|
-
//
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
775
|
+
// Option 1: get() 메서드 사용
|
|
776
|
+
fn main() {
|
|
777
|
+
let data = vec![1, 2, 3];
|
|
778
|
+
match data.get(5) {
|
|
779
|
+
Some(value) => println!("Value: {}", value),
|
|
780
|
+
None => println!("Index out of bounds"),
|
|
781
|
+
}
|
|
782
|
+
}
|
|
729
783
|
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
784
|
+
// Option 2: 범위 체크
|
|
785
|
+
fn main() {
|
|
786
|
+
let data = vec![1, 2, 3];
|
|
787
|
+
let index = 5;
|
|
788
|
+
|
|
789
|
+
if index < data.len() {
|
|
790
|
+
let value = data[index];
|
|
791
|
+
println!("Value: {}", value);
|
|
792
|
+
} else {
|
|
793
|
+
println!("Index out of bounds");
|
|
794
|
+
}
|
|
795
|
+
}
|
|
733
796
|
|
|
734
|
-
|
|
735
|
-
|
|
797
|
+
// Option 3: unwrap_or 사용
|
|
798
|
+
fn main() {
|
|
799
|
+
let data = vec![1, 2, 3];
|
|
800
|
+
let value = data.get(5).unwrap_or(&0); // 기본값 0
|
|
801
|
+
println!("Value: {}", value);
|
|
736
802
|
}
|
|
737
803
|
```
|
|
738
804
|
|
|
739
805
|
---
|
|
740
806
|
|
|
741
|
-
##
|
|
807
|
+
## 컨테이너 디버깅 시나리오
|
|
742
808
|
|
|
743
|
-
###
|
|
744
|
-
Intermittent test failures; data corruption in concurrent writes.
|
|
809
|
+
### 시나리오 1: 컨테이너가 즉시 종료됨
|
|
745
810
|
|
|
746
|
-
|
|
747
|
-
```
|
|
748
|
-
|
|
749
|
-
|
|
811
|
+
**문제**:
|
|
812
|
+
```bash
|
|
813
|
+
$ docker ps -a
|
|
814
|
+
CONTAINER ID IMAGE STATUS
|
|
815
|
+
abc123 myapp Exited (1) 2 seconds ago
|
|
750
816
|
```
|
|
751
817
|
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
#### 1. Enable Race Detector
|
|
818
|
+
**디버깅 단계**:
|
|
755
819
|
```bash
|
|
756
|
-
#
|
|
757
|
-
|
|
820
|
+
# 1. 로그 확인
|
|
821
|
+
docker logs abc123
|
|
822
|
+
# Error: Database connection failed
|
|
758
823
|
|
|
759
|
-
#
|
|
760
|
-
|
|
761
|
-
WARNING: DATA RACE
|
|
762
|
-
Write at 0x00c0001a0180 by goroutine 7:
|
|
763
|
-
main.updateCounter()
|
|
764
|
-
/app/counter.go:23 +0x45
|
|
824
|
+
# 2. 컨테이너 재시작하지 않고 셸 접속
|
|
825
|
+
docker run --rm -it --entrypoint /bin/sh myapp
|
|
765
826
|
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
827
|
+
# 3. 환경 변수 확인
|
|
828
|
+
env | grep DB
|
|
829
|
+
# DB_HOST=localhost # 문제 발견! 컨테이너 내에서는 localhost가 아님
|
|
769
830
|
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
==================
|
|
831
|
+
# 4. 네트워크 확인
|
|
832
|
+
ping db-host
|
|
833
|
+
# ping: db-host: Name or service not known
|
|
774
834
|
```
|
|
775
835
|
|
|
776
|
-
|
|
836
|
+
**해결 방법**:
|
|
777
837
|
```bash
|
|
778
|
-
#
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
# Debug
|
|
782
|
-
dlv exec ./myapp
|
|
838
|
+
# Option 1: 환경 변수 수정
|
|
839
|
+
docker run -e DB_HOST=db-container myapp
|
|
783
840
|
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
841
|
+
# Option 2: Docker Compose로 네트워크 설정
|
|
842
|
+
# docker-compose.yml
|
|
843
|
+
version: '3'
|
|
844
|
+
services:
|
|
845
|
+
app:
|
|
846
|
+
image: myapp
|
|
847
|
+
environment:
|
|
848
|
+
DB_HOST: db
|
|
849
|
+
depends_on:
|
|
850
|
+
- db
|
|
851
|
+
db:
|
|
852
|
+
image: postgres:15
|
|
788
853
|
```
|
|
789
854
|
|
|
790
|
-
|
|
791
|
-
Unsynchronized access to shared counter.
|
|
792
|
-
|
|
793
|
-
```go
|
|
794
|
-
// Before (race condition)
|
|
795
|
-
var counter int
|
|
796
|
-
|
|
797
|
-
func updateCounter() {
|
|
798
|
-
counter++ // ❌ Not atomic
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
func getCounter() int {
|
|
802
|
-
return counter // ❌ Unsynchronized read
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
// After (fixed with mutex)
|
|
806
|
-
import "sync"
|
|
855
|
+
---
|
|
807
856
|
|
|
808
|
-
|
|
809
|
-
counter int
|
|
810
|
-
mu sync.RWMutex
|
|
811
|
-
)
|
|
857
|
+
### 시나리오 2: Kubernetes Pod CrashLoopBackOff
|
|
812
858
|
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
859
|
+
**문제**:
|
|
860
|
+
```bash
|
|
861
|
+
$ kubectl get pods
|
|
862
|
+
NAME READY STATUS RESTARTS
|
|
863
|
+
myapp-pod-abc123 0/1 CrashLoopBackOff 5
|
|
864
|
+
```
|
|
818
865
|
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
866
|
+
**디버깅 단계**:
|
|
867
|
+
```bash
|
|
868
|
+
# 1. Pod 설명 확인
|
|
869
|
+
kubectl describe pod myapp-pod-abc123
|
|
870
|
+
# Events:
|
|
871
|
+
# Warning BackOff kubelet Back-off restarting failed container
|
|
824
872
|
|
|
825
|
-
|
|
826
|
-
|
|
873
|
+
# 2. 로그 확인
|
|
874
|
+
kubectl logs myapp-pod-abc123
|
|
875
|
+
# panic: open /config/app.yaml: no such file or directory
|
|
827
876
|
|
|
828
|
-
|
|
877
|
+
# 3. 이전 컨테이너 로그 확인
|
|
878
|
+
kubectl logs myapp-pod-abc123 --previous
|
|
829
879
|
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
880
|
+
# 4. ConfigMap 확인
|
|
881
|
+
kubectl get configmap myapp-config -o yaml
|
|
882
|
+
# (ConfigMap이 없거나 잘못 마운트됨)
|
|
833
883
|
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
}
|
|
884
|
+
# 5. 볼륨 마운트 확인
|
|
885
|
+
kubectl get pod myapp-pod-abc123 -o yaml | grep -A 10 volumeMounts
|
|
837
886
|
```
|
|
838
887
|
|
|
839
|
-
|
|
840
|
-
```
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
888
|
+
**해결 방법**:
|
|
889
|
+
```yaml
|
|
890
|
+
# 1. ConfigMap 생성
|
|
891
|
+
apiVersion: v1
|
|
892
|
+
kind: ConfigMap
|
|
893
|
+
metadata:
|
|
894
|
+
name: myapp-config
|
|
895
|
+
data:
|
|
896
|
+
app.yaml: |
|
|
897
|
+
database:
|
|
898
|
+
host: db-service
|
|
899
|
+
port: 5432
|
|
900
|
+
|
|
901
|
+
# 2. Pod에 ConfigMap 마운트
|
|
902
|
+
apiVersion: v1
|
|
903
|
+
kind: Pod
|
|
904
|
+
metadata:
|
|
905
|
+
name: myapp-pod
|
|
906
|
+
spec:
|
|
907
|
+
containers:
|
|
908
|
+
- name: myapp
|
|
909
|
+
image: myapp:latest
|
|
910
|
+
volumeMounts:
|
|
911
|
+
- name: config
|
|
912
|
+
mountPath: /config
|
|
913
|
+
volumes:
|
|
914
|
+
- name: config
|
|
915
|
+
configMap:
|
|
916
|
+
name: myapp-config
|
|
863
917
|
```
|
|
864
918
|
|
|
865
919
|
---
|
|
866
920
|
|
|
867
|
-
##
|
|
921
|
+
## 분산 시스템 디버깅 시나리오
|
|
868
922
|
|
|
869
|
-
###
|
|
870
|
-
Production error: `Cannot read properties of undefined (reading 'name')`.
|
|
923
|
+
### 시나리오: 마이크로서비스 간 타임아웃
|
|
871
924
|
|
|
872
|
-
|
|
873
|
-
```
|
|
874
|
-
TypeError: Cannot read properties of undefined (reading 'name')
|
|
875
|
-
at getUserName (user.service.ts:15:23)
|
|
876
|
-
at processUser (user.controller.ts:42:10)
|
|
877
|
-
at async Router.handle (express.js:234:15)
|
|
878
|
-
```
|
|
879
|
-
|
|
880
|
-
### Investigation Steps
|
|
881
|
-
|
|
882
|
-
#### 1. Add Source Maps for Debugging
|
|
883
|
-
```json
|
|
884
|
-
// tsconfig.json
|
|
885
|
-
{
|
|
886
|
-
"compilerOptions": {
|
|
887
|
-
"sourceMap": true,
|
|
888
|
-
"inlineSourceMap": false,
|
|
889
|
-
"outDir": "./dist",
|
|
890
|
-
"rootDir": "./src"
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
```
|
|
925
|
+
**문제**: Service A → Service B 호출 시 타임아웃 발생
|
|
894
926
|
|
|
895
|
-
|
|
896
|
-
```bash
|
|
897
|
-
# Run with inspector
|
|
898
|
-
node --inspect -r ts-node/register src/app.ts
|
|
927
|
+
**디버깅 단계**:
|
|
899
928
|
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
929
|
+
**1. OpenTelemetry로 트레이스 수집**:
|
|
930
|
+
```python
|
|
931
|
+
# Service A
|
|
932
|
+
from opentelemetry import trace
|
|
903
933
|
|
|
904
|
-
|
|
905
|
-
```json
|
|
906
|
-
{
|
|
907
|
-
"version": "0.2.0",
|
|
908
|
-
"configurations": [
|
|
909
|
-
{
|
|
910
|
-
"type": "node",
|
|
911
|
-
"request": "launch",
|
|
912
|
-
"name": "TypeScript: Debug",
|
|
913
|
-
"runtimeArgs": ["-r", "ts-node/register"],
|
|
914
|
-
"args": ["${file}"],
|
|
915
|
-
"cwd": "${workspaceFolder}",
|
|
916
|
-
"sourceMaps": true
|
|
917
|
-
}
|
|
918
|
-
]
|
|
919
|
-
}
|
|
920
|
-
```
|
|
934
|
+
tracer = trace.get_tracer(__name__)
|
|
921
935
|
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
// Set breakpoint here
|
|
927
|
-
debugger;
|
|
928
|
-
return user.name; // user is undefined!
|
|
929
|
-
}
|
|
936
|
+
with tracer.start_as_current_span("call-service-b") as span:
|
|
937
|
+
response = requests.get("http://service-b/api/data", timeout=5)
|
|
938
|
+
span.set_attribute("http.status_code", response.status_code)
|
|
939
|
+
```
|
|
930
940
|
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
941
|
+
**2. Jaeger UI에서 트레이스 분석**:
|
|
942
|
+
```
|
|
943
|
+
Trace: request-123
|
|
944
|
+
├─ Service A: call-service-b (50ms)
|
|
945
|
+
│ └─ HTTP GET http://service-b/api/data
|
|
946
|
+
│ ├─ DNS lookup: 10ms
|
|
947
|
+
│ ├─ TCP connect: 15ms
|
|
948
|
+
│ └─ Waiting for response: 5000ms ← 타임아웃!
|
|
949
|
+
└─ Service B: process-request (4950ms)
|
|
950
|
+
├─ Database query: 4900ms ← 병목!
|
|
951
|
+
└─ Response serialization: 50ms
|
|
936
952
|
```
|
|
937
953
|
|
|
938
|
-
|
|
939
|
-
|
|
954
|
+
**3. 근본 원인 식별**:
|
|
955
|
+
- Service B의 데이터베이스 쿼리가 4.9초 소요
|
|
956
|
+
- Service A의 타임아웃이 5초로 설정되어 있어 경합 상태 발생
|
|
940
957
|
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
console.log(name);
|
|
947
|
-
}
|
|
958
|
+
**해결 방법**:
|
|
959
|
+
```python
|
|
960
|
+
# Option 1: Service B의 쿼리 최적화
|
|
961
|
+
# 인덱스 추가
|
|
962
|
+
CREATE INDEX idx_user_email ON users(email);
|
|
948
963
|
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
const user = await fetchUser(userId); // ✅ Await promise
|
|
952
|
-
if (!user) {
|
|
953
|
-
throw new Error(`User ${userId} not found`);
|
|
954
|
-
}
|
|
955
|
-
const name = getUserName(user);
|
|
956
|
-
console.log(name);
|
|
957
|
-
}
|
|
964
|
+
# Option 2: Service A의 타임아웃 증가
|
|
965
|
+
response = requests.get("http://service-b/api/data", timeout=10)
|
|
958
966
|
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
return user?.name ?? 'Unknown'; // ✅ Safe access
|
|
962
|
-
}
|
|
963
|
-
```
|
|
967
|
+
# Option 3: 캐싱 추가
|
|
968
|
+
from redis import Redis
|
|
964
969
|
|
|
965
|
-
|
|
966
|
-
```typescript
|
|
967
|
-
// user.service.test.ts
|
|
968
|
-
describe('getUserName', () => {
|
|
969
|
-
it('should handle undefined user gracefully', () => {
|
|
970
|
-
const result = getUserName(undefined);
|
|
971
|
-
expect(result).toBe('Unknown');
|
|
972
|
-
});
|
|
970
|
+
cache = Redis(host='redis', port=6379)
|
|
973
971
|
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
972
|
+
def get_data():
|
|
973
|
+
cached = cache.get('data')
|
|
974
|
+
if cached:
|
|
975
|
+
return cached
|
|
976
|
+
|
|
977
|
+
data = expensive_database_query()
|
|
978
|
+
cache.setex('data', 300, data) # 5분 캐시
|
|
979
|
+
return data
|
|
980
980
|
```
|
|
981
981
|
|
|
982
982
|
---
|
|
983
983
|
|
|
984
|
-
##
|
|
984
|
+
## 성능 디버깅 시나리오
|
|
985
985
|
|
|
986
|
-
###
|
|
987
|
-
Dashboard loads slowly; query taking 5+ seconds.
|
|
986
|
+
### 시나리오: 느린 API 응답
|
|
988
987
|
|
|
989
|
-
|
|
988
|
+
**문제**: API 엔드포인트 응답 시간이 3초 이상
|
|
990
989
|
|
|
991
|
-
|
|
992
|
-
```sql
|
|
993
|
-
-- PostgreSQL: Enable slow query log
|
|
994
|
-
ALTER SYSTEM SET log_min_duration_statement = 1000; -- 1 second
|
|
995
|
-
SELECT pg_reload_conf();
|
|
990
|
+
**디버깅 단계**:
|
|
996
991
|
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
ORDER BY total_time DESC
|
|
1001
|
-
LIMIT 10;
|
|
992
|
+
**1. py-spy로 CPU 프로파일링** (Python):
|
|
993
|
+
```bash
|
|
994
|
+
py-spy record -o profile.svg --pid <pid>
|
|
1002
995
|
```
|
|
1003
996
|
|
|
1004
|
-
|
|
1005
|
-
```
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
GROUP BY u.id, u.name
|
|
1013
|
-
ORDER BY order_count DESC;
|
|
1014
|
-
|
|
1015
|
-
-- Output:
|
|
1016
|
-
Sort (cost=15234.56..15235.56 rows=400 width=16) (actual time=5234.123..5234.234 rows=1000 loops=1)
|
|
1017
|
-
-> HashAggregate (cost=15210.00..15214.00 rows=400 width=16) (actual time=5233.456..5233.789 rows=1000 loops=1)
|
|
1018
|
-
-> Hash Left Join (cost=1234.00..12345.00 rows=286500 width=8) (actual time=123.456..4567.890 rows=500000 loops=1)
|
|
1019
|
-
-> Seq Scan on users u (cost=0.00..1245.00 rows=50000 width=8) (actual time=0.012..234.567 rows=50000 loops=1)
|
|
1020
|
-
Filter: (created_at > '2024-01-01'::date)
|
|
1021
|
-
-> Hash (cost=800.00..800.00 rows=30000 width=8) (actual time=123.444..123.444 rows=30000 loops=1)
|
|
1022
|
-
-> Seq Scan on orders o (cost=0.00..800.00 rows=30000 width=8) (actual time=0.010..89.123 rows=30000 loops=1)
|
|
1023
|
-
Planning Time: 1.234 ms
|
|
1024
|
-
Execution Time: 5234.567 ms ← SLOW!
|
|
1025
|
-
```
|
|
1026
|
-
|
|
1027
|
-
#### 3. Identify Missing Indexes
|
|
1028
|
-
```sql
|
|
1029
|
-
-- Check existing indexes
|
|
1030
|
-
SELECT tablename, indexname, indexdef
|
|
1031
|
-
FROM pg_indexes
|
|
1032
|
-
WHERE tablename IN ('users', 'orders');
|
|
1033
|
-
|
|
1034
|
-
-- Missing:
|
|
1035
|
-
-- 1. Index on users.created_at
|
|
1036
|
-
-- 2. Index on orders.user_id
|
|
997
|
+
**2. Flamegraph 분석**:
|
|
998
|
+
```
|
|
999
|
+
main() [100%]
|
|
1000
|
+
├─ process_request() [95%]
|
|
1001
|
+
│ ├─ load_users() [80%] ← 병목!
|
|
1002
|
+
│ │ └─ database.query() [78%]
|
|
1003
|
+
│ └─ serialize_response() [15%]
|
|
1004
|
+
└─ logging() [5%]
|
|
1037
1005
|
```
|
|
1038
1006
|
|
|
1039
|
-
|
|
1007
|
+
**3. 데이터베이스 쿼리 분석**:
|
|
1040
1008
|
```sql
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
-- Re-run EXPLAIN ANALYZE
|
|
1046
|
-
EXPLAIN ANALYZE
|
|
1047
|
-
SELECT u.name, COUNT(o.id) as order_count
|
|
1048
|
-
FROM users u
|
|
1049
|
-
LEFT JOIN orders o ON u.id = o.user_id
|
|
1050
|
-
WHERE u.created_at > '2024-01-01'
|
|
1051
|
-
GROUP BY u.id, u.name
|
|
1052
|
-
ORDER BY order_count DESC;
|
|
1053
|
-
|
|
1054
|
-
-- Output (improved):
|
|
1055
|
-
Sort (cost=234.56..235.56 rows=400 width=16) (actual time=45.123..45.234 rows=1000 loops=1)
|
|
1056
|
-
-> HashAggregate (cost=210.00..214.00 rows=400 width=16) (actual time=44.456..44.789 rows=1000 loops=1)
|
|
1057
|
-
-> Hash Left Join (cost=123.00..200.00 rows=5000 width=8) (actual time=12.345..40.567 rows=5000 loops=1)
|
|
1058
|
-
-> Index Scan using idx_users_created_at on users u (cost=0.42..100.00 rows=5000 width=8) (actual time=0.023..15.234 rows=5000 loops=1)
|
|
1059
|
-
Index Cond: (created_at > '2024-01-01'::date)
|
|
1060
|
-
-> Hash (cost=80.00..80.00 rows=3000 width=8) (actual time=12.321..12.321 rows=3000 loops=1)
|
|
1061
|
-
-> Index Scan using idx_orders_user_id on orders o (cost=0.42..80.00 rows=3000 width=8) (actual time=0.012..8.123 rows=3000 loops=1)
|
|
1062
|
-
Execution Time: 45.567 ms ← 100x FASTER!
|
|
1063
|
-
```
|
|
1064
|
-
|
|
1065
|
-
### Root Cause
|
|
1066
|
-
Missing indexes on frequently queried columns (`users.created_at`, `orders.user_id`).
|
|
1067
|
-
|
|
1068
|
-
### Test Case
|
|
1069
|
-
```python
|
|
1070
|
-
# tests/test_query_performance.py
|
|
1071
|
-
import pytest
|
|
1072
|
-
import time
|
|
1073
|
-
|
|
1074
|
-
def test_dashboard_query_performance(db):
|
|
1075
|
-
"""Ensure dashboard query completes within 100ms."""
|
|
1076
|
-
query = """
|
|
1077
|
-
SELECT u.name, COUNT(o.id) as order_count
|
|
1078
|
-
FROM users u
|
|
1079
|
-
LEFT JOIN orders o ON u.id = o.user_id
|
|
1080
|
-
WHERE u.created_at > '2024-01-01'
|
|
1081
|
-
GROUP BY u.id, u.name
|
|
1082
|
-
ORDER BY order_count DESC
|
|
1083
|
-
"""
|
|
1009
|
+
EXPLAIN ANALYZE SELECT * FROM users WHERE status = 'active';
|
|
1010
|
+
-- Seq Scan on users (cost=0.00..1234.56)
|
|
1011
|
+
-- 인덱스 없음!
|
|
1012
|
+
```
|
|
1084
1013
|
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1014
|
+
**4. 해결 방법**:
|
|
1015
|
+
```sql
|
|
1016
|
+
-- 인덱스 추가
|
|
1017
|
+
CREATE INDEX idx_users_status ON users(status);
|
|
1088
1018
|
|
|
1089
|
-
|
|
1090
|
-
|
|
1019
|
+
-- 쿼리 재실행
|
|
1020
|
+
EXPLAIN ANALYZE SELECT * FROM users WHERE status = 'active';
|
|
1021
|
+
-- Index Scan using idx_users_status (cost=0.28..45.67)
|
|
1022
|
+
-- 응답 시간: 3초 → 50ms
|
|
1091
1023
|
```
|
|
1092
1024
|
|
|
1093
1025
|
---
|
|
1094
1026
|
|
|
1095
|
-
##
|
|
1027
|
+
## 요약: 디버깅 체크리스트
|
|
1028
|
+
|
|
1029
|
+
### 1. 재현 (Reproduce)
|
|
1030
|
+
- [ ] 최소 재현 예제 (MRE) 작성
|
|
1031
|
+
- [ ] 일관된 재현 단계 문서화
|
|
1032
|
+
- [ ] 환경 정보 기록 (OS, 언어 버전, 의존성)
|
|
1033
|
+
|
|
1034
|
+
### 2. 격리 (Isolate)
|
|
1035
|
+
- [ ] 이진 탐색으로 문제 범위 좁히기
|
|
1036
|
+
- [ ] 최근 변경사항 확인 (git diff, git log)
|
|
1037
|
+
- [ ] 입력 데이터 및 엣지 케이스 검증
|
|
1038
|
+
|
|
1039
|
+
### 3. 조사 (Investigate)
|
|
1040
|
+
- [ ] 스택 트레이스를 아래에서 위로 읽기
|
|
1041
|
+
- [ ] 주요 결정 지점에 로깅 추가
|
|
1042
|
+
- [ ] 에러 위치 이전에 브레이크포인트 설정
|
|
1043
|
+
- [ ] 디버거에서 변수 상태 확인
|
|
1044
|
+
|
|
1045
|
+
### 4. 가설 (Hypothesize)
|
|
1046
|
+
- [ ] 근본 원인에 대한 이론 수립
|
|
1047
|
+
- [ ] 가장 가능성 높은 원인 2-3개 식별
|
|
1048
|
+
- [ ] 가설 검증 실험 설계
|
|
1049
|
+
|
|
1050
|
+
### 5. 수정 (Fix)
|
|
1051
|
+
- [ ] 최소한의 수정 먼저 구현
|
|
1052
|
+
- [ ] 회귀 테스트 추가 (RED → GREEN)
|
|
1053
|
+
- [ ] 필요시 리팩토링 (REFACTOR 단계)
|
|
1054
|
+
- [ ] 문서 업데이트
|
|
1055
|
+
|
|
1056
|
+
### 6. 검증 (Verify)
|
|
1057
|
+
- [ ] 전체 테스트 스위트 실행
|
|
1058
|
+
- [ ] 엣지 케이스 명시적 테스트
|
|
1059
|
+
- [ ] 프로덕션 유사 환경에서 수정 검증
|
|
1060
|
+
- [ ] 재발 모니터링
|
|
1096
1061
|
|
|
1097
|
-
|
|
1098
|
-
1. **Async debugging** with Python debugpy and asyncio inspection
|
|
1099
|
-
2. **Goroutine leak detection** with Delve and pprof
|
|
1100
|
-
3. **Distributed tracing** with OpenTelemetry across microservices
|
|
1101
|
-
4. **Kubernetes debugging** with ephemeral containers and memory profiling
|
|
1102
|
-
5. **Rust memory leaks** using Valgrind and Weak references
|
|
1103
|
-
6. **Go race conditions** with built-in race detector
|
|
1104
|
-
7. **TypeScript null pointers** with optional chaining and type guards
|
|
1105
|
-
8. **SQL performance** with EXPLAIN ANALYZE and index optimization
|
|
1062
|
+
---
|
|
1106
1063
|
|
|
1107
|
-
|
|
1064
|
+
**End of Examples** | moai-essentials-debug v2.1.0
|