@wooojin/forgen 0.4.8 → 0.4.9
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.
- package/.claude-plugin/plugin.json +1 -1
- package/assets/dev-guide/be/README.md +226 -0
- package/assets/dev-guide/be/adapters/build-agents-md.sh +63 -0
- package/assets/dev-guide/be/principles/common.md +433 -0
- package/assets/dev-guide/be/principles/go.md +469 -0
- package/assets/dev-guide/be/principles/node.md +388 -0
- package/assets/dev-guide/be/skills/go/be-build/SKILL.md +262 -0
- package/assets/dev-guide/be/skills/go/be-perf/SKILL.md +308 -0
- package/assets/dev-guide/be/skills/go/be-review/SKILL.md +119 -0
- package/assets/dev-guide/be/skills/go/be-security/SKILL.md +362 -0
- package/assets/dev-guide/be/skills/node/be-build/SKILL.md +239 -0
- package/assets/dev-guide/be/skills/node/be-perf/SKILL.md +272 -0
- package/assets/dev-guide/be/skills/node/be-review/SKILL.md +118 -0
- package/assets/dev-guide/be/skills/node/be-security/SKILL.md +355 -0
- package/assets/dev-guide/be/sources/12factor/INDEX.md +53 -0
- package/assets/dev-guide/be/sources/api-design/INDEX.md +56 -0
- package/assets/dev-guide/be/sources/ddia/INDEX.md +55 -0
- package/assets/dev-guide/be/sources/go-runtime/INDEX.md +62 -0
- package/assets/dev-guide/be/sources/node-runtime/INDEX.md +60 -0
- package/assets/dev-guide/be/sources/otel/INDEX.md +53 -0
- package/assets/dev-guide/be/sources/owasp-api/INDEX.md +52 -0
- package/assets/dev-guide/be/sources/postgres/INDEX.md +55 -0
- package/assets/dev-guide/be/sources/sre-book/INDEX.md +48 -0
- package/assets/dev-guide/fe/README.md +197 -0
- package/assets/dev-guide/fe/adapters/build-agents-md.sh +63 -0
- package/assets/dev-guide/fe/adapters/refresh.sh +68 -0
- package/assets/dev-guide/fe/principles/common.md +160 -0
- package/assets/dev-guide/fe/principles/react.md +183 -0
- package/assets/dev-guide/fe/principles/vue.md +196 -0
- package/assets/dev-guide/fe/skills/react/fe-build/SKILL.md +139 -0
- package/assets/dev-guide/fe/skills/react/fe-perf/SKILL.md +179 -0
- package/assets/dev-guide/fe/skills/react/fe-review/SKILL.md +141 -0
- package/assets/dev-guide/fe/skills/vue/fe-build/SKILL.md +148 -0
- package/assets/dev-guide/fe/skills/vue/fe-perf/SKILL.md +163 -0
- package/assets/dev-guide/fe/skills/vue/fe-review/SKILL.md +136 -0
- package/assets/dev-guide/fe/sources/a11y-dx/INDEX.md +41 -0
- package/assets/dev-guide/fe/sources/a11y-dx/chrome-devtools-memory.md +150 -0
- package/assets/dev-guide/fe/sources/a11y-dx/chrome-devtools-performance.md +99 -0
- package/assets/dev-guide/fe/sources/a11y-dx/lighthouse-audits.md +146 -0
- package/assets/dev-guide/fe/sources/a11y-dx/react-devtools-profiler.md +128 -0
- package/assets/dev-guide/fe/sources/a11y-dx/wcag22-new-criteria.md +174 -0
- package/assets/dev-guide/fe/sources/perf/01-core-web-vitals.md +58 -0
- package/assets/dev-guide/fe/sources/perf/02-inp.md +83 -0
- package/assets/dev-guide/fe/sources/perf/03-lcp-cls.md +130 -0
- package/assets/dev-guide/fe/sources/perf/04-speculation-rules.md +148 -0
- package/assets/dev-guide/fe/sources/perf/05-view-transitions.md +153 -0
- package/assets/dev-guide/fe/sources/perf/06-nextjs-caching.md +188 -0
- package/assets/dev-guide/fe/sources/perf/07-server-components.md +181 -0
- package/assets/dev-guide/fe/sources/perf/08-ppr.md +133 -0
- package/assets/dev-guide/fe/sources/perf/09-nextjs-image.md +200 -0
- package/assets/dev-guide/fe/sources/perf/10-optimize-lcp.md +201 -0
- package/assets/dev-guide/fe/sources/perf/INDEX.md +88 -0
- package/assets/dev-guide/fe/sources/react/INDEX.md +41 -0
- package/assets/dev-guide/fe/sources/react/keeping-components-pure.md +135 -0
- package/assets/dev-guide/fe/sources/react/no-effect-patterns.md +183 -0
- package/assets/dev-guide/fe/sources/react/react-compiler.md +182 -0
- package/assets/dev-guide/fe/sources/react/server-components.md +194 -0
- package/assets/dev-guide/fe/sources/react/server-functions.md +192 -0
- package/assets/dev-guide/fe/sources/react/suspense.md +218 -0
- package/assets/dev-guide/fe/sources/react/use-action-state.md +123 -0
- package/assets/dev-guide/fe/sources/react/use-form-status.md +158 -0
- package/assets/dev-guide/fe/sources/react/use-hook.md +153 -0
- package/assets/dev-guide/fe/sources/react/use-optimistic.md +194 -0
- package/assets/dev-guide/fe/sources/toss-ff/INDEX.md +58 -0
- package/assets/dev-guide/fe/sources/toss-ff/cohesion-code-directory.md +79 -0
- package/assets/dev-guide/fe/sources/toss-ff/cohesion-form-fields.md +110 -0
- package/assets/dev-guide/fe/sources/toss-ff/cohesion-magic-number.md +47 -0
- package/assets/dev-guide/fe/sources/toss-ff/coupling-item-edit-modal.md +124 -0
- package/assets/dev-guide/fe/sources/toss-ff/coupling-use-bottom-sheet.md +57 -0
- package/assets/dev-guide/fe/sources/toss-ff/coupling-use-page-state.md +71 -0
- package/assets/dev-guide/fe/sources/toss-ff/overview-4-principles.md +77 -0
- package/assets/dev-guide/fe/sources/toss-ff/predictability-hidden-logic.md +59 -0
- package/assets/dev-guide/fe/sources/toss-ff/predictability-http.md +77 -0
- package/assets/dev-guide/fe/sources/toss-ff/predictability-use-user.md +110 -0
- package/assets/dev-guide/fe/sources/toss-ff/readability-comparison-order.md +52 -0
- package/assets/dev-guide/fe/sources/toss-ff/readability-condition-name.md +64 -0
- package/assets/dev-guide/fe/sources/toss-ff/readability-login-start-page.md +183 -0
- package/assets/dev-guide/fe/sources/toss-ff/readability-magic-number.md +53 -0
- package/assets/dev-guide/fe/sources/toss-ff/readability-submit-button.md +73 -0
- package/assets/dev-guide/fe/sources/toss-ff/readability-ternary-operator.md +38 -0
- package/assets/dev-guide/fe/sources/toss-ff/readability-use-page-state.md +77 -0
- package/assets/dev-guide/fe/sources/toss-ff/readability-user-policy.md +98 -0
- package/assets/dev-guide/fe/sources/vue/INDEX.md +17 -0
- package/assets/dev-guide/fe/sources/vue/composition-api.md +251 -0
- package/assets/dev-guide/fe/sources/vue/nuxt-data-fetching.md +232 -0
- package/assets/dev-guide/fe/sources/vue/pinia-state-management.md +134 -0
- package/assets/dev-guide/fe/sources/vue/reactivity-pitfalls.md +261 -0
- package/assets/dev-guide/fe/sources/vue/style-guide-priority-a.md +117 -0
- package/assets/dev-guide/fe/sources/vue/style-guide-priority-b.md +231 -0
- package/assets/dev-guide/fe/sources/vue/style-guide-priority-c.md +86 -0
- package/assets/dev-guide/fe/sources/vue/style-guide-priority-d.md +72 -0
- package/dist/cli.js +42 -0
- package/dist/core/dashboard-cli.d.ts +12 -0
- package/dist/core/dashboard-cli.js +226 -0
- package/dist/core/dev-guide-injector.d.ts +26 -0
- package/dist/core/dev-guide-injector.js +137 -0
- package/dist/core/init.js +53 -0
- package/dist/core/lifecycle-classifier.d.ts +23 -0
- package/dist/core/lifecycle-classifier.js +104 -0
- package/dist/core/observability-backfill.d.ts +31 -0
- package/dist/core/observability-backfill.js +178 -0
- package/dist/core/observability-store.d.ts +58 -0
- package/dist/core/observability-store.js +195 -0
- package/dist/core/session-store.js +4 -0
- package/dist/core/spawn.d.ts +17 -0
- package/dist/core/spawn.js +179 -2
- package/dist/core/statusline-cli.js +34 -1
- package/dist/engine/compound-extractor.js +39 -0
- package/dist/engine/compound-loop.js +6 -0
- package/dist/engine/compound-retire.d.ts +20 -0
- package/dist/engine/compound-retire.js +85 -0
- package/dist/hooks/context-guard.js +25 -1
- package/dist/hooks/post-tool-use.js +48 -0
- package/dist/hooks/solution-injector.js +93 -0
- package/dist/host/install-claude.d.ts +6 -2
- package/dist/host/install-claude.js +74 -2
- package/dist/host/install-codex.d.ts +4 -0
- package/dist/host/install-codex.js +71 -0
- package/dist/host/install-orchestrator.js +1 -0
- package/package.json +6 -6
- package/plugin.json +1 -1
- package/scripts/postinstall.js +134 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: be-perf-go
|
|
3
|
+
description: Go 서비스의 p95/p99 성능 문제를 진단하고 수정. DB N+1, GC pause, goroutine leak, lock contention, allocation 최적화, network roundtrip 카테고리별 절차로 접근한다.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# be-perf (Go)
|
|
7
|
+
|
|
8
|
+
> **호출 시점**: "p99가 800ms야 잡아줘", "GC pause가 심해", "goroutine 수가 계속 올라가", "메모리 사용량이 이상해".
|
|
9
|
+
> **선행 로딩**: `principles/common.md` + `principles/go.md` 필수.
|
|
10
|
+
|
|
11
|
+
## 0. 절대 금지
|
|
12
|
+
|
|
13
|
+
1. 측정 없이 최적화 추측 금지 — pprof 데이터 없이 "아마 GC겠지"는 근거 없음.
|
|
14
|
+
2. p50만 보고 OK 선언 금지 — p95/p99 반드시 확인.
|
|
15
|
+
3. unsafe 패키지 성능 최적화 목적 사용 금지 — 이득이 미미하고 버그 위험 높음.
|
|
16
|
+
|
|
17
|
+
## 1. 진단 절차
|
|
18
|
+
|
|
19
|
+
### Step 1 — 현재 지표 수집
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# pprof 활성화 (프로덕션에서는 조건부로만)
|
|
23
|
+
import _ "net/http/pprof"
|
|
24
|
+
go func() { http.ListenAndServe(":6060", nil) }()
|
|
25
|
+
|
|
26
|
+
# 30초 CPU 프로파일 수집
|
|
27
|
+
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
|
|
28
|
+
|
|
29
|
+
# 메모리 프로파일
|
|
30
|
+
go tool pprof http://localhost:6060/debug/pprof/heap
|
|
31
|
+
|
|
32
|
+
# goroutine 덤프
|
|
33
|
+
curl http://localhost:6060/debug/pprof/goroutine?debug=2
|
|
34
|
+
|
|
35
|
+
# 벤치마크 기준선
|
|
36
|
+
go test -bench=. -benchmem -count=5 ./...
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Step 2 — 병목 카테고리 분류
|
|
40
|
+
|
|
41
|
+
| 증상 | 카테고리 |
|
|
42
|
+
|------|----------|
|
|
43
|
+
| pprof CPU에서 GC 관련 함수 상위 | GC pause / allocation 과다 |
|
|
44
|
+
| goroutine 수 지속 증가 | goroutine leak |
|
|
45
|
+
| 특정 goroutine이 mutex 대기 | lock contention |
|
|
46
|
+
| DB slow query | N+1 / 인덱스 누락 |
|
|
47
|
+
| 외부 API 대기 | network roundtrip |
|
|
48
|
+
|
|
49
|
+
## 2. 카테고리별 진단 및 픽스
|
|
50
|
+
|
|
51
|
+
### 2.1 DB N+1
|
|
52
|
+
|
|
53
|
+
```go
|
|
54
|
+
// WRONG: N+1
|
|
55
|
+
orders, _ := repo.FindOrders(ctx)
|
|
56
|
+
for _, o := range orders {
|
|
57
|
+
o.User, _ = repo.FindUser(ctx, o.UserID) // N번 쿼리
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// RIGHT: JOIN 또는 IN 쿼리
|
|
61
|
+
orders, _ := repo.FindOrdersWithUsers(ctx)
|
|
62
|
+
// SELECT o.*, u.name FROM orders o JOIN users u ON o.user_id = u.id
|
|
63
|
+
|
|
64
|
+
// 또는 IN 쿼리
|
|
65
|
+
userIDs := make([]string, len(orders))
|
|
66
|
+
for i, o := range orders { userIDs[i] = o.UserID }
|
|
67
|
+
users, _ := repo.FindUsersByIDs(ctx, userIDs)
|
|
68
|
+
// SELECT * FROM users WHERE id = ANY($1)
|
|
69
|
+
userMap := make(map[string]*User, len(users))
|
|
70
|
+
for _, u := range users { userMap[u.ID] = u }
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 2.2 GC Pause / 과도한 Allocation
|
|
74
|
+
|
|
75
|
+
**탐지**: pprof heap 에서 alloc_objects 상위 함수 확인.
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
go tool pprof -alloc_objects http://localhost:6060/debug/pprof/heap
|
|
79
|
+
# top 10 으로 allocation 많은 함수 확인
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**픽스**:
|
|
83
|
+
```go
|
|
84
|
+
// sync.Pool로 자주 할당하는 버퍼 재사용
|
|
85
|
+
var bufPool = sync.Pool{New: func() interface{} { return new(bytes.Buffer) }}
|
|
86
|
+
|
|
87
|
+
func encodeJSON(v interface{}) ([]byte, error) {
|
|
88
|
+
buf := bufPool.Get().(*bytes.Buffer)
|
|
89
|
+
defer func() { buf.Reset(); bufPool.Put(buf) }()
|
|
90
|
+
if err := json.NewEncoder(buf).Encode(v); err != nil {
|
|
91
|
+
return nil, err
|
|
92
|
+
}
|
|
93
|
+
return append([]byte(nil), buf.Bytes()...), nil
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 슬라이스 용량 사전 할당
|
|
97
|
+
func buildResult(items []Item) []Result {
|
|
98
|
+
results := make([]Result, 0, len(items)) // capacity 미리 지정
|
|
99
|
+
for _, item := range items {
|
|
100
|
+
results = append(results, transform(item))
|
|
101
|
+
}
|
|
102
|
+
return results
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 문자열 빌더 (+ concat 대신)
|
|
106
|
+
var sb strings.Builder
|
|
107
|
+
sb.Grow(len(parts) * 20) // 예상 크기 미리 할당
|
|
108
|
+
for _, p := range parts {
|
|
109
|
+
sb.WriteString(p)
|
|
110
|
+
}
|
|
111
|
+
result := sb.String()
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 2.3 Goroutine Leak
|
|
115
|
+
|
|
116
|
+
**탐지**:
|
|
117
|
+
```bash
|
|
118
|
+
# goroutine 덤프 - 비정상적으로 많으면 leak
|
|
119
|
+
curl -s http://localhost:6060/debug/pprof/goroutine?debug=2 | head -50
|
|
120
|
+
# 또는 goleak 라이브러리 (테스트에서)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
```go
|
|
124
|
+
// 테스트에서 leak 탐지
|
|
125
|
+
import "go.uber.org/goleak"
|
|
126
|
+
|
|
127
|
+
func TestCreateOrder(t *testing.T) {
|
|
128
|
+
defer goleak.VerifyNone(t)
|
|
129
|
+
// ...
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**픽스**:
|
|
134
|
+
```go
|
|
135
|
+
// WRONG: 종료 조건 없는 goroutine
|
|
136
|
+
func startWorker() {
|
|
137
|
+
go func() {
|
|
138
|
+
for {
|
|
139
|
+
processJob() // context 없음 — 영원히 실행
|
|
140
|
+
}
|
|
141
|
+
}()
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// RIGHT: context 기반 종료
|
|
145
|
+
func startWorker(ctx context.Context) {
|
|
146
|
+
go func() {
|
|
147
|
+
for {
|
|
148
|
+
select {
|
|
149
|
+
case <-ctx.Done():
|
|
150
|
+
return
|
|
151
|
+
default:
|
|
152
|
+
processJob(ctx)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}()
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// 고정 크기 worker pool
|
|
159
|
+
func NewWorkerPool(ctx context.Context, n int, jobs <-chan Job) {
|
|
160
|
+
var wg sync.WaitGroup
|
|
161
|
+
for i := 0; i < n; i++ {
|
|
162
|
+
wg.Add(1)
|
|
163
|
+
go func() {
|
|
164
|
+
defer wg.Done()
|
|
165
|
+
for {
|
|
166
|
+
select {
|
|
167
|
+
case job, ok := <-jobs:
|
|
168
|
+
if !ok { return }
|
|
169
|
+
job.Execute(ctx)
|
|
170
|
+
case <-ctx.Done():
|
|
171
|
+
return
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}()
|
|
175
|
+
}
|
|
176
|
+
wg.Wait()
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### 2.4 Lock Contention
|
|
181
|
+
|
|
182
|
+
**탐지**: pprof mutex 프로파일.
|
|
183
|
+
```bash
|
|
184
|
+
go tool pprof http://localhost:6060/debug/pprof/mutex
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
**픽스**:
|
|
188
|
+
```go
|
|
189
|
+
// sync.RWMutex: 읽기 많고 쓰기 적을 때
|
|
190
|
+
type Cache struct {
|
|
191
|
+
mu sync.RWMutex
|
|
192
|
+
items map[string]Item
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
func (c *Cache) Get(key string) (Item, bool) {
|
|
196
|
+
c.mu.RLock() // 읽기 lock (다중 동시 읽기 허용)
|
|
197
|
+
defer c.mu.RUnlock()
|
|
198
|
+
item, ok := c.items[key]
|
|
199
|
+
return item, ok
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
func (c *Cache) Set(key string, item Item) {
|
|
203
|
+
c.mu.Lock() // 쓰기 lock (단독)
|
|
204
|
+
defer c.mu.Unlock()
|
|
205
|
+
c.items[key] = item
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// atomic 연산 (카운터)
|
|
209
|
+
import "sync/atomic"
|
|
210
|
+
var requestCount int64
|
|
211
|
+
atomic.AddInt64(&requestCount, 1) // mutex 불필요
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### 2.5 Network Roundtrip
|
|
215
|
+
|
|
216
|
+
```go
|
|
217
|
+
// HTTP 클라이언트 연결 풀 설정
|
|
218
|
+
transport := &http.Transport{
|
|
219
|
+
MaxIdleConns: 100,
|
|
220
|
+
MaxIdleConnsPerHost: 10,
|
|
221
|
+
IdleConnTimeout: 90 * time.Second,
|
|
222
|
+
// Keep-Alive 기본 활성화
|
|
223
|
+
}
|
|
224
|
+
client := &http.Client{
|
|
225
|
+
Transport: transport,
|
|
226
|
+
Timeout: 5 * time.Second,
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// 독립적인 외부 호출 병렬화
|
|
230
|
+
g, ctx := errgroup.WithContext(ctx)
|
|
231
|
+
var user *User
|
|
232
|
+
var inventory *Inventory
|
|
233
|
+
|
|
234
|
+
g.Go(func() error {
|
|
235
|
+
var err error
|
|
236
|
+
user, err = userClient.GetUser(ctx, userID)
|
|
237
|
+
return err
|
|
238
|
+
})
|
|
239
|
+
g.Go(func() error {
|
|
240
|
+
var err error
|
|
241
|
+
inventory, err = inventoryClient.GetStock(ctx, productID)
|
|
242
|
+
return err
|
|
243
|
+
})
|
|
244
|
+
if err := g.Wait(); err != nil { ... }
|
|
245
|
+
|
|
246
|
+
// gRPC: 스트리밍 활용 (대용량 응답)
|
|
247
|
+
// 단건 요청 반복 → 배치 RPC
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### 2.6 DB 연결 풀 / 쿼리 최적화
|
|
251
|
+
|
|
252
|
+
```go
|
|
253
|
+
db.SetMaxOpenConns(25)
|
|
254
|
+
db.SetMaxIdleConns(10)
|
|
255
|
+
db.SetConnMaxLifetime(5 * time.Minute)
|
|
256
|
+
db.SetConnMaxIdleTime(5 * time.Minute)
|
|
257
|
+
|
|
258
|
+
// 슬로우 쿼리 로깅
|
|
259
|
+
// PostgreSQL: log_min_duration_statement = 100
|
|
260
|
+
// 또는 ORM 레벨 후킹
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## 3. 벤치마크 패턴
|
|
264
|
+
|
|
265
|
+
```go
|
|
266
|
+
func BenchmarkCreateOrder(b *testing.B) {
|
|
267
|
+
svc := setupService(b)
|
|
268
|
+
req := validRequest()
|
|
269
|
+
|
|
270
|
+
b.ResetTimer()
|
|
271
|
+
b.RunParallel(func(pb *testing.PB) {
|
|
272
|
+
for pb.Next() {
|
|
273
|
+
_, err := svc.CreateOrder(context.Background(), req)
|
|
274
|
+
if err != nil {
|
|
275
|
+
b.Fatal(err)
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
})
|
|
279
|
+
// 메모리 할당도 확인: go test -bench=. -benchmem
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
## 4. 출력 형식
|
|
284
|
+
|
|
285
|
+
```
|
|
286
|
+
## 성능 진단 결과
|
|
287
|
+
|
|
288
|
+
### 측정 기준선
|
|
289
|
+
- p50: Xms / p95: Xms / p99: Xms
|
|
290
|
+
- goroutine 수: N
|
|
291
|
+
- heap: XMB
|
|
292
|
+
|
|
293
|
+
### 발견된 병목
|
|
294
|
+
1. [DB N+1] internal/service/order.go:55 — 주문 목록 조회 시 N번 user 쿼리
|
|
295
|
+
2. [GC] internal/handler/serialize.go:30 — 요청마다 bytes.Buffer 새로 할당
|
|
296
|
+
|
|
297
|
+
### 적용한 픽스
|
|
298
|
+
- 변경 파일: <목록>
|
|
299
|
+
- 재측정: p95 Xms → Xms, heap XMB → XMB
|
|
300
|
+
|
|
301
|
+
### 추가 권고
|
|
302
|
+
- ...
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## 5. 관련 문서
|
|
306
|
+
|
|
307
|
+
- 원칙: [`principles/common.md`](../../../principles/common.md) F섹션, [`principles/go.md`](../../../principles/go.md) G6
|
|
308
|
+
- 코퍼스: `sources/go-runtime/`, `sources/postgres/`
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: be-review-go
|
|
3
|
+
description: Go PR을 사내 BE 원칙 기준으로 리뷰. [SEVERITY] file:line — 이슈 형식으로 출력하고, 머지 차단/비차단을 명확히 구분한다.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# be-review (Go)
|
|
7
|
+
|
|
8
|
+
> **호출 시점**: "이 PR 리뷰해줘", "이 Go 코드 리뷰해줘", "OWASP 관점에서 검토해줘".
|
|
9
|
+
> **선행 로딩**: `principles/common.md` + `principles/go.md` 필수.
|
|
10
|
+
|
|
11
|
+
## 0. 절대 금지
|
|
12
|
+
|
|
13
|
+
1. golangci-lint가 잡는 이슈를 수동 리뷰에 중복 포함하지 마라 (자동 도구 역할 존중).
|
|
14
|
+
2. 주관적 스타일 의견을 [HIGH]로 분류 금지.
|
|
15
|
+
3. Go idiom이 아닌 이유로 [HIGH] 처리 금지 — 보안/안전성 기준으로만.
|
|
16
|
+
|
|
17
|
+
## 1. 리뷰 출력 형식
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
## 리뷰 요약
|
|
21
|
+
- 변경: N files +X -Y
|
|
22
|
+
- HIGH N, MED N, LOW N / 머지 [차단|비차단]
|
|
23
|
+
|
|
24
|
+
[HIGH] internal/handler/order.go:42 — context 없는 DB 쿼리 (N9 antipa.)
|
|
25
|
+
[HIGH] internal/handler/order.go:88 — 에러 무시 (_ = err), 결제 실패 묵살
|
|
26
|
+
[MED] internal/service/order.go:120 — goroutine 종료 조건 없음 (leak 위험)
|
|
27
|
+
[LOW] internal/repository/order.go:55 — 인터페이스 구현 패키지에서 정의
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### SEVERITY 기준
|
|
31
|
+
|
|
32
|
+
| SEVERITY | 정의 | 머지 |
|
|
33
|
+
|----------|------|------|
|
|
34
|
+
| **HIGH** | 에러 묵살, goroutine leak, context 미전파, OWASP 취약점, panic 남용, race condition | 차단 |
|
|
35
|
+
| **MED** | 큰 인터페이스, goroutine 종료 조건 불명확, 에러 래핑 누락, defer cancel 누락 | 권고 |
|
|
36
|
+
| **LOW** | 네이밍, 패키지 구조, 코멘트 스타일 | 비차단 |
|
|
37
|
+
|
|
38
|
+
## 2. 체크리스트
|
|
39
|
+
|
|
40
|
+
### 2.1 에러 처리 (HIGH)
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
[ ] 에러 무시 없음 — _ = err 패턴
|
|
44
|
+
[ ] 빈 에러 처리 없음 — if err != nil { return } (로그 없는 경우)
|
|
45
|
+
[ ] 에러 래핑 — fmt.Errorf("funcName: %w", err)
|
|
46
|
+
[ ] panic 사용이 정당한가 — 초기화 실패 / 프로그래밍 오류에만
|
|
47
|
+
[ ] sentinel error 정의 — errors.New로 변수화
|
|
48
|
+
[ ] errors.Is / errors.As 올바른 사용
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 2.2 context 전파 (HIGH/MED)
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
[ ] I/O 함수 첫 인자가 context.Context
|
|
55
|
+
[ ] DB 쿼리: QueryContext, ExecContext, QueryRowContext 사용
|
|
56
|
+
[ ] HTTP 요청: http.NewRequestWithContext 사용
|
|
57
|
+
[ ] context.WithTimeout 후 defer cancel() 존재
|
|
58
|
+
[ ] context.Value — 비즈니스 파라미터 전달에 미사용
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### 2.3 goroutine 안전성 (HIGH/MED)
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
[ ] goroutine 종료 조건 존재 (ctx.Done() 또는 done channel)
|
|
65
|
+
[ ] WaitGroup / errgroup으로 완료 대기
|
|
66
|
+
[ ] 채널 close — sender가 담당
|
|
67
|
+
[ ] 버퍼드 채널 크기 의도적으로 선택
|
|
68
|
+
[ ] sync.Mutex Lock 후 defer Unlock
|
|
69
|
+
[ ] go test -race 통과 (PR CI에 포함)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 2.4 보안 (HIGH)
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
[ ] SQL 파라미터화 — 문자열 concat 없음
|
|
76
|
+
[ ] 소유권 검증 — URL params로 타인 리소스 접근 불가
|
|
77
|
+
[ ] 입력 검증 — 외부 데이터 go-validator 또는 수동 검증
|
|
78
|
+
[ ] 응답에 내부 필드 노출 없음
|
|
79
|
+
[ ] 시크릿 코드 내 하드코딩 없음
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 2.5 인터페이스 설계 (MED/LOW)
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
[ ] 인터페이스 크기 — 1~3 메서드 (5+ 메서드 경고)
|
|
86
|
+
[ ] 인터페이스 정의 위치 — consumer 패키지 (구현 패키지 X)
|
|
87
|
+
[ ] 표준 라이브러리 인터페이스 재활용 (io.Reader 등)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 2.6 코드 품질 (LOW)
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
[ ] 함수 50줄 이하
|
|
94
|
+
[ ] 중첩 깊이 4 이하 (early return 활용)
|
|
95
|
+
[ ] 네이밍 — Go 관습 (CamelCase, receiver 단문자)
|
|
96
|
+
[ ] 패키지 이름 — 단수 소문자 (orders X → order O)
|
|
97
|
+
[ ] golangci-lint 이슈 없음
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## 3. 이슈 카탈로그 (즉시 참조)
|
|
101
|
+
|
|
102
|
+
| 패턴 | SEVERITY | 설명 |
|
|
103
|
+
|------|----------|------|
|
|
104
|
+
| `_ = err` | HIGH | 에러 묵살 |
|
|
105
|
+
| `db.Query(ctx, "... WHERE id = " + id)` | HIGH | SQL injection |
|
|
106
|
+
| `go func() { for { process() } }()` | HIGH | goroutine leak |
|
|
107
|
+
| `ctx, cancel := ...; cancel()` (defer 없음) | MED | context 누수 |
|
|
108
|
+
| `resp, _ := http.Get(url)` | HIGH | context 없음 + 에러 무시 |
|
|
109
|
+
| `resp.Body.Close()` 누락 | MED | resource leak |
|
|
110
|
+
| `interface` 5+ 메서드 | MED | 분리 필요 |
|
|
111
|
+
| `type Repo interface { ... }` in repo pkg | LOW | consumer 측 정의 권장 |
|
|
112
|
+
| `panic(err)` in handler | HIGH | 서버 크래시 |
|
|
113
|
+
| `fmt.Println(...)` in prod | LOW | slog 구조화 로그 |
|
|
114
|
+
| `json.Unmarshal(data, &v)` 에러 무시 | HIGH | 에러 처리 |
|
|
115
|
+
|
|
116
|
+
## 4. 관련 문서
|
|
117
|
+
|
|
118
|
+
- 원칙: [`principles/common.md`](../../../principles/common.md), [`principles/go.md`](../../../principles/go.md)
|
|
119
|
+
- 보안: [`skills/go/be-security/SKILL.md`](../be-security/SKILL.md)
|