π DATT 02 - λΆν ν μ€νΈ λ° λ³λͺ© κ΅¬κ° λΆμ
νλ‘μ νΈ κ°μ
DATTλ μμΉ κΈ°λ° μ₯μ κΈ°λ‘ λ° κ³΅μ νλ«νΌμ΄λ€.
νμ¬ κ²μ ꡬ쑰λ μ¬μ©μ μμ² μλ§λ€ μ€μκ° ν¬λ‘€λ§μ μννλ€.
1
2
3
4
5
6
7
8
9
Client
β
Spring Boot
β
FastAPI Crawling Engine
β
External Place Sources
β
PostgreSQL(PostGIS)
Week1-Day1μμλ:
API μλ΅ μκ° λ‘κΉ
ν¬λ‘€λ§ μκ° λ‘κΉ
DB Query μκ° λ‘κΉ
λ± κ³μΈ‘ νκ²½μ ꡬμΆνλ€.
μ΄λ² μμ μμλ μ€μ λΆν ν μ€νΈλ₯Ό ν΅ν΄:
βμ΄λκ° μΌλ§λ λλ¦°κ°?β
λ₯Ό μμΉ κΈ°λ°μΌλ‘ λΆμνλ κ²μ λͺ©νλ‘ νλ€.
μ€λ μμ λͺ©ν
μ€λ ν΄κ²°νλ €λ λ¬Έμ :
μ€μ μλ΅ μκ° μΈ‘μ
P95/P99 λΆμ
λ³λͺ© κ΅¬κ° νμΈ
μ€μκ° ν¬λ‘€λ§ ꡬ쑰μ νκ³ νμ
κ°μ μ°μ μμ λμΆ
νμ¬ λ¬Έμ μν©
νμ¬ κ΅¬μ‘°μμλ μ¬μ©μ μμ²λ§λ€:
FastAPI νΈμΆ
μΈλΆ μ¬μ΄νΈ μ κ·Ό
λ°μ΄ν° κ°κ³΅
DB μ μ₯
μ΄ λͺ¨λ μνλλ€.
1
2
3
4
5
6
7
8
9
10
11
12
13
μ¬μ©μ μμ²
β
Spring API
β
FastAPI ν¬λ‘€λ§
β
μΈλΆ μ¬μ΄νΈ μ κ·Ό
β
λ°μ΄ν° κ°κ³΅
β
DB μ μ₯
β
μλ΅ λ°ν
μ¦:
μΈλΆ λ€νΈμν¬ μν
ν¬λ‘€λ§ μ²λ¦¬ μκ°
μ μ₯ μ²λ¦¬ μκ°
μ΄ λͺ¨λ μλ΅ μκ°μ μ§μ μν₯μ μ£Όλ ꡬ쑰λ€.
λ¬Έμ νμ
1
2
3
4
- κ²μ μλ΅ μκ°μ΄ λ§€μ° λΆκ·μΉν¨
- νΉμ μ§μ κ²μ μ μ μ΄ μ΄μ μ§μ° λ°μ
- λμ μμ² μ¦κ° μ μλ΅ μκ°μ΄ κΈκ²©ν μ¦κ°
- μΉ΄ν
κ³ λ¦¬ λ³κ²½ μ λ°λ³΅μ μΈ μ¬ν¬λ‘€λ§ λ°μ
λ°μ 쑰건
κ΄λ²μν μ§μ κ²μ
λ°μ΄ν° κ°μκ° λ§μ κ²½μ°
λμ μμ² μ¦κ°
μΈλΆ μ¬μ΄νΈ μλ΅ μ§μ°
ν μ€νΈ λμ μ§μ:
μΈμ²
κ°λ¨
νλ
μ±μ
νμ¬ μμ¬λλ μμΈ
νμ¬ μμ¬λλ λ³λͺ©:
μΈλΆ ν¬λ‘€λ§ I/O
FastAPI μ²λ¦¬ μκ° μ¦κ°
μμ²λ§λ€ λ°μνλ DB μ μ₯
λκΈ°ν μμ² νλ¦ κ΅¬μ‘°
μ£Όμ
νμ¬ μμΈμ μμ§ μμ ν νμ λμ§ μμλ€.
λ€λ§ μ΄λ² μμ μμλ:
1
μ€μ κ³μΈ‘ λ°μ΄ν° κΈ°λ°μΌλ‘ λ³λͺ© μμΉλ₯Ό νμΈ
νλ κ²μ λͺ©νλ‘ νλ€.
μΈ‘μ λ° λΆμ
λΆν ν μ€νΈ νκ²½
μ¬μ© λꡬ:
1
k6
ν μ€νΈ μλ리μ€
| μ§μ | μΉ΄ν κ³ λ¦¬ |
|---|---|
| μΈμ² | μΉ΄ν |
| κ°λ¨ | μμμ |
| νλ | μ μ§ |
| μ±μ | λ거리 |
λμ μ¬μ©μ μ
1λͺ
10λͺ
30λͺ
μμ§ν λ‘κ·Έ
API μλ΅ λ‘κ·Έ
1
2
3
4
5
[SEARCH_API]
keyword=μΈμ²
category=cafe
durationMs=8420
status=200
ν¬λ‘€λ§ λ‘κ·Έ
1
2
3
4
5
[CRAWLING]
keyword=μΈμ²
category=cafe
durationMs=6210
resultCount=42
DB Query λ‘κ·Έ
1
2
3
[DB_QUERY]
query=searchPlaces
durationMs=180
μ±λ₯ μΈ‘μ κ²°κ³Ό
νμ¬ μμΉλ μμμ΄λ©°,
μ€μ ν
μ€νΈ κ²°κ³Όλ‘ κ΅μ²΄ μμ μ΄λ€.
| νλͺ© | κ²°κ³Ό |
|---|---|
| νκ· μλ΅ μκ° | 6.8s |
| P95 | 9.4s |
| P99 | 12.1s |
| μ€ν¨μ¨ | 5% |
λΆμ κ²°κ³Ό
νμ¬ μλ΅ μκ° λλΆλΆμ:
1
μΈλΆ ν¬λ‘€λ§ μ²λ¦¬ μκ°
μ μ§μ€λλ κ²μΌλ‘ 보μΈλ€.
νΉν:
FastAPI ν¬λ‘€λ§ μκ°
μΈλΆ μ¬μ΄νΈ μλ΅ μκ°
μ΄ μ 체 μλ΅ μκ° λλΆλΆμ μ°¨μ§νλ€.
λν:
μ¬μ©μ μμ² νλ¦ λ΄λΆ DB μ μ₯
μμ²λ§λ€ λ°λ³΅ μνλλ ν¬λ‘€λ§
ꡬ쑰λ μλ΅ μ§μ°μ μ¦κ°μν€λ μμΈμΌλ‘ νμΈλμλ€.
μ€μ νμΈλ μμΈ
νμ¬κΉμ§ νμΈλ μ£Όμ λ³λͺ©:
μ€μκ° μΈλΆ I/O
μ¬μ©μ μμ² νλ¦ λ΄λΆ ν¬λ‘€λ§
DB μ μ₯ ν¬ν¨ ꡬ쑰
λκΈ°ν μ²λ¦¬ λ°©μ
νΉν λ€μ κ΅¬μ‘°κ° κ°μ₯ ν° λ¬Έμ μλ€.
1
2
3
4
5
6
7
μ¬μ©μ μμ²
β
μ€μκ° ν¬λ‘€λ§
β
DB μ μ₯
β
μλ΅ λ°ν
μ¦:
βμ¬μ©μ μλ΅ νλ¦ μμ λλ¦° μμ μ΄ λͺ¨λ ν¬ν¨λ ꡬ쑰β
μλ€.
μ μ©ν ν΄κ²° λ°©λ²
μ΄λ² λ¨κ³μμλ μμ§ κ΅¬μ‘° λ³κ²½μ μννμ§ μμλ€.
λμ λ€μ μμ μ μλ£νλ€.
μ€μ μλ΅ μκ° μΈ‘μ
λ³λͺ© κ΅¬κ° μλ³
P95/P99 μΈ‘μ
λΆν ν μ€νΈ μλλ¦¬μ€ κ΅¬μΆ
κ°μ μ κΈ°μ€ λ°μ΄ν° ν보
μ½λ λ³κ²½ μ¬ν
Before
κΈ°μ‘΄μλ λ³λ λΆν ν μ€νΈ νκ²½μ΄ μ‘΄μ¬νμ§ μμλ€.
1
2
3
4
5
6
7
@GetMapping("/search")
public ResponseEntity<?> search(String keyword) {
return ResponseEntity.ok(
placeService.search(keyword)
);
}
After
k6 κΈ°λ° λΆν ν μ€νΈ νκ²½ μΆκ°.
1
2
3
4
5
6
7
8
9
10
11
import http from 'k6/http';
import { sleep } from 'k6';
export default function () {
http.get(
'https://datt-prd.xyz/api/search?keyword=μΈμ²'
);
sleep(1);
}
Trade-off
μ΄λ² μμ μΌλ‘ μΈν΄ λ°μνλ Trade-off:
| μ₯μ | λ¨μ |
|---|---|
| μ€μ λ³λͺ© μμΉ νμΈ κ°λ₯ | ν μ€νΈ νκ²½ κ΅¬μΆ λΉμ© μ¦κ° |
| κ°μ μ κΈ°μ€ λ°μ΄ν° ν보 κ°λ₯ | λ‘κ·Έ λ° μΈ‘μ λ°μ΄ν° κ΄λ¦¬ νμ |
| P95/P99 κΈ°λ° λΆμ κ°λ₯ | λΆν ν μ€νΈ νκ²½ μ μ§ νμ |
μ΄μ κ΄μ μμμ κ³ λ € μ¬ν
μ΄μ νκ²½μμλ λ¨μ νκ· μλ΅ μκ°λ³΄λ€:
P95
P99
Error Rate
κ° λ μ€μνλ€.
νΉν μΈλΆ I/O κΈ°λ° κ΅¬μ‘°μμλ:
Timeout
Retry
Circuit Breaker
μ λ΅μ΄ νμμ μ΄λ€.
λν:
μΈλΆ API μ₯μ
ν¬λ‘€λ§ μ€ν¨
λ€νΈμν¬ μ§μ°
μ΄ μ 체 μλΉμ€ μ₯μ λ‘ μ νλ κ°λ₯μ±μ΄ μ‘΄μ¬νλ€.
νκ³
μ΄λ² μμ μμ κ°μ₯ μ€μνλ μ μ:
βλλ¦° κ² κ°λ€β
κ° μλλΌ:
βμ΄λκ° μΌλ§λ λλ¦°κ°β
λ₯Ό μμΉ κΈ°λ°μΌλ‘ νμΈν κ²μ΄λ€.
νΉν:
μΈλΆ I/O
λκΈ°ν μμ² νλ¦
μ€μκ° ν¬λ‘€λ§ ꡬ쑰
κ° νμ¬ κ΅¬μ‘°μ ν΅μ¬ λ³λͺ©μμ νμΈν μ μμλ€.
λ€μ μμ μμ
λ€μ λ¨κ³:
λ°°μΉ κΈ°λ° ν¬λ‘€λ§ ꡬ쑰 μ€κ³
μ¬μ©μ μμ²κ³Ό ν¬λ‘€λ§ νλ¦ λΆλ¦¬
DB μ‘°ν κΈ°λ° κ²μ API μ ν μ€λΉ
Redis μΊμ± μ λ΅ κ²ν