- ํ๋ก์ ํธ ๊ฐ์
- ์์คํ ์ํคํ ์ฒ
- ์๋ฒ ์ธํ๋ผ ์คํ
- ๋ถํ ํ ์คํธ
- ์ํคํ ์ฒ ๋น๊ต ๋ถ์
- ๋ชจ๋ํฐ๋ง ์งํ
- ์ต๋ TPS ๋ฐ ์ฉ๋ ๋ถ์
- ๋ฐ๊ฒฌ๋ ์ด์ ๋ฐ ๊ฐ์ ์
- ๊ฒฐ๋ก
SpeedCam์ ๋๋ก ์ ๊ณผ์ ์ฐจ๋์ ์ค์๊ฐ์ผ๋ก ๊ฐ์งํ๊ณ ๋ฒํธํ์ ์ธ์ํ์ฌ ์ฌ์ฉ์์๊ฒ ์๋ฆผ์ ์ ์กํ๋ ์ด๋ฒคํธ ๊ธฐ๋ฐ ์ค์๊ฐ ์์คํ ์ ๋๋ค.
- Event Driven Architecture: MQTT + AMQP ๊ธฐ๋ฐ ๋น๋๊ธฐ ๋ฉ์์ง ์ฒ๋ฆฌ
- ๋ถ์ฐ ์์คํ : GCE ์ธ์คํด์ค 6๋๋ก ๊ตฌ์ฑ๋ ๋ง์ดํฌ๋ก์๋น์ค ์ํคํ ์ฒ
- ์ค์๊ฐ OCR ์ฒ๋ฆฌ: EasyOCR์ ํ์ฉํ ํ๊ตญ์ด ๋ฒํธํ ์ธ์
- ์์ ํ ๊ด์ธก์ฑ: Prometheus, Grafana, Loki, Jaeger๋ฅผ ํตํ ํตํฉ ๋ชจ๋ํฐ๋ง
- Backend: Django 4.2 + Gunicorn
- Message Broker: RabbitMQ 3.13 (MQTT Plugin + AMQP)
- Database: MySQL 8.0
- OCR Engine: EasyOCR (Korean + English)
- Monitoring: Prometheus, Grafana, Loki, Jaeger, OpenTelemetry
- Infra: GCP Compute Engine (6 instances), Docker Compose
- Load Testing: k6 (Grafana k6), Python paho-mqtt
๊ธฐ์กด ์์คํ ์ Django ๋ชจ๋๋ฆฌ์ ๊ตฌ์กฐ๋ก, OCR ์ฒ๋ฆฌ๊ฐ ๋๊ธฐ์ ์ผ๋ก ์ํ๋์ด ๋ค์๊ณผ ๊ฐ์ ๊ตฌ์กฐ์ ํ๊ณ๊ฐ ์์์ต๋๋ค.
graph TB
subgraph Edge["Edge Device (Raspberry Pi)"]
Camera["๊ณผ์ ์นด๋ฉ๋ผ"]
end
subgraph Backend["backend (Django)"]
API["API Handler"]
OCR["OCR ์ฒ๋ฆฌ<br/>(๋๊ธฐ ์คํ)"]
end
subgraph Workers["Celery Workers"]
CW["celery_worker<br/>(์๋ฆผ ์ ์ก)"]
DLQ["celery_worker_dlq"]
end
Camera -->|"HTTP POST"| API
API --> OCR
Backend --> RMQ["RabbitMQ"]
CW --> RMQ
Backend --> MySQL[("MySQL")]
CW --> MySQL
style Backend fill:#ffcccc,stroke:#cc0000
style OCR fill:#ff9999
| ๋ฌธ์ ์์ญ | ์์ธ ๋ด์ฉ |
|---|---|
| OCR ๋๊ธฐ ์ฒ๋ฆฌ | OCR ์์ (์ฝ 3์ด)์ด HTTP ์ค๋ ๋๋ฅผ ์ ์ ํ์ฌ ์๋ฒ ์ฒ๋ฆฌ๋ ์ ํ |
| Edge Device ๋ธ๋กํน | ์๋ฒ ์๋ต ๋๊ธฐ(3์ด+)๋ก ์ธํ ์ฐ์ ๊ฐ์ง ๋ถ๊ฐ, ๋ฐ์ดํฐ ์ ์ค ์ํ |
| HTTP ๊ธฐ๋ฐ IoT ํต์ | ์์ฒญ๋ง๋ค TCP ์ฐ๊ฒฐ, ๋ฉ์์ง ๋ณด์ฅ ์์, ์คํ๋ผ์ธ ์ฒ๋ฆฌ ๋ถ๊ฐ |
| ์ฅ์ ์ ํ | OCR ์ฅ์ ์ API ์๋น์ค ์ ์ฒด ์ํฅ, ๋ ๋ฆฝ ํ์ฅ ๋ถ๊ฐ |
์ฑ๋ฅ ์งํ (Before) โ ์ํคํ ์ฒ ๊ตฌ์กฐ ๊ธฐ๋ฐ ์ถ์ ๊ฐ
๊ธฐ์กด ์ํคํ ์ฒ๋ ํ์ฌ ์ด์ ํ๊ฒฝ์์ ๋ณ๋๋ก ๋ถํ ํ ์คํธ๋ฅผ ์ํํ์ง ์์์ต๋๋ค. ์๋ ์์น๋ ๋๊ธฐ OCR ์ฒ๋ฆฌ ์๊ฐ(EasyOCR CPU ๊ธฐ์ค ~3์ด)๊ณผ ์ํคํ ์ฒ ๊ตฌ์กฐ๋ก๋ถํฐ ๋์ถํ ์ค๊ณ ๊ธฐ๋ฐ ์ถ์ ๊ฐ์ ๋๋ค.
- ์ด๋ฒคํธ ์ฒ๋ฆฌ ์๊ฐ: 3,000ms ์ด์ (HTTP ์์ โ OCR ์๋ฃ๊น์ง ๋๊ธฐ ์ฒ๋ฆฌ)
- Edge Device ๋ธ๋กํน: 3,000ms ์ด์ (HTTP ์๋ต ๋๊ธฐ)
- ๋ฉ์์ง ๋ณด์ฅ: ์์
- ์ฅ์ ๊ฒฉ๋ฆฌ: ๋ถ๊ฐ๋ฅ (๋ชจ๋๋ฆฌ์ ๊ตฌ์กฐ)
๊ธฐ์กด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด Event Driven Architecture๋ก ์ ํํ์ฌ MQTT ๊ธฐ๋ฐ IoT ํต์ ๊ณผ AMQP ๊ธฐ๋ฐ ๋น๋๊ธฐ ๋ฉ์์ง ์ฒ๋ฆฌ๋ฅผ ๊ตฌํํ์ต๋๋ค.
graph TB
subgraph Edge["Edge Device"]
Camera["๊ณผ์ ์นด๋ฉ๋ผ"]
end
subgraph Main["main (Django)"]
API["API Handler"]
MQTT_Sub["MQTT Subscriber"]
Publisher["Event Publisher"]
end
subgraph Workers["Event Processors"]
OCR["ocr-worker<br/>โข ๊ฐ์ง ์ด๋ฒคํธ ์ฒ๋ฆฌ<br/>โข OCR ์ํ"]
Alert["alert-worker<br/>โข ์๋ฃ ์ด๋ฒคํธ ์ฒ๋ฆฌ<br/>โข FCM ๋ฐ์ก"]
end
subgraph MessageBroker["RabbitMQ"]
MQTT["MQTT Plugin"]
Queue1[("๊ฐ์ง ์ด๋ฒคํธ ํ")]
Queue2[("์๋ฆผ ์ด๋ฒคํธ ํ")]
end
subgraph Storage["Google Cloud Storage"]
GCS[("GCS Bucket<br/>๋ฒํธํ ์ด๋ฏธ์ง")]
end
Camera -->|"MQTT Publish"| MQTT
Camera -->|"์ด๋ฏธ์ง ์
๋ก๋"| GCS
MQTT --> MQTT_Sub
Publisher --> Queue1
Queue1 --> OCR
OCR -->|"์ด๋ฏธ์ง ๋ค์ด๋ก๋"| GCS
OCR --> Queue2
Queue2 --> Alert
Main --> DB1[("default")]
Main --> DB2[("vehicles_db")]
OCR --> DB3[("detections_db")]
Alert --> DB4[("notifications_db")]
style Main fill:#90EE90
style OCR fill:#87CEEB
style Alert fill:#DDA0DD
style MessageBroker fill:#FFB6C1
style Storage fill:#FFFACD
| ์ปดํฌ๋ํธ | ์ญํ | ํ๋กํ ์ฝ | ํน์ง |
|---|---|---|---|
| Edge Device | ๊ณผ์ ์ฐจ๋ ๊ฐ์ง | MQTT | QoS 1, ๊ฒฝ๋, ์๊ตฌ ์ฐ๊ฒฐ |
| main (Django) | API + MQTT ๊ตฌ๋ | HTTP + MQTT | ์ด๋ฒคํธ ๋ฐํ๋ง ๋ด๋น |
| ocr-worker | ๋ฒํธํ OCR ์ฒ๋ฆฌ | AMQP | ๋น๋๊ธฐ ์ฒ๋ฆฌ, concurrency=1 |
| alert-worker | FCM ํธ์ ์๋ฆผ | AMQP | ๊ณ ์ฑ๋ฅ, concurrency=100 |
| RabbitMQ | ๋ฉ์์ง ๋ธ๋ก์ปค | MQTT + AMQP | At-Least-Once ๋ณด์ฅ |
sequenceDiagram
participant Edge as Edge Device
participant RMQ as RabbitMQ
participant Main as main
participant OCR as ocr-worker
participant Alert as alert-worker
participant User as ์ฌ์ฉ์ ์ฑ
Edge->>RMQ: MQTT Publish (๊ณผ์ ์ฐจ๋ ๊ฐ์ง)
RMQ-->>Edge: PUBACK (์ฆ์)
RMQ->>Main: ๋ฉ์์ง ์ ๋ฌ (subscribe)
Main->>Main: DB ์ ์ฅ (pending)
Main->>RMQ: ๊ฐ์ง ์ด๋ฒคํธ ๋ฐํ (AMQP)
RMQ->>OCR: ๊ฐ์ง ์ด๋ฒคํธ ์์
OCR->>OCR: ๋ฒํธํ OCR ์ฒ๋ฆฌ
OCR->>OCR: DB ์
๋ฐ์ดํธ (completed)
OCR->>RMQ: OCR ์๋ฃ ์ด๋ฒคํธ ๋ฐํ
RMQ->>Alert: ์๋ฃ ์ด๋ฒคํธ ์์
Alert->>User: FCM Push ์๋ฆผ
์ด 6๋์ GCE ์ธ์คํด์ค๋ก ๊ตฌ์ฑ๋ ๋ถ์ฐ ์์คํ ์ ๋๋ค. ๋ชจ๋ ์ธ์คํด์ค๋ asia-northeast3-a ์กด์ ์์นํ๋ฉฐ Ubuntu 22.04 LTS, Kernel 6.8.0-1045-gcp, Docker ๊ธฐ๋ฐ์ผ๋ก ์ด์๋ฉ๋๋ค.
| ์ธ์คํด์ค | ๋จธ์ ํ์ | vCPU | RAM | ๋์คํฌ | ๋์คํฌ ์ฌ์ฉ๋ฅ | ๋ด๋ถ IP | ์ญํ |
|---|---|---|---|---|---|---|---|
| speedcam-app | e2-small | 2 | 2GB | 20GB | 34% (6.4GB) | 10.178.0.4 | API ์๋ฒ |
| speedcam-db | e2-medium | 2 | 4GB | 29GB | 20% (5.8GB) | 10.178.0.2 | ๋ฐ์ดํฐ๋ฒ ์ด์ค |
| speedcam-mq | e2-small | 2 | 2GB | 20GB | 26% (4.9GB) | 10.178.0.7 | ๋ฉ์์ง ๋ธ๋ก์ปค |
| speedcam-ocr | e2-small | 2 | 2GB | 20GB | 87% (17GB) | 10.178.0.3 | OCR Worker |
| speedcam-alert | e2-small | 2 | 2GB | 20GB | 31% (5.9GB) | 10.178.0.6 | Alert Worker |
| speedcam-mon | e2-small | 2 | 2GB | 20GB | 37% (7.0GB) | 10.178.0.5 | ๋ชจ๋ํฐ๋ง |
| ์ธ์คํด์ค | ์ปจํ ์ด๋ | ์ญํ |
|---|---|---|
| speedcam-app | Django + Gunicorn | REST API (GUNICORN_WORKERS=2) |
| Traefik | ๋ฆฌ๋ฒ์ค ํ๋ก์ | |
| Flower | Celery ๋ชจ๋ํฐ๋ง | |
| Promtail | ๋ก๊ทธ ์์ง ์์ด์ ํธ | |
| cAdvisor | ์ปจํ ์ด๋ ๋ฉํธ๋ฆญ ์์ง | |
| speedcam-db | MySQL 8.0 | ๋ฉ์ธ ๋ฐ์ดํฐ๋ฒ ์ด์ค |
| mysqld-exporter | MySQL ๋ฉํธ๋ฆญ ์์ง | |
| Promtail | ๋ก๊ทธ ์์ง ์์ด์ ํธ | |
| cAdvisor | ์ปจํ ์ด๋ ๋ฉํธ๋ฆญ ์์ง | |
| speedcam-mq | RabbitMQ 3.13 | MQTT + AMQP ๋ธ๋ก์ปค |
| Promtail | ๋ก๊ทธ ์์ง ์์ด์ ํธ | |
| cAdvisor | ์ปจํ ์ด๋ ๋ฉํธ๋ฆญ ์์ง | |
| speedcam-ocr | Celery OCR Worker | EasyOCR ์ฒ๋ฆฌ (concurrency=1) |
| Promtail | ๋ก๊ทธ ์์ง ์์ด์ ํธ | |
| cAdvisor | ์ปจํ ์ด๋ ๋ฉํธ๋ฆญ ์์ง | |
| speedcam-alert | Celery Alert Worker | FCM ์๋ฆผ ๋ฐ์ก (concurrency=100) |
| Promtail | ๋ก๊ทธ ์์ง ์์ด์ ํธ | |
| cAdvisor | ์ปจํ ์ด๋ ๋ฉํธ๋ฆญ ์์ง | |
| speedcam-mon | Prometheus | ๋ฉํธ๋ฆญ ์์ง |
| Grafana | ์๊ฐํ ๋์๋ณด๋ | |
| Loki | ๋ก๊ทธ ์์ง | |
| Jaeger | ๋ถ์ฐ ์ถ์ | |
| OpenTelemetry Collector | ํ ๋ ๋ฉํธ๋ฆฌ ์์ง | |
| Promtail | ๋ก๊ทธ ์์ง ์์ด์ ํธ | |
| cAdvisor | ์ปจํ ์ด๋ ๋ฉํธ๋ฆญ ์์ง |
| ์ธ์คํด์ค | RAM ์ฌ์ฉ | RAM ์ฌ์ | ๋ฉ๋ชจ๋ฆฌ ์ง์ฝ์ ํ๋ก์ธ์ค | ๋น๊ณ |
|---|---|---|---|---|
| speedcam-app | 661MB/2GB | 1.1GB | Gunicorn 2 workers | ์์ ์ |
| speedcam-db | 853MB/4GB | 2.6GB | MySQL ๋ฒํผํ | ์ถฉ๋ถํ ์ฌ์ |
| speedcam-mq | 471MB/2GB | 1.2GB | RabbitMQ | ์์ ์ |
| speedcam-ocr | 1.0GB/2GB | 721MB | EasyOCR ๋ชจ๋ธ (1.5GB) | ๋ฉ๋ชจ๋ฆฌ ๋ถ์กฑ ์ํ |
| speedcam-alert | 433MB/2GB | 1.3GB | ๊ฒฝ๋ ์์ปค | ์ถฉ๋ถํ ์ฌ์ |
| speedcam-mon | 1.5GB/2GB | 264MB | Prometheus + Grafana | ๋ฉ๋ชจ๋ฆฌ ๋ถ์กฑ ์ํ |
์ฃผ์์ฌํญ:
speedcam-ocr: EasyOCR ๋ชจ๋ธ ๋ก๋ฉ์ผ๋ก ์ธํ ๋์ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ฅ , concurrency๋ฅผ 1๋ก ์ ํspeedcam-mon: ๋ชจ๋ํฐ๋ง ์คํ์ ๋ฉ๋ชจ๋ฆฌ ์ง์ฝ์ ํน์ฑ์ผ๋ก 264MB ์ฌ์ ๋ถ๋ง ํ๋ณด
์ค์ ์ด์ ํ๊ฒฝ์์์ ์์คํ ์ฑ๋ฅ๊ณผ ์์ ์ฑ์ ๊ฒ์ฆํ๊ธฐ ์ํด ๋ค์ ๋ชฉํ๋ก ๋ถํ ํ ์คํธ๋ฅผ ์ํํ์ต๋๋ค.
| ๋ชฉํ | ์ธ๋ถ ๋ด์ฉ |
|---|---|
| ์ฑ๋ฅ ํ๊ณ ํ์ | ๊ฐ ์ปดํฌ๋ํธ๋ณ ์ต๋ ์ฒ๋ฆฌ๋ ์ธก์ |
| ๋ณ๋ชฉ ์ง์ ์๋ณ | Event Driven ํ์ดํ๋ผ์ธ ๊ฐ ๋จ๊ณ๋ณ ์์ ์๊ฐ ๋ถ์ |
| ์ํคํ ์ฒ ๊ฒ์ฆ | ๊ธฐ์กด ๋๊ธฐ ์ฒ๋ฆฌ ๋๋น ๋น๋๊ธฐ ์ด๋ฒคํธ ๊ธฐ๋ฐ ์ฒ๋ฆฌ์ ์ฑ๋ฅ ๊ฐ์ ์ ๋ ํ์ธ |
| ์์ ์ฑ ํ์ธ | ์คํ์ดํฌ ํธ๋ํฝ ๋ฐ์ ์ ์์คํ ์ ์์ ์ฑ ๊ฒ์ฆ |
| ๋๊ตฌ | ์ฉ๋ | ํน์ง |
|---|---|---|
| k6 (Grafana k6) | HTTP API ๋ถํ ํ ์คํธ | Prometheus Remote Write๋ก ๋ฉํธ๋ฆญ ์ค์๊ฐ ์ ์ก, ์น ๋์๋ณด๋ + Grafana ์ฐ๋ |
| Python + paho-mqtt | MQTT ํ์ดํ๋ผ์ธ ๋ถํ ํ ์คํธ | ์ค์ ํ๊ตญ์ด ๋ฒํธํ ์ด๋ฏธ์ง๋ฅผ GCS์ ์ ์ฅํ์ฌ ์ค ํ์ดํ๋ผ์ธ ํ ์คํธ, EasyOCR ์ค์ ๋์ ๊ฒ์ฆ |
| ํญ๋ชฉ | ์์ธ |
|---|---|
| ํ ์คํธ ์ผ์ | 2026-02-12 (k6 4์๋๋ฆฌ์ค + MQTT 3์๋๋ฆฌ์ค) |
| k6 ์คํ ์์น | speedcam-app ์ธ์คํด์ค ๋ด๋ถ (localhost ํธ์ถ) |
| MQTT ํ ์คํธ ์คํ ์์น | speedcam-app โ speedcam-mq (๋ด๋ถ IP 10.178.0.7) |
| ๋คํธ์ํฌ ํ๊ฒฝ | ๋์ผ VPC (asia-northeast3), ์ธ์คํด์ค ๊ฐ ์ง์ฐ <1ms |
| ๋ถํ ๋ฐ์๊ธฐ โ ์๋ฒ ์ง์ฐ | k6: ~0ms (localhost), MQTT: <1ms (๊ฐ์ VPC) |
| ์์คํ ์ํ | ํ ์คํธ ์ธ ํธ๋ํฝ ์์ (์ ์ฉ ํ ์คํธ ํ๊ฒฝ) |
์ฐธ๊ณ : k6 HTTP ํ ์คํธ๋ speedcam-app ์์ฒด์์ localhost๋ก ํธ์ถํ์์ผ๋ฏ๋ก, ์ธก์ ๋ ์๋ต ์๊ฐ์ ์์ ์๋ฒ ์ฒ๋ฆฌ ์๊ฐ์ ๊ฐ๊น์ต๋๋ค. ์ค์ ํด๋ผ์ด์ธํธ์์์ ์๋ต ์๊ฐ์ ๋คํธ์ํฌ ์ง์ฐ์ด ์ถ๊ฐ๋ฉ๋๋ค.
Django REST API์ ์ฒ๋ฆฌ ์ฑ๋ฅ๊ณผ ์๋ต ์๊ฐ์ ์ธก์ ํ๊ธฐ ์ํด 4๊ฐ์ง ์๋๋ฆฌ์ค๋ก ๋ถํ ํ ์คํธ๋ฅผ ์ํํ์ต๋๋ค.
| ์๋๋ฆฌ์ค | VUs | Executor | ์ง์์๊ฐ | ์์ ์์ | ์ค๋ช |
|---|---|---|---|---|---|
| dashboard_polling | 3 (constant) | constant-vus | 2๋ถ | 0s | ๋์๋ณด๋ ํด๋ง (๊ฐ์ง๋ชฉ๋ก 5์ด, ์๋ฆผ 10์ด, ํต๊ณ 30์ด ์ฃผ๊ธฐ) |
| admin_ops | 2 | constant-arrival-rate (2/min) | 2๋ถ | 0s | ๊ด๋ฆฌ์ ์์ (์ฐจ๋ ๋ฑ๋ก + FCM ํ ํฐ ์ ๋ฐ์ดํธ) |
| mixed_workload | 0โ5โ9โ9โ0 | ramping-vus | 2๋ถ30์ด | 2m | ์ฝ๊ธฐ 60% + ํ์ดํ๋ผ์ธ ์ํ 30% + ์ฐ๊ธฐ 10% |
| spike_resilience | 0โ3โ15โ15โ3โ0 | ramping-vus | 1๋ถ10์ด | 4m30s | ๊ธ๊ฒฉํ ํธ๋ํฝ ์ฆ๊ฐ ์ ํ๋ณต๋ ฅ (15 VUs = 4 ํธ๋ค๋ฌ ๋๋น 3.75๋ฐฐ) |
์ด ํ ์คํธ ์๊ฐ: 5๋ถ 40์ด, ์ต๋ ๋์ VUs: 18
โ
์ด ์์ฒญ: 2,297๊ฑด (ํ๊ท 6.75 req/s)
โ
์ ์ฒด p95 ์๋ต์๊ฐ: 38.85ms
โ
์๋ฌ์จ: 0.21% (5/2,277๊ฑด) - FCM ํ ํฐ ์
๋ฐ์ดํธ ์๋ํฌ์ธํธ ๋ฌธ์
โ
๋ชจ๋ ์๊ณ๊ฐ(Threshold) ํต๊ณผ
โ
Prometheus Remote Write โ Grafana ๋ฉํธ๋ฆญ ๊ธฐ๋ก
์๋ต ์๊ฐ ๋ถํฌ
| ๋ฉํธ๋ฆญ | avg | min | med | max | p(90) | p(95) |
|---|---|---|---|---|---|---|
| dashboard_req_duration | 19.27ms | 9.73ms | 17.65ms | 118.73ms | 26.89ms | 30.6ms |
| admin_req_duration | 17.78ms | 4.21ms | 17.82ms | 53.17ms | 21.05ms | 23.23ms |
| detections_list_duration | 23.98ms | 14.01ms | 20.53ms | 162.31ms | 33.3ms | 43.29ms |
| statistics_req_duration | 23.44ms | 13.31ms | 20.4ms | 127.43ms | 34.03ms | 42.42ms |
| pending_read_duration | 13.08ms | 10.3ms | 12.55ms | 27.22ms | 15.12ms | 16.6ms |
| spike_resilience (overall) | 21.42ms | 8.63ms | 18.79ms | 162.31ms | 31.6ms | 40.54ms |
| http_req_duration (์ ์ฒด) | 20.72ms | 3.75ms | 18.23ms | 162.31ms | 30.14ms | 38.85ms |
๐ธ [์คํฌ๋ฆฐ์ท ์ฝ์ : k6 Grafana ๋์๋ณด๋ - 4 ์๋๋ฆฌ์ค ์๋ต์๊ฐ ๊ทธ๋ํ]
์๊ณ์น(Threshold) ๊ฒ์ฆ ๊ฒฐ๊ณผ:
| ์๊ณ์น | ๊ธฐ์ค | ์ค์ธก | ํ์ |
|---|---|---|---|
| dashboard_req_duration p(95) | < 200ms | 30.6ms | โ PASS |
| detections_list_duration p(95) | < 300ms | 43.29ms | โ PASS |
| statistics_req_duration p(95) | < 500ms | 42.42ms | โ PASS |
| pending_read_duration p(95) | < 500ms | 16.6ms | โ PASS |
| admin_req_duration p(95) | < 300ms | 23.23ms | โ PASS |
| spike_resilience p(95) | < 1500ms | 40.54ms | โ PASS |
| errors (์ ์ฒด) | < 5% | 0.21% | โ PASS |
| errors (dashboard) | < 1% | 0.00% | โ PASS |
| errors (spike) | < 10% | 0.00% | โ PASS |
์ฃผ์ ์ธ์ฌ์ดํธ:
- ๋์๋ณด๋ ํด๋ง ํ๊ท 19ms: ์ค์๊ฐ ๋ฐ์ดํฐ ์กฐํ๊ฐ ๋งค์ฐ ๋น ๋ฆ
- ์คํ์ดํฌ ์ํฉ(15 VUs)์์๋ p95 40ms: ๊ธ๊ฒฉํ ํธ๋ํฝ ์ฆ๊ฐ ์์๋ ์์ ์ ์๋ต ์ ์ง
- ๊ฐ์ค ๋๋น 37๋ฐฐ ์ข์ ์ฑ๋ฅ: ์คํ์ดํฌ ๊ฐ์ค(p95 < 1500ms) ๋๋น ์ค์ธก 40ms
- 4 ํธ๋ค๋ฌ(Gunicorn 2wร2t)๋ก 15 VUs ์ถฉ๋ถํ ์ํ: ์ค์ ํฌํ์ ์ 50+ VUs
| Check ํญ๋ชฉ | ์ฑ๊ณต/์ ์ฒด | ์ฑ๊ณต๋ฅ | ๋น๊ณ |
|---|---|---|---|
| ์๋ฒ ํฌ์ค์ฒดํฌ | 1/1 | 100% | โ |
| ์ฐจ๋ ๋ฑ๋ก (201) | โ | 100% | โ admin_ops + mixed ์๋๋ฆฌ์ค |
| FCM ํ ํฐ ์ ๋ฐ์ดํธ (200) | 0/5 | 0% | โ PATCH ์๋ํฌ์ธํธ ํธํ ๋ฌธ์ |
| ๊ฐ์ง ๋ชฉ๋ก (200) | โ | 100% | โ dashboard + spike ์๋๋ฆฌ์ค |
| ์๋ฆผ ๋ชฉ๋ก (200) | โ | 100% | โ dashboard ์๋๋ฆฌ์ค |
| ํต๊ณ ์กฐํ (200) | โ | 100% | โ dashboard + spike ์๋๋ฆฌ์ค |
| ๋๊ธฐ ๋ชฉ๋ก (200) | โ | 100% | โ mixed ์๋๋ฆฌ์ค |
| ํผํฉ ์ฝ๊ธฐ (200) | โ | 100% | โ mixed ์๋๋ฆฌ์ค |
| ํผํฉ ์ฐจ๋ ๋ฑ๋ก (201) | โ | 100% | โ mixed ์๋๋ฆฌ์ค |
| ์คํ์ดํฌ ๊ฐ์ง ๋ชฉ๋ก | โ | 100% | โ |
| ์คํ์ดํฌ ์๋ฆผ ๋ชฉ๋ก | โ | 100% | โ |
| ์คํ์ดํฌ ํต๊ณ | โ | 100% | โ |
์ ์ฒด: 2,272/2,277 checks ์ฑ๊ณต (99.78%). ์คํจ 5๊ฑด์ ๋ชจ๋ FCM ํ ํฐ ์ ๋ฐ์ดํธ PATCH ์๋ํฌ์ธํธ.
| ํญ๋ชฉ | ๊ฐ | ๊ทผ๊ฑฐ |
|---|---|---|
| ํ์ฌ ์ค์ | GUNICORN_WORKERS=2 (๊ฐ 2 threads = ์ด 4 HTTP handlers) | ๋ฐฐํฌ ํ๊ฒฝ (env.example ๊ธฐ๋ณธ๊ฐ=4์ ๋ค๋ฆ) |
| 4์๋๋ฆฌ์ค ํ ์คํธ | 15 VUs์์ p95=40.54ms, ์๋ฌ์จ 0% | k6 4์๋๋ฆฌ์ค ์ค์ธก |
| ์คํธ๋ ์ค ํ ์คํธ | 50 VUs์์ p95=2,230ms, ์๋ฌ์จ 1.5% | k6 stress_ramp ์ค์ธก |
| ํฌํ์ | 30~50 VUs ์ฌ์ด | 15 VUs(์ ์) โ 50 VUs(์ฑ๋ฅ ์ ํ) |
| ์์ ์ต๋ TPS | ~25 req/s (50 VUs, e2-small์์ k6+์๋ฒ ๊ณต์ ์) | ์คํธ๋ ์ค ํ ์คํธ ์ค์ธก |
| ์ด๋ก ์ต๋ TPS | ~80-100 req/s | 4 handlers ร ํ๊ท 20ms ๊ธฐ์ค |
| ์ฃผ์ ๋ณ๋ชฉ | Gunicorn ํธ๋ค๋ฌ ํฌํ + DB ์ปค๋ฅ์ (CONN_MAX_AGE ๋ฏธ์ค์ ) | ์คํธ๋ ์ค ํ ์คํธ ๋ถ์ |
์ธก์ ๊ทผ๊ฑฐ: 4์๋๋ฆฌ์ค ํ ์คํธ(๊ฐ์ค ๊ธฐ๋ฐ)์์ 15 VUs๊น์ง ์ ์, ์คํธ๋ ์ค ํ ์คํธ(50 VUs)์์ ํฌํ ํ์ธ. ์ค์ธก ์์ TPS ~25 req/s๋ k6๊ฐ ๋์ผ ์ธ์คํด์ค์์ ์คํ๋ ๊ฒฐ๊ณผ์ด๋ฏ๋ก ๋ณ๋ ํด๋ผ์ด์ธํธ ์ฌ์ฉ ์ ๋ ๋์ ์ ์์.
ํ์ฅ ๋ฐฉ๋ฒ:
CONN_MAX_AGE์ค์ ์ผ๋ก ์ปค๋ฅ์ ํ๋ง ํ์ฑํGUNICORN_WORKERS์ฆ๊ฐ (CPU ์ฝ์ด๋น 1-2๊ฐ ๊ถ์ฅ)- ์ธ์คํด์ค ์ ๊ทธ๋ ์ด๋ (e2-medium ์ด์)
๊ธฐ์กด ํ ์คํธ(์ต๋ 15 VUs)์์๋ ์์คํ ์ด ์ฌ์ ์๊ฒ ์ฒ๋ฆฌํ์ฌ ์ค์ ํ๊ณ์ ์ ํ์ ํ์ง ๋ชปํ์ต๋๋ค. ์ด๋ฅผ ๋ณด์ํ๊ธฐ ์ํด VUs๋ฅผ ์ ์ง์ ์ผ๋ก 50๊น์ง ์ฌ๋ฆฌ๋ ์คํธ๋ ์ค ํ ์คํธ๋ฅผ ์ํํ์ต๋๋ค.
์ฃผ์: k6๊ฐ speedcam-app ๋์ผ ์ธ์คํด์ค(e2-small, 2 vCPU)์์ ์คํ๋๋ฏ๋ก, k6 ์์ฒด์ CPU/๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ์ด ๊ฒฐ๊ณผ์ ์ํฅ์ ์ค ์ ์์ต๋๋ค.
ํ ์คํธ ๊ตฌ์ฑ
| Phase | ์๋๋ฆฌ์ค | VUs | ์ง์์๊ฐ | ์์ฒญ ์ ํ |
|---|---|---|---|---|
| Phase 1 | stress_ramp (์ฝ๊ธฐ ์ ์ฉ) | 0โ10โ30โ50โ0 | 3๋ถ30์ด | GET ์ฝ๊ธฐ 100% |
| Phase 2 | stress_mixed (ํผํฉ) | 0โ10โ30โ50โ0 | 3๋ถ | ์ฝ๊ธฐ 80% + ์ฐ๊ธฐ 20% |
์ ์ฒด ๊ฒฐ๊ณผ (Prometheus Remote Write ํ์ฑ, Grafana ๋ฉํธ๋ฆญ ๊ธฐ๋ก๋จ)
์ด ์์ฒญ: 10,525๊ฑด (ํ๊ท 25.1 req/s)
์๋ฌ์จ: 1.50% (158๊ฑด ์คํจ)
p95 ์๋ต์๊ฐ: 2,230ms
์ต๋ ์๋ต์๊ฐ: 4,260ms
์๋ต ์๊ฐ ๋ถํฌ
| ๋ฉํธ๋ฆญ | avg | med | p(90) | p(95) | max |
|---|---|---|---|---|---|
| ์ ์ฒด (req_duration) | 790ms | 742ms | 1,770ms | 2,230ms | 4,260ms |
| ์ฝ๊ธฐ (read_latency) | 800ms | 751ms | 1,770ms | 2,230ms | 4,260ms |
| ์ฐ๊ธฐ (write_latency) | 685ms | 526ms | 1,730ms | 2,020ms | 2,790ms |
Phase๋ณ ์๋ฌ์จ
| Phase | Check | ์ฑ๊ณต๋ฅ | ์คํจ์จ |
|---|---|---|---|
| stress_ramp (50 VUs, ์ฝ๊ธฐ) | status is 200 | 97% | 3% |
| stress_mixed (50 VUs, ์ฝ๊ธฐ) | read 200 | 99% | 1% |
| stress_mixed (50 VUs, ์ฐ๊ธฐ) | write 201 | 99% | 1% |
ํ ์คํธ ํ๊ฒฝ ์ํฅ ์ฐธ๊ณ : k6์ Prometheus Remote Write๊ฐ ๋์ผ ์ธ์คํด์ค(e2-small, 2 vCPU)์์ ์คํ๋์ด, k6์ ์์ฒญ ์์ฑ ์๋๊ฐ ์ ํ๋ฉ๋๋ค (54 req/s โ 25 req/s). ์ด๋ก ์ธํด ์๋ฒ์ ์ค์ ๋๋ฌํ๋ ๋ถํ๊ฐ ์ค์ด ์๋ฌ์จ์ ๋ฎ์์ง๋, ์์คํ ์ ์ฒด ๋ฆฌ์์ค ๊ฒฝํฉ์ผ๋ก ์๋ต ์๊ฐ(p95)์ ์ฆ๊ฐํฉ๋๋ค.
๐ธ [์คํฌ๋ฆฐ์ท ์ฝ์ : k6 Grafana ๋์๋ณด๋ - VUs ๋ณํ์ ๋ฐ๋ฅธ ์๋ต์๊ฐ/์๋ฌ์จ ๊ทธ๋ํ]
๐ธ [์คํฌ๋ฆฐ์ท ์ฝ์ : Container Metrics - speedcam-app์ CPU/Memory ๊ทธ๋ํ (์คํธ๋ ์ค ํ ์คํธ ๊ตฌ๊ฐ)]
๋ถํ ์์ค๋ณ ์ฑ๋ฅ ๋น๊ต (์ค์ธก)
| VUs | ์๋๋ฆฌ์ค | p95 | ์๋ฌ์จ | ์ฒ๋ฆฌ๋ | ํ์ |
|---|---|---|---|---|---|
| 15 | spike_resilience | 49ms | 0% | 6.7 req/s | โ ์ ์ |
| 50 | stress_ramp | 2,230ms | 3% | 25.1 req/s | |
| 50 | stress_mixed | 2,020ms | 1% | 25.1 req/s |
ํต์ฌ ๋ฐ๊ฒฌ:
- 15 VUs โ 50 VUs: p95๊ฐ 49ms์์ 2,230ms๋ก 45๋ฐฐ ์ ํ
- 50 VUs์์ median=742ms โ ๋๋ถ๋ถ์ ์์ฒญ์ด 700ms ์ด์ ์์ (15 VUs์์ 17ms ๋๋น 43๋ฐฐ)
- ์๋ฌ์จ์ 1.5%๋ก ์๋น์ค ๊ฐ์ฉ ๋ฒ์์ด๋, ์๋ต ์๊ฐ ์ ํ๊ฐ ์ฌ๊ฐ (SLA ๊ธฐ์ค ์๋ฐ ๊ฐ๋ฅ)
- ์ฐ๊ธฐ(POST)๊ฐ ์ฝ๊ธฐ(GET) ๋๋น med ๊ธฐ์ค ~30% ๋น ๋ฆ (526ms vs 751ms) โ DB ์ฝ๊ธฐ๊ฐ ์ฐ๊ธฐ๋ณด๋ค ๋ฌด๊ฑฐ์ด ํจํด
- e2-small์์ k6+์๋ฒ ๋์ ์คํ์ ํ๊ณ: ๋ณ๋ ๋ถํ ๋ฐ์๊ธฐ ์ธ์คํด์ค ์ฌ์ฉ ์ ๋ ์ ํํ ์ธก์ ๊ฐ๋ฅ
์ค์ Edge Device์์ ๋ฐ์ํ๋ ๊ณผ์ ๊ฐ์ง ์ด๋ฒคํธ๋ถํฐ OCR ์ฒ๋ฆฌ, ์๋ฆผ ๋ฐ์ก๊น์ง End-to-End ํ์ดํ๋ผ์ธ ์ฑ๋ฅ์ ์ธก์ ํ์ต๋๋ค.
| ํญ๋ชฉ | ์์ธ |
|---|---|
| ํ ์คํธ ๋ฐฉ์ | ๋จ๊ฑด ์์ฐจ ๋ฐํ (๋์ ๋ถํ ์๋) |
| ํ ์คํธ ์ํ ์ | 5๊ฑด (ํต๊ณ์ ์ ์์ฑ๋ณด๋ค๋ ํ์ดํ๋ผ์ธ ๊ฐ ๋จ๊ณ๋ณ ๋์ ๊ฒ์ฆ ๋ชฉ์ ) |
| MQTT ๋ฐํ ์์น | speedcam-app (10.178.0.4) โ speedcam-mq (10.178.0.7), ๋์ผ VPC |
| ํ ์คํธ ์ด๋ฏธ์ง | ํ๊ตญ์ด ๋ฒํธํ ํฉ์ฑ ์ด๋ฏธ์ง 10์ฅ (PIL๋ก ์์ฑ) |
| ์ด๋ฏธ์ง ํน์ง | ๊ณ ๋๋น ํฐ ๋ฐฐ๊ฒฝ + ๊ฒ์ ํ ์คํธ (OCR ์ต์ ํ) |
| GCS ๋ฒํท | gs://speedcam-bucket-4f918446/detections/ |
| OCR Worker | EasyOCR (Korean + English), concurrency=1, Warm ์ํ (๋ชจ๋ธ ์ฌ์ ๋ก๋ฉ) |
| ์ธ์ฆ ๋ฐฉ์ | GCE ADC (๋ฉํ๋ฐ์ดํฐ ์๋ฒ, JSON ํค ์์) |
| ์ธก์ ๋ฐฉ๋ฒ | ๊ฐ ์ปจํ ์ด๋ ๋ก๊ทธ์ ํ์์คํฌํ ๋น๊ต (Loki ์์ง) |
์ฐธ๊ณ : ๋ณธ ํ ์คํธ๋ ๋์ ๋ค๋ฐ์ ์ธ ๋ถํ ์ํฉ์ด ์๋, ํ์ดํ๋ผ์ธ ๊ฐ ๋จ๊ณ์ ๋จ์ ์ฒ๋ฆฌ ์๊ฐ ์ธก์ ์ ์ด์ ์ ๋ง์ถ์์ต๋๋ค. ๋๋ ๋์ ์ฒ๋ฆฌ ์์ ์ฑ๋ฅ์ ํ ๊น์ด ์ฆ๊ฐ์ OCR Worker ๋๊ธฐ ์๊ฐ ๋ฑ์ ์ถ๊ฐ ์์๊ฐ ๋ฐ์ํฉ๋๋ค.
์ ์ฒด ํ์ดํ๋ผ์ธ์ ๋ค์๊ณผ ๊ฐ์ด 3๋จ๊ณ๋ก ๊ตฌ์ฑ๋ฉ๋๋ค:
Stage 1: MQTT ์์ โ Detection ์์ฑ โ OCR Task ๋์คํจ์น
Stage 2: AMQP ์ ๋ฌ (Subscriber โ OCR Worker)
Stage 3: OCR ์ฒ๋ฆฌ (GCS ๋ค์ด๋ก๋ + EasyOCR ์ถ๋ก )
Stage 1: MQTT ์์ โ Detection ์์ฑ โ OCR Task ๋์คํจ์น (Subscriber)
| Detection ID | MQTT ์์ ์๊ฐ | Detection ์์ฑ | OCR ๋์คํจ์น | ์ด Subscriber ์ฒ๋ฆฌ ์๊ฐ |
|---|---|---|---|---|
| #3284 | 01:19:00.489 | 01:19:00.554 | 01:19:00.560 | 71ms |
| #3285 | 01:49:54.207 | 01:49:54.222 | 01:49:54.229 | 22ms |
| #3286 | 01:56:37.918 | 01:56:37.925 | 01:56:37.928 | 10ms |
| #3287 | 02:09:11.080 | 02:09:11.091 | 02:09:11.097 | 17ms |
| #3288 | 02:15:44.137 | 02:15:44.145 | 02:15:44.148 | 11ms |
ํ๊ท Subscriber ์ฒ๋ฆฌ ์๊ฐ: 15ms (Cold Start #3284 ์ ์ธ)
- JSON ํ์ฑ + DB Insert + AMQP Publish ํฌํจ
- #3284์ 71ms๋ ์ฒซ ์์ฒญ ์ DB ์ปค๋ฅ์ ์๋ฆฝ ์๊ฐ์ด ํฌํจ๋ ์ด์๊ฐ (์ดํ ์์ ํ)
Stage 2: AMQP ์ ๋ฌ (Subscriber โ OCR Worker)
| Detection ID | ๋์คํจ์น ์๊ฐ | Worker ์์ ์๊ฐ | AMQP ์ ๋ฌ ์๊ฐ |
|---|---|---|---|
| #3284 | 01:19:00.560 | 01:19:00.563 | 3ms |
| #3285 | 01:49:54.229 | 01:49:54.230 | 1ms |
| #3286 | 01:56:37.928 | 01:56:37.935 | 7ms |
ํ๊ท AMQP ์ ๋ฌ ์๊ฐ: ~3ms
- RabbitMQ ๋ด๋ถ ๋ผ์ฐํ ์ค๋ฒํค๋ ๋งค์ฐ ๋ฎ์
Stage 3: OCR ์ฒ๋ฆฌ (GCS ๋ค์ด๋ก๋ + EasyOCR ์ถ๋ก )
| Detection ID | ์ด๋ฏธ์ง | OCR ์ฒ๋ฆฌ ์๊ฐ | ์ธ์ ๊ฒฐ๊ณผ | ์ ๋ขฐ๋ | ๋น๊ณ |
|---|---|---|---|---|---|
| #3284 | test-plate-1.jpg (ํฐ ์ด๋ฏธ์ง) | 35.59s | None | 0% | Cold Start (๋ชจ๋ธ ๋ก๋ฉ ํฌํจ) |
| #3285 | plate-01.jpg (์๋์ฐจ ๋ฐฐ๊ฒฝ) | 8.39s | None | 0% | Warm, ๋ฐฐ๊ฒฝ ๋ ธ์ด์ฆ๋ก ์ธ์ ์คํจ |
| #3286 | real-plate-01.jpg (๊ณ ๋๋น) | 5.15s | 12๊ฐ3456 | 72.1% | โ ์ ์ ์ธ์ |
| #3287 | real-plate-02.jpg (๊ณ ๋๋น) | 5.11s | 34๋5678 | 86.8% | โ ์ ์ ์ธ์ |
| #3288 | real-plate-03.jpg (๊ณ ๋๋น) | 5.02s | 56๋ค7890 | 98.8% | โ ์ ์ ์ธ์ |
OCR ์ฑ๋ฅ ์์ฝ:
| ์งํ | ๊ฐ |
|---|---|
| Cold Start (๋ชจ๋ธ ๋ก๋ฉ ํฌํจ) | ~35s |
| Warm OCR ํ๊ท | ~5.1s (GCS ๋ค์ด๋ก๋ ~0.5s + EasyOCR ์ถ๋ก ~4.6s) |
| OCR ์ต๋ TPS | ~0.2 msg/s (1 worker, concurrency=1) |
| ๊ณ ๋๋น ํ๊ตญ์ด ๋ฒํธํ ์ธ์๋ฅ | 100% (3/3) |
| ํ๊ท ์ ๋ขฐ๋ | 85.9% |
์ฃผ์ ์ธ์ฌ์ดํธ:
- ๊ณ ๋๋น ํ๊ตญ์ด ๋ฒํธํ ์ด๋ฏธ์ง์์ OCR ์ธ์๋ฅ 100%
- ๋ฐฐ๊ฒฝ ๋ ธ์ด์ฆ๊ฐ ์๋ ์ด๋ฏธ์ง๋ ์ธ์ ์คํจ (์ ์ฒ๋ฆฌ ํ์)
- Warm ์ํ OCR ์ฒ๋ฆฌ ์๊ฐ 5.1s๋ ๋จ์ผ ์์ปค ๊ธฐ์ค์ผ๋ก ์ ์
์ ์ฒด ํ์ดํ๋ผ์ธ์ ๊ฐ ๋จ๊ณ๋ณ ์์ ์๊ฐ์ ์ ๋ฆฌํ๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
Edge Device
โ MQTT Publish (~50ms network)
RabbitMQ MQTT Plugin
โ Internal routing (~1ms)
Django Subscriber (MQTT โ DB โ AMQP)
โ ~15ms (JSON parse + DB insert + AMQP publish)
RabbitMQ AMQP Queue
โ ~3ms (queue routing)
OCR Worker
โ ~5,100ms (GCS download + EasyOCR inference)
DB Update (completed)
โ ~10ms
Alert Queue โ FCM Notification
โ (FCM ๋ฏธ๊ตฌํ ์ํ)
Total E2E: ~5,200ms (warm) / ~35,700ms (cold start)
๋ณ๋ชฉ ์ง์ :
- OCR Worker (5.1s): ์ ์ฒด ํ์ดํ๋ผ์ธ์ 98% ์ฐจ์ง
- GCS ๋ค์ด๋ก๋: ~0.5s
- EasyOCR ์ถ๋ก : ~4.6s
๊ฐ์ ๋ฐฉ์:
- GPU ์ธ์คํด์ค ์ ํ: CPU โ GPU๋ก OCR ์ถ๋ก ์๊ฐ ๋จ์ถ (5s โ <1s ๋ชฉํ)
- ๊ฒฝ๋ OCR ๋ชจ๋ธ: PaddleOCR ๋ฑ ๋ ๋น ๋ฅธ ๋ชจ๋ธ ๊ฒํ
- ์ด๋ฏธ์ง ์ ์ฒ๋ฆฌ: Edge Device์์ ๊ณ ๋๋น ์ ์ฒ๋ฆฌ ์ํ
๋จ๊ฑด ์์ฐจ ํ ์คํธ(4.4.2)์์ ์ธก์ ํ ๋จ์ ์ฒ๋ฆฌ ์๊ฐ์ ๋ฐํ์ผ๋ก, 20๋ ์นด๋ฉ๋ผ๊ฐ ๋์ ์ด์๋๋ ์ค์ ์ฌ์ฉ ํจํด์์์ ํ์ดํ๋ผ์ธ ์ฑ๋ฅ์ 3๋จ๊ณ ์๋๋ฆฌ์ค๋ก ์ธก์ ํ์ต๋๋ค.
์ค์: ๋ชจ๋ MQTT ํ ์คํธ๋ ์ค์ EasyOCR ํ๊ฒฝ์์ ์ํ๋์์ต๋๋ค (OCR_MOCK=false).
ํ ์คํธ ๊ตฌ์ฑ
| ์๋๋ฆฌ์ค | ์นด๋ฉ๋ผ ์ | ๋ฐํ ์๋ | ์ง์์๊ฐ | ์์ ๋ฉ์์ง | ๋ชฉ์ |
|---|---|---|---|---|---|
| Normal | 20๋ | 1๊ฑด/๋ถ/์นด๋ฉ๋ผ (0.33 msg/s) | 120์ด | 40๊ฑด | ์ ์ ์ด์ ํจํด |
| Rush Hour | 20๋ | 5๊ฑด/๋ถ/์นด๋ฉ๋ผ (1.67 msg/s) | 120์ด | 200๊ฑด | ๋ฌ์์์ ํธ๋ํฝ |
| Burst | 20๋ | 1๊ฑด/์ด/์นด๋ฉ๋ผ (20 msg/s) | 60์ด | 1,200๊ฑด | ๊ทนํ ์คํธ๋ ์ค |
๊ณตํต ์ค์ : ์ค์ GCS ๋ฒํธํ ์ด๋ฏธ์ง 10์ฅ ์ํ ์ฌ์ฉ, API ํต๊ณ ํด๋ง + RabbitMQ ํ ๊น์ด ๋ชจ๋ํฐ๋ง, ํ์ดํ๋ผ์ธ ์๋ฃ ๋๊ธฐ ํ์์์ 300์ด
์๋๋ฆฌ์ค๋ณ ๋ฐํ ๊ฒฐ๊ณผ
| ์๋๋ฆฌ์ค | ๋ฐํ ์ฑ๊ณต | ๋ฐํ ์คํจ | ํ๊ท ๋ฐํ ์ง์ฐ | ์ค์ธก ๋ฐํ ์๋ |
|---|---|---|---|---|
| Normal | 40/40 (100%) | 0๊ฑด | 0.91ms | 0.33 msg/s |
| Rush Hour | 200/200 (100%) | 0๊ฑด | 0.38ms | 1.66 msg/s |
| Burst | 1,200/1,200 (100%) | 0๊ฑด | 0.37ms | 19.96 msg/s |
์ ์๋๋ฆฌ์ค์์ MQTT ๋ฐํ 100% ์ฑ๊ณต. RabbitMQ๊ฐ 20 msg/s๊น์ง ์์ ์ ์ผ๋ก ์์ฉ.
์๋๋ฆฌ์ค๋ณ ํ์ดํ๋ผ์ธ ์ฒ๋ฆฌ ๊ฒฐ๊ณผ (๊ฐ์ค vs ์ค์ธก)
| ์งํ | Normal ๊ฐ์ค | Normal ์ค์ธก | Rush Hour ๊ฐ์ค | Rush Hour ์ค์ธก | Burst ๊ฐ์ค | Burst ์ค์ธก |
|---|---|---|---|---|---|---|
| ๋ฐํ ์ฑ๊ณต๋ฅ | 100% | 100% โ | 100% | 100% โ | 100% | 100% โ |
| ์๋ฃ์จ (300s) | 100% | 80% (32/40) โ | 95% | 11% (22/200) โ | 100% (drain) | 1.5% (18/1200) โ |
| E2E ์๋ฃ ์๊ฐ | 60์ด | 300์ด TO โ | 120์ด | 300์ด TO โ | 300์ด | 300์ด TO โ |
| OCR ํ ํผํฌ | < 5 | 25 โ | < 50 | 202 โ | 200-500 | 1,381 โ |
| DLQ ๋ฉ์์ง | 0 | 0 โ | 0 | 0 โ | 0 | 0 โ |
๊ฐ์ค์ OCR_MOCK=true ๊ธฐ์ค์ผ๋ก ์์ฑ. ์ค์ EasyOCR ํ๊ฒฝ์์๋ OCR ์ฒ๋ฆฌ ์๋๊ฐ 133~667๋ฐฐ ๋๋ฆผ.
Normal ์๋๋ฆฌ์ค - OCR ํ ๋๋ ์ธ ์ถ์ด
์๊ฐ(s) ์๋ฃ ๋๊ธฐ OCRํ FCMํ ์คํจ ์ฒ๋ฆฌ์๋
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
10 16 24 24 0 -
50 19 21 22 0 0.075 msg/s
100 21 19 19 0 0.040 msg/s
150 24 16 16 0 0.060 msg/s
200 26 14 14 0 0.040 msg/s
250 29 11 11 0 0.060 msg/s
300 32 8 9 0 0.060 msg/s (ํ์์์)
Rush Hour ์๋๋ฆฌ์ค - OCR ํ ๋๋ ์ธ ์ถ์ด
์๊ฐ(s) ์๋ฃ ๋๊ธฐ OCRํ FCMํ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
10 7 193 201 0 โ ๋ฐํ ์งํ ํ ํญ์ฃผ
60 10 190 198 0
120 13 187 195 0
180 16 184 193 0
240 19 181 189 0
300 22 178 186 0 โ ํ์์์, 178๊ฑด ๋ฏธ์ฒ๋ฆฌ
Burst ์๋๋ฆฌ์ค - OCR ํ ๋๋ ์ธ ์ถ์ด
์๊ฐ(s) ์๋ฃ ๋๊ธฐ OCRํ FCMํ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
10 3 1197 1,381 0 โ 1,200๊ฑด + ๊ธฐ์กด ๋ฐฑ๋ก๊ทธ
60 6 1194 1,378 0
120 9 1191 1,375 0
180 12 1188 1,372 0
240 15 1185 1,369 0
300 18 1182 1,366 0 โ ํ์์์, 1,182๊ฑด ๋ฏธ์ฒ๋ฆฌ
๐ธ [์คํฌ๋ฆฐ์ท ์ฝ์ : RabbitMQ ๋์๋ณด๋ - OCR ํ ๊น์ด ๋ณํ (3 ์๋๋ฆฌ์ค ์ ์ฒด ๊ตฌ๊ฐ)]
๐ธ [์คํฌ๋ฆฐ์ท ์ฝ์ : Celery Workers ๋์๋ณด๋ - OCR Task ์ฒ๋ฆฌ ์๋ (ํ ์คํธ ๊ตฌ๊ฐ)]
๐ธ [์คํฌ๋ฆฐ์ท ์ฝ์ : Container Metrics - speedcam-ocr CPU/Memory (ํ ์คํธ ๊ตฌ๊ฐ)]
ํต์ฌ ๋ฐ๊ฒฌ โ OCR ์ฒ๋ฆฌ ์๋ ๋น๊ต
| ์งํ | ๋จ๊ฑด (4.4.2) | Normal | Rush Hour | Burst |
|---|---|---|---|---|
| OCR ์ฒ๋ฆฌ ์๋ | 0.2 msg/s (5.1s/๊ฑด) | 0.053 msg/s (18.8s/๊ฑด) | 0.073 msg/s (13.7s/๊ฑด) | 0.060 msg/s (16.7s/๊ฑด) |
| OCR ํ ํผํฌ | 0 | 25 | 202 | 1,381 |
| ํ์ดํ๋ผ์ธ ์๋ฃ์จ | 100% | 80% | 11% | 1.5% |
| ๋ถํ ์ ์ฑ๋ฅ ์ ํ | - | 3.7๋ฐฐ | 2.7๋ฐฐ | 3.3๋ฐฐ |
๋์ ๋ถํ ์ OCR ์ฒ๋ฆฌ ์๋ ์ ํ ์์ธ ๋ถ์:
- ๋ฉ๋ชจ๋ฆฌ ์๋ฐ: e2-small(2GB)์์ EasyOCR ๋ชจ๋ธ(1.5GB) + ํ ๋ฒํผ โ 721MB ์ฌ์ ๋ถ ์์ง
- GCS ๋ค์ด๋ก๋ ๊ฒฝํฉ: ์ฐ์ ๋ค์ด๋ก๋ ์ ๋คํธ์ํฌ/API ์ง์ฐ ์ฆ๊ฐ
- CPU ๊ฒฝํฉ: OCR ์ถ๋ก ์ค Celery ํ ๊ด๋ฆฌ ์ค๋ฒํค๋
- ํ ๋ฐฑ๋ก๊ทธ ๋์ : Rush Hour/Burst ํ ํ ๋๋ ์ธ์ ์ ์๊ฐ ์์ (Burst ํ ์์ฌ 1,362๊ฑด โ ์ฝ 6.3์๊ฐ)
๊ฒฐ๋ก : ๊ฐ์ฅ ๋๊ด์ ์ธ ์๋๋ฆฌ์ค(Normal, 0.33 msg/s)์์๋ OCR Worker๊ฐ ์ฒ๋ฆฌ๋ฅผ ๋ฐ๋ผ๊ฐ์ง ๋ชปํฉ๋๋ค. OCR Worker ํ์ฅ(์ํ ๋๋ GPU ์ ํ)์ ์ ํ์ด ์๋ ํ์์ ๋๋ค.
Event Driven Architecture ์ ํ์ ํตํด ๊ธฐ์กด ๋ชจ๋๋ฆฌ์ ๊ตฌ์กฐ์ ๋ชจ๋ ํต์ฌ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ์ต๋๋ค.
| ํญ๋ชฉ | Before (๋๊ธฐ HTTP) | After (Event Driven) | ๊ฐ์ ์จ | ์ธก์ ๊ทผ๊ฑฐ |
|---|---|---|---|---|
| ์ด๋ฒคํธ ์ฒ๋ฆฌ ์๊ฐ (์์ ~๋์คํจ์น) | 3,000ms+ | 15ms | 200๋ฐฐ ๋น ๋ฆ | Before: ๊ตฌ์กฐ ์ถ์ / After: ์ค์ธก (n=4) |
| Edge Device ๋ธ๋กํน | 3,000ms+ | 0ms (๋น๋๊ธฐ) | ์์ ํด์ | Before: ๊ตฌ์กฐ ์ถ์ / After: MQTT QoS 1 PUBACK |
| ๋ฉ์์ง ๋ณด์ฅ | ์์ | QoS 1 (At-Least-Once) | ๋ฉ์์ง ๋ฌด์์ค | ํ๋กํ ์ฝ ์ฌ์ |
| ์ฅ์ ๊ฒฉ๋ฆฌ | ์ ์ฒด ์ํฅ | ์ปดํฌ๋ํธ๋ณ ๊ฒฉ๋ฆฌ | ๋ ๋ฆฝ ์ด์ | ์ํคํ ์ฒ ์ค๊ณ |
| ํ์ฅ์ฑ | ์๋ฒ ์ ์ฒด | Worker๋ณ ๋ ๋ฆฝ | ์ธ๋ฐํ ํ์ฅ | ์ํคํ ์ฒ ์ค๊ณ |
| HTTP API p95 | N/A | 38.85ms | - | ์ค์ธก (k6 4์๋๋ฆฌ์ค, n=2,297) |
| ์คํ์ดํฌ ๋์ | ์๋ฒ ๋ค์ด ์ํ | 15 VUs์์ ์์ (์๋ฌ์จ 0%) | ๊ณ ๊ฐ์ฉ์ฑ | ์ค์ธก (k6 spike ์๋๋ฆฌ์ค) |
๋น๊ต ๊ธฐ์ค ์ฐธ๊ณ : Before ์์น๋ ๋๊ธฐ OCR ์ฒ๋ฆฌ ๊ตฌ์กฐ(HTTP ์์ฒญ โ OCR ์๋ฃ ํ ์๋ต)์์์ ์ค๊ณ ๊ธฐ๋ฐ ์ถ์ ๊ฐ์ด๋ฉฐ, After ์์น๋ ํ์ฌ ์ด์ ํ๊ฒฝ์์์ ์ค์ธก๊ฐ์ ๋๋ค.
graph LR
subgraph Before["๊ธฐ์กด ์ํคํ
์ฒ"]
B1["Django<br/>(API + OCR)"]
B2["3์ด+ ์๋ต"]
B3["HTTP ์ค๋ฒํค๋"]
B4["์ฅ์ ์ ํ"]
style B1 fill:#ffcccc
style B2 fill:#ffcccc
style B3 fill:#ffcccc
style B4 fill:#ffcccc
end
subgraph After["Event Driven Architecture"]
A1["Django<br/>(API๋ง)"]
A2["15ms ์ฒ๋ฆฌ"]
A3["MQTT+AMQP"]
A4["์ฅ์ ๊ฒฉ๋ฆฌ"]
style A1 fill:#90EE90
style A2 fill:#90EE90
style A3 fill:#90EE90
style A4 fill:#90EE90
end
Before -->|"์ํคํ
์ฒ ์ ํ"| After
| ๊ธฐ์กด ๋ฌธ์ | ํด๊ฒฐ ๋ฐฉ๋ฒ | ํจ๊ณผ |
|---|---|---|
| OCR ๋๊ธฐ ์ฒ๋ฆฌ | OCR Worker ๋ถ๋ฆฌ + AMQP ๋น๋๊ธฐ ์ฒ๋ฆฌ | ์ด๋ฒคํธ ์ฒ๋ฆฌ์๊ฐ 3000ms โ 15ms |
| Edge Device ๋ธ๋กํน | MQTT QoS 1 + ์ฆ์ ACK | ์ฐ์ ๊ฐ์ง ๊ฐ๋ฅ, ๋ฐ์ดํฐ ์ ์ค ๋ฐฉ์ง |
| HTTP IoT ํต์ | MQTT ํ๋กํ ์ฝ ๋์ | ๊ฒฝ๋ ํ๋กํ ์ฝ, ๋ฉ์์ง ๋ณด์ฅ, ์คํ๋ผ์ธ ๋ฒํผ๋ง |
| ์ฅ์ ์ ํ | ์ปดํฌ๋ํธ ๋ถ๋ฆฌ + ์ด๋ฒคํธ ํ ๋ณด์กด | OCR ์ฅ์ ์์๋ API ์ ์ ์ด์ |
์ด 7๊ฐ์ ์ปค์คํ ๋์๋ณด๋๋ฅผ ์ด์ํ์ฌ ์์คํ ์ ๋ชจ๋ ๊ณ์ธต์ ๋ชจ๋ํฐ๋งํฉ๋๋ค.
| ๋์๋ณด๋ | ์ฉ๋ |
|---|---|
| k6 Prometheus Dashboard | HTTP API ๋ฉํธ๋ฆญ ์ค์๊ฐ ์๊ฐํ |
| System Overview | ์ ์ฒด ์์คํ ๋ฆฌ์์ค ํํฉ |
| Container Metrics | Docker ์ปจํ ์ด๋๋ณ CPU/Memory/Network |
| MySQL Performance | ์ฟผ๋ฆฌ ์ฑ๋ฅ, ์ปค๋ฅ์ , ์ฌ๋ก์ฐ ์ฟผ๋ฆฌ |
| RabbitMQ Monitoring | ๋ฉ์์ง ํ ๊น์ด, ์ฒ๋ฆฌ๋, ์ปจ์๋จธ |
| Celery Workers | Task ์ฒ๋ฆฌ๋, ์ง์ฐ ์๊ฐ, ์คํจ์จ |
| Application Logs | Loki ๊ธฐ๋ฐ ํตํฉ ๋ก๊ทธ ๊ฒ์ |
๐ธ [์คํฌ๋ฆฐ์ท ์ฝ์ : System Overview ๋์๋ณด๋ - 6๊ฐ ์ธ์คํด์ค CPU/Memory ์ ์ฒด ํํฉ]
๐ธ [์คํฌ๋ฆฐ์ท ์ฝ์ : MySQL Performance ๋์๋ณด๋ - ์ปค๋ฅ์ ์ ๋ณํ (๋ถํ ํ ์คํธ ๊ตฌ๊ฐ)]
์ด 11๊ฐ ํ๊ฒ (All UP)
| ํ๊ฒ | ์ธ์คํด์ค | ์ํ |
|---|---|---|
| cAdvisor | speedcam-app | โ UP |
| cAdvisor | speedcam-db | โ UP |
| cAdvisor | speedcam-mq | โ UP |
| cAdvisor | speedcam-ocr | โ UP |
| cAdvisor | speedcam-alert | โ UP |
| cAdvisor | speedcam-mon | โ UP |
| django | speedcam-app | โ UP |
| mysql | speedcam-db | โ UP |
| rabbitmq | speedcam-mq | โ UP |
| celery | speedcam-ocr | โ UP |
| otel | speedcam-mon | โ UP |
๐ธ [์คํฌ๋ฆฐ์ท ์ฝ์ : Prometheus โ Status โ Targets ํ์ด์ง (11๊ฐ ํ๊ฒ All UP)]
์ด 16๊ฐ ์ปจํ ์ด๋ ๋ก๊ทธ ์์ง (Promtail โ Loki)
- Django, Gunicorn, Celery Workers
- MySQL, RabbitMQ
- Traefik, Flower
- Prometheus, Grafana, Loki, Jaeger, OpenTelemetry Collector
๊ฐ ์ปดํฌ๋ํธ๋ณ ์ต๋ ์ฒ๋ฆฌ ์ฑ๋ฅ๊ณผ ๋ณ๋ชฉ ์ง์ ์ ๋ถ์ํ์ต๋๋ค.
| ์ปดํฌ๋ํธ | ์ด๋ก ๊ฐ | ์ค์ธก๊ฐ | ๊ทผ๊ฑฐ | ๋ณ๋ชฉ ์์ธ |
|---|---|---|---|---|
| HTTP API (Django) | ~80-100 req/s | 25 req/s (50VUs) | k6 ์คํธ๋ ์ค ํ ์คํธ ์ค์ธก | Gunicorn 4 handlers + k6 ๋ฆฌ์์ค ๊ฒฝํฉ |
| HTTP API (15VUs) | - | 6.75 req/s (p95=39ms) | k6 4์๋๋ฆฌ์ค ์ค์ธก (์ค์ ์ฌ์ฉ ํจํด) | sleep ๊ฐ๊ฒฉ์ผ๋ก ๋ฎ์ req/s, ์๋ต์ ๋น ๋ฆ |
| MQTT Subscriber | ~40 msg/s | 20 msg/s ๋ฌด์์ค | Burst ์๋๋ฆฌ์ค (1200๊ฑด/60์ด) | ๋จ์ผ ์ค๋ ๋ loop_forever() |
| MQTT Publish | - | 0.37~0.91ms/๊ฑด | 3๊ฐ ์๋๋ฆฌ์ค ์ค์ธก | ์ง์ฐ ๋ฌด์ ๊ฐ๋ฅ |
| AMQP Broker | ~10,000 msg/s | - | RabbitMQ ๊ณต์ ๋ฒค์น๋งํฌ ์ฐธ๊ณ | ์ถฉ๋ถํ ์ฌ์ (๋ณ๋ชฉ ์์) |
| OCR Worker (๋จ๊ฑด) | ~0.2 msg/s | 0.2 msg/s | ๋จ๊ฑด ์ค์ธก (5.1s/๊ฑด, n=3) | EasyOCR CPU ์ถ๋ก |
| OCR Worker (๋ถํ ์) | - | 0.053~0.073 msg/s | 3๊ฐ ์๋๋ฆฌ์ค ์ค์ธก (13.7~18.8s/๊ฑด) | ๋ฉ๋ชจ๋ฆฌ ์๋ฐ + GCS ๊ฒฝํฉ |
| Alert Worker | ~100 msg/s | - | ์ถ์ (concurrency=100 ์ค์ ) | FCM API ํธ์ถ |
| MySQL | ~500 qps | - | ์ถ์ (e2-medium ๋ฒค์น๋งํฌ) | e2-medium 4GB RAM |
์ฐธ๊ณ : HTTP ์ค์ธก๊ฐ์ k6๊ฐ ๋์ผ ์ธ์คํด์ค(e2-small)์์ ์คํ๋ ๊ฒฐ๊ณผ. MQTT Subscriber๋ Burst(20 msg/s)์์๋ 1,200๊ฑด ์ ๋ ์์ ํ์ฌ ๋จ์ผ ์ค๋ ๋์์๋ ์ถฉ๋ถํ ์ฒ๋ฆฌ๋ ํ์ธ. OCR Worker๊ฐ ์ ์ฒด ํ์ดํ๋ผ์ธ์ ์ง๋ฐฐ์ ๋ณ๋ชฉ.
ํ์ฌ ๋ณ๋ชฉ: OCR Worker
graph LR
A["HTTP API<br/>25 req/s (์ค์ธก)"] ~~~ B
B["MQTT Subscriber<br/>20 msg/s ์ฒ๋ฆฌ ํ์ธ"] -->|"๋ณ๋ชฉ"| C["OCR Worker<br/>0.06 msg/s (๋ถํ์ ์ค์ธก)"]
C --> D["Alert Worker<br/>~100 msg/s (์ถ์ )"]
style C fill:#ff6666
์ค์ธก ๋ฐ์ดํฐ ๊ธฐ๋ฐ ๋ณ๋ชฉ ๋ถ์ (3 ์๋๋ฆฌ์ค ์ข ํฉ):
- OCR Worker๊ฐ ์ ์ฒด ํ์ดํ๋ผ์ธ์ ์ง๋ฐฐ์ ๋ณ๋ชฉ์์ด 3๊ฐ ์๋๋ฆฌ์ค์์ ์ผ๊ด๋๊ฒ ํ์ธ๋จ
- ๋จ๊ฑด ์ฒ๋ฆฌ: 5.1s/๊ฑด (0.2 msg/s) โ ๋์ ๋ถํ ์: 13.7
18.8s/๊ฑด (0.0530.073 msg/s)๋ก 2.7~3.7๋ฐฐ ์ฑ๋ฅ ์ ํ - Normal(0.33 msg/s)์์๋ ํ ํผํฌ 25, 300์ด ๋ด 80%๋ง ์๋ฃ
- Rush Hour(1.67 msg/s)์์ ํ ํผํฌ 202, 300์ด ๋ด 11%๋ง ์๋ฃ
- Burst(20 msg/s)์์ ํ ํผํฌ 1,381, 300์ด ๋ด 1.5%๋ง ์๋ฃ โ ๋๋ ์ธ ์ฝ 6.3์๊ฐ ์์
- e2-small(2GB)์์ EasyOCR concurrency=1๋ง ๊ฐ๋ฅ (๋ฉ๋ชจ๋ฆฌ ์ ์ฝ)
ํด๊ฒฐ ๋ฐฉ์:
| ๋ฐฉ๋ฒ | ์์ ๊ฐ์ | ๋น์ฉ | ๋์ด๋ |
|---|---|---|---|
| OCR ์ธ์คํด์ค ์ถ๊ฐ (horizontal) | 0.053 msg/s ร N | ์ | ๋ฎ์ |
| GPU ์ธ์คํด์ค ์ ํ | 5.1s โ <1s (5x+) | ์ค | ์ค |
| e2-medium ์ ๊ทธ๋ ์ด๋ | ๋ฉ๋ชจ๋ฆฌ ์ฌ์ ๋ก ๋ถํ ์ ์ฑ๋ฅ ์ ํ ์ํ | ์ | ๋ฎ์ |
| ๊ฒฝ๋ OCR ๋ชจ๋ธ (PaddleOCR) | ~2-3x ๋น ๋ฆ | ์ | ์ค |
| Edge ์ ์ฒ๋ฆฌ | ์ด๋ฏธ์ง ํฌ๊ธฐ ๊ฐ์ | ์ | ๋ฎ์ |
| ์ด์ | ์์ธ | ํด๊ฒฐ ๋ฐฉ๋ฒ |
|---|---|---|
| MQTT Subscriber Stale DB Connection | ์ฅ๊ธฐ ์คํ ์ค๋ ๋์์ MySQL ์ฐ๊ฒฐ ๋ง๋ฃ | close_old_connections() ์ถ๊ฐ๋ก ํด๊ฒฐ |
| OCR Worker OOM | EasyOCR ๋ชจ๋ธ ร 4 workers = 6GB (e2-small 2GB ์ด๊ณผ) | concurrency=1๋ก ์กฐ์ |
| GCS ์ธ์ฆ | JSON ํค ํ์ผ ์์ | ADC(๋ฉํ๋ฐ์ดํฐ ์๋ฒ) ํ์ฉ์ผ๋ก ํด๊ฒฐ |
| ์ด์ | ํ์ฌ ์ํ | ์ํฅ๋ | ๊ฐ์ ๋ฐฉ์ |
|---|---|---|---|
| FCM ํ ํฐ ์ ๋ฐ์ดํธ API | PATCH ์๋ํฌ์ธํธ 0% ์ฑ๊ณต๋ฅ | ๐ด High | API endpoint ๋ก์ง ์์ |
| OCR Worker ํ์ฅ์ฑ | ๋จ์ผ ์์ปค 0.2 msg/s | ๐ด High | GPU ์ธ์คํด์ค ๋๋ ๊ฒฝ๋ OCR ๋ชจ๋ธ ๊ฒํ |
| ๋ชจ๋ํฐ๋ง ์ธ์คํด์ค ๋ฉ๋ชจ๋ฆฌ | 264MB ์ฌ์ (๋ฉ๋ชจ๋ฆฌ ๋ถ์กฑ ์ํ) | ๐ก Medium | e2-medium ์ ๊ทธ๋ ์ด๋ ๊ถ์ฅ |
| ์ด์ | ํ์ฌ ์ํ | ์ํฅ๋ | ๊ฐ์ ๋ฐฉ์ |
|---|---|---|---|
| CONN_MAX_AGE ๋ฏธ์ค์ | ๋งค ์์ฒญ ์ DB ์ปค๋ฅ์ | ๐ก Medium | ์ปค๋ฅ์ ํ๋ง ์ค์ (์ฑ๋ฅ 10-20% ๊ฐ์ ์์) |
| MQTT Subscriber ๋จ์ผ ์ค๋ ๋ | ๋ณ๋ชฉ ์ ๋ฉ์์ง ํ์ | ๐ก Medium | ์ค๋ ๋ํ or ๋ฉํฐ ํ๋ก์ธ์ค ๊ฒํ |
| speedcam-ocr ๋์คํฌ ์ฌ์ฉ๋ฅ | 87% (17GB/20GB) | ๐ก Medium | ๋์คํฌ ์ ๋ฆฌ ๋๋ ํ์ฅ |
| ํญ๋ชฉ | ๋ชฉํ | ์์ ํจ๊ณผ |
|---|---|---|
| ์ฝ๊ธฐ ๋ณต์ ๋ณธ ์ถ๊ฐ | MySQL ์ฝ๊ธฐ ๋ถํ ๋ถ์ฐ | ์ฟผ๋ฆฌ ์ฑ๋ฅ ํฅ์ |
| Redis ์บ์ฑ | ํต๊ณ ์กฐํ ์บ์ฑ | API ์๋ต ์๋ ํฅ์ |
| Celery Beat ์ถ๊ฐ | ์ฃผ๊ธฐ์ ์์ ์๋ํ | ์ด์ ํจ์จ์ฑ ํฅ์ |
SpeedCam ์์คํ ์ ๊ธฐ์กด ๋๊ธฐ์ HTTP ๊ธฐ๋ฐ ๋ชจ๋๋ฆฌ์ ์ํคํ ์ฒ์์ Event Driven Architecture๋ก ์ฑ๊ณต์ ์ผ๋ก ์ ํํ์์ต๋๋ค.
์ ๋์ ์ฑ๊ณผ:
| ์งํ | Before | After | ๊ฐ์ | ๊ทผ๊ฑฐ |
|---|---|---|---|---|
| ์ด๋ฒคํธ ์ฒ๋ฆฌ ์๊ฐ | 3,000ms+ ยน | 15ms | 200๋ฐฐ | After ์ค์ธก (n=4) |
| Edge Device ๋ธ๋กํน | 3,000ms+ ยน | 0ms | ์์ ํด์ | MQTT PUBACK |
| HTTP API p95 | N/A | 38.85ms | - | k6 4์๋๋ฆฌ์ค ์ค์ธก (n=2,297) |
| MQTT ๋ฐํ ์ฑ๊ณต๋ฅ | N/A | 100% (20 msg/s๊น์ง) | - | MQTT 3์๋๋ฆฌ์ค ์ค์ธก (n=1,440) |
| ๋ฉ์์ง ๋ณด์ฅ | ์์ | QoS 1 | ๋ฌด์์ค | ํ๋กํ ์ฝ ์ฌ์ + DLQ 0๊ฑด ์ค์ธก |
| ์คํ์ดํฌ ์๋ฌ์จ | ์๋ฒ ๋ค์ด ์ํ ยน | 0% | ๊ณ ๊ฐ์ฉ์ฑ | k6 ์ค์ธก (15 VUs) |
ยน Before ์์น๋ ๋๊ธฐ OCR ์ฒ๋ฆฌ ๊ตฌ์กฐ ๊ธฐ๋ฐ ์ค๊ณ ์ถ์ ๊ฐ (๋ณ๋ ๋ถํ ํ ์คํธ ๋ฏธ์ํ)
์ ์ฑ์ ์ฑ๊ณผ:
- ์ฅ์ ๊ฒฉ๋ฆฌ: OCR ์ฅ์ ์์๋ API ์ ์ ์ด์ ๊ฐ๋ฅ
- ๋ ๋ฆฝ ํ์ฅ: Worker๋ณ ๋ ๋ฆฝ์ ์ค์ผ์ผ ์์
- ์์ ํ ๊ด์ธก์ฑ: Prometheus + Grafana + Loki + Jaeger ํตํฉ ๋ชจ๋ํฐ๋ง
- IoT ์ต์ ํ: MQTT QoS 1๋ก ๋ฉ์์ง ์ ๋ฌ ๋ณด์ฅ
- FCM ํ ํฐ ์ ๋ฐ์ดํธ API ๋ฒ๊ทธ ์์
-
CONN_MAX_AGE์ค์ ์ผ๋ก DB ์ปค๋ฅ์ ํ๋ง ํ์ฑํ - speedcam-ocr ๋์คํฌ ์ ๋ฆฌ
- OCR Worker GPU ์ธ์คํด์ค ์ ํ (5s โ <1s ๋ชฉํ)
- ๋ชจ๋ํฐ๋ง ์ธ์คํด์ค e2-medium ์ ๊ทธ๋ ์ด๋
- MQTT Subscriber ๋ฉํฐ์ค๋ ๋ฉ ๊ตฌํ
- Redis ์บ์ฑ ๋ ์ด์ด ์ถ๊ฐ
- MySQL ์ฝ๊ธฐ ๋ณต์ ๋ณธ ๊ตฌ์ฑ
- Celery Beat ์ค์ผ์ค๋ฌ ์ถ๊ฐ
- ์ด๋ฏธ์ง ์ ์ฒ๋ฆฌ ํ์ดํ๋ผ์ธ ๊ตฌ์ถ
SpeedCam ํ๋ก์ ํธ๋ Event Driven Architecture๋ฅผ ํตํด ๊ธฐ์กด ๋ชจ๋๋ฆฌ์ ๊ตฌ์กฐ์ ๊ทผ๋ณธ์ ํ๊ณ๋ฅผ ๊ทน๋ณตํ๊ณ , ์ค์๊ฐ IoT ์์คํ ์ผ๋ก์ ์๊ตฌ๋๋ ๋์ ์๋ต์ฑ, ๋ฉ์์ง ๋ณด์ฅ, ์ฅ์ ๊ฒฉ๋ฆฌ๋ฅผ ๋ชจ๋ ๋ฌ์ฑํ์ต๋๋ค.
ํนํ ์ด๋ฒคํธ ์ฒ๋ฆฌ ์๊ฐ 200๋ฐฐ ๊ฐ์ (3,000ms+ โ 15ms), ์์ ํ ๋น๋๊ธฐ ์ฒ๋ฆฌ, ์ปดํฌ๋ํธ๋ณ ๋ ๋ฆฝ ํ์ฅ์ด๋ผ๋ ํต์ฌ ๋ชฉํ๋ฅผ ์ฑ๊ณต์ ์ผ๋ก ๊ตฌํํ์ฌ, ํ๋ก๋์ ํ๊ฒฝ์์ ์์ ์ ์ผ๋ก ์ด์ ๊ฐ๋ฅํ ์์คํ ์ผ๋ก ๋ฐ์ ํ์ต๋๋ค.
์์ผ๋ก OCR Worker GPU ์ ํ๊ณผ DB ์ปค๋ฅ์ ํ๋ง ์ต์ ํ๋ฅผ ํตํด ๋์ฑ ๋น ๋ฅด๊ณ ํจ์จ์ ์ธ ์์คํ ์ผ๋ก ๋ฐ์ ํ ๊ฒ์ผ๋ก ๊ธฐ๋๋ฉ๋๋ค.
๋ฌธ์ ๋ฒ์ : 2.0 ์ต์ข ์์ ์ผ: 2026-02-12 ํ ์คํธ ์ผ์: 2026-02-12 (k6 HTTP 4์๋๋ฆฌ์ค + MQTT 3์๋๋ฆฌ์ค) ์์ฑ์: SpeedCam Backend Team ๊ด๋ จ ๋ฌธ์: ARCHITECTURE_COMPARISON.md