Version: v1.1.1 | Changelog
Production-level AI Agent - Tool Calling ๊ธฐ๋ฐ ์ฅ์ / ๋ ์จ / ์ฌํ์ฉ ๊ฐ๊ฒฉ ๊ฒ์, ์น ๊ฒ์, ์ด๋ฏธ์ง ์์ฑ, ํ๊ธฐ๋ฌผ ์ด๋ฏธ์ง ๋ถ๋ฆฌ๋ฐฐ์ถ ์ธ์ ๋ฐ ๋ถ๋ฅ, ์ฑ๋ด ๊ธฐ๋ฅ ์ ๊ณต
- Self-managed Kubernetes 24-Nodes ํด๋ฌ์คํฐ์์ Istio Service Mesh(mTLS, Auth Offloading)์ ArgoCD GitOps๋ก ์ด์ํฉ๋๋ค.
- Scan API (LLM x2): VU 1000 ๋ถํ ํ ์คํธ ์๋ฃ โ 97.8% ์ฑ๊ณต๋ฅ , 373.4 req/m throughput, E2E P95 173.3s
- Redis Streams + Pub/Sub + State KV ๊ธฐ๋ฐ Event Bus Layer๋ก ์ค์๊ฐ SSE ์ด๋ฒคํธ๋ฅผ ์ฒ๋ฆฌํ๊ณ , KEDA๋ก ์ด๋ฒคํธ ๋๋ฆฌ๋ธ ์คํ ์ค์ผ์ผ๋ง์ ์ํํฉ๋๋ค.
- RabbitMQ + TaskIQ/Celery ๋น๋๊ธฐ Task Queue๋ก AI ํ์ดํ๋ผ์ธ์ ์ฒ๋ฆฌํ๊ณ , EFK + Jaeger + LangSmith๋ก ๋ก๊น ยทํธ๋ ์ด์ฑ์ ์์งํฉ๋๋ค.
- 8๊ฐ ๋๋ฉ์ธ ๋ง์ดํฌ๋ก์๋น์ค(auth, users, scan, chat, character, location, info, images)๋ฅผ ๋ชจ๋ ธ๋ ํฌ๋ก ๊ด๋ฆฌํฉ๋๋ค.
- Service: https://frontend.dev.growbin.app
Edge Layer : Route 53, AWS ALB, Istio Ingress Gateway
Service Layer : auth, users, scan, character, location, chat, info, images (w/ Envoy Sidecar)
Integration Layer :
- Event Bus : Redis Streams + Pub/Sub + State KV, Event Router, SSE Gateway
- Worker (Storage) : auth-worker, auth-Bus, users-worker, character-worker, character-match-worker, info-worker
- Worker (AI) : scan-worker (VisionโRuleโAnswerโReward), chat-worker (LangGraph Multi-Agent)
Persistence Layer : PostgreSQL, Redis (Blacklist/State/Streams/Pub-Sub/Cache)
Platform Layer : ArgoCD, Istiod, KEDA, Prometheus, Grafana, Kiali, Jaeger, LangSmith, EFK Stack๋ณธ ์๋น์ค๋ 5-Layer Architecture๋ก ๊ตฌ์ฑ๋์์ต๋๋ค.
- Edge Layer: AWS ALB๊ฐ SSL Termination์ ์ฒ๋ฆฌํ๊ณ , ํธ๋ํฝ์
Istio Ingress Gateway๋ก ์ ๋ฌํฉ๋๋ค. Gateway๋VirtualService๊ท์น์ ๋ฐ๋ผ North-South ํธ๋ํฝ์ ๋ผ์ฐํ ํฉ๋๋ค. - Service Layer: ๋ชจ๋ ๋ง์ดํฌ๋ก์๋น์ค๋ Istio Service Mesh ๋ด์์ ๋์ํ๋ฉฐ,
Envoy Sidecar๋ฅผ ํตํด mTLS ํต์ , ํธ๋ํฝ ์ ์ด, ๋ฉํธ๋ฆญ ์์ง์ ์ํํฉ๋๋ค.authโusersgRPC ํต์ ,chatโimagesgRPC ํต์ ์ผ๋ก ๋๋ฉ์ธ ๊ฐ ๋๊ธฐ ํธ์ถ์ ์ฒ๋ฆฌํฉ๋๋ค. - Integration Layer - Event Bus: Redis Streams(๋ด๊ตฌ์ฑ) + Pub/Sub(์ค์๊ฐ) + State KV(๋ณต๊ตฌ) 3-tier ์ด๋ฒคํธ ์ํคํ ์ฒ๋ก SSE ํ์ดํ๋ผ์ธ์ ์ฒ๋ฆฌํฉ๋๋ค. RabbitMQ + TaskIQ/Celery ๋น๋๊ธฐ Task Queue๋ก AI ํ์ดํ๋ผ์ธ์ ์ฒ๋ฆฌํ๊ณ , KEDA๊ฐ ์ด๋ฒคํธ ๋๋ฆฌ๋ธ ์คํ ์ค์ผ์ผ๋ง์ ์ํํฉ๋๋ค.
- Integration Layer - Worker: Storage Worker(
worker-storage๋ ธ๋)๋ Persistence Layer์ ์ ๊ทผํ์ฌ ๋ฐ์ดํฐ๋ฅผ ๋๊ธฐํํฉ๋๋ค.auth-worker๋ RabbitMQ์์ ๋ธ๋๋ฆฌ์คํธ ์ด๋ฒคํธ๋ฅผ ์๋นํด Redis์ ์ ์ฅํ๊ณ ,auth-Bus๋ Redis Outbox ํจํด์ผ๋ก ์คํจ ์ด๋ฒคํธ๋ฅผ ์ฌ๋ฐํํฉ๋๋ค.users-worker๋ Celery Batch๋ก ์บ๋ฆญํฐ ์์ ๊ถ์ PostgreSQL์ UPSERTํฉ๋๋ค.info-worker๋ Celery Beat๋ก ํ๊ฒฝ ๋ด์ค๋ฅผ ์ฃผ๊ธฐ์ ์ผ๋ก ์์งํฉ๋๋ค. AI Worker(worker-ai๋ ธ๋)๋ OpenAI/Google API์ ํต์ ํ๋ฉฐ,scan-worker๊ฐ VisionโRuleโAnswerโReward ์ฒด์ธ์ gevent pool๋ก ์ฒ๋ฆฌํ๊ณ ,chat-worker๊ฐ LangGraph Multi-Agent๋ฅผ ์คํํฉ๋๋ค. - Persistence Layer: ์๋น์ค๋ ์์์ฑ์ ์ํด PostgreSQL, Redis๋ฅผ ์ฌ์ฉํฉ๋๋ค. Redis๋ ์ฉ๋๋ณ๋ก ๋ถ๋ฆฌ(Blacklist/OAuth State/Streams/Pub-Sub/Cache)๋๋ฉฐ, Helm Chart๋ก ๊ด๋ฆฌ๋๋ ๋ ๋ฆฝ์ ์ธ ๋ฐ์ดํฐ ์ธํ๋ผ์ ๋๋ค.
- Platform Layer:
Istiod๊ฐ Service Mesh๋ฅผ ์ ์ดํ๊ณ ,ArgoCD๊ฐ GitOps ๋๊ธฐํ๋ฅผ ๋ด๋นํฉ๋๋ค.KEDA๊ฐ ์ด๋ฒคํธ ๋๋ฆฌ๋ธ ์คํ ์ค์ผ์ผ๋ง์ ์ํํ๊ณ , Observability ์คํ(Prometheus/Grafana/Kiali,Jaeger,LangSmith,EFK Stack)์ด ๋ฉํธ๋ฆญยทํธ๋ ์ด์ฑยท๋ก๊น ์ ํตํฉ ๊ด๋ฆฌํฉ๋๋ค.
๊ฐ ๊ณ์ธต์ ์๋ก ๋ ๋ฆฝ์ ์ผ๋ก ๊ธฐ๋ฅํ๋๋ก ์ค๊ณ๋์์ผ๋ฉฐ, Platform Layer๊ฐ ์ ๊ณ์ธต์ ์ ์ด ๋ฐ ๊ด์ธกํฉ๋๋ค. ํ๋ก๋์ ํ๊ฒฝ์ ์ ์ ๋ก ํ Self-manged Kubernetes ๊ธฐ๋ฐ ํด๋ฌ์คํฐ๋ก ์ปจํ ์ด๋ํ๋ ์ดํ๋ฆฌ์ผ์ด์ ์ ์ค์ผ์คํธ๋ ์ด์ ์ ์ง์ํฉ๋๋ค. Istio Service Mesh๋ฅผ ๋์ ํ์ฌ mTLS ๋ณด์ ํต์ , ํธ๋ํฝ ์ ์ด(VirtualService), ์ธ์ฆ ์์(Auth Offloading)์ ๊ตฌํํ์ต๋๋ค. ํด๋ฌ์คํฐ์ ์์ ์ฑ๊ณผ ์ฑ๋ฅ์ ๋ณด์ฅํ๊ธฐ ์ํด ๋ชจ๋ํฐ๋ง ์์คํ ์ ๋์ , IaC(Infrastructure as Code) ๋ฐ GitOps ํ์ดํ๋ผ์ธ์ ๊ตฌ์ถํด ๋ชจ๋ ธ๋ ํฌ ๊ธฐ๋ฐ ์ฝ๋๋ฒ ์ด์ค๊ฐ SSOT(Single Source Of Truth)๋ก ๊ธฐ๋ฅํ๋๋ก ์ ์๋์์ต๋๋ค.
Status: Production Ready โ VU 1000 ๋ถํ ํ ์คํธ ์๋ฃ, 97.8% ์ฑ๊ณต๋ฅ
flowchart LR
subgraph Client["๐ค Client"]
CL["Browser/App"]
end
subgraph API["๐ Scan API"]
SA["POST /api/v1/scan<br/>Dispatch Chain"]
end
subgraph MQ["๐ฌ RabbitMQ"]
VQ[("scan.vision")]
RQ[("scan.rule")]
AQ[("scan.answer")]
WQ[("scan.reward")]
end
subgraph Workers["๐ง Celery Workers (gevent)"]
VW["Vision Worker<br/>GPT-5.2 Vision"]
RW["Rule Worker<br/>Lite RAG"]
AW["Answer Worker<br/>GPT-5.2-mini"]
WW["Reward Worker<br/>๋ณด์ ํ์ "]
end
subgraph Streams["๐ Redis Streams"]
RS[("scan:events:{shard}<br/>(4 shards)")]
end
subgraph EventBus["๐ Event Bus"]
ER["Event Router<br/>(XREADGROUP)"]
PS[("Pub/Sub<br/>sse:events:{hash}")]
end
subgraph SSE["๐ SSE Gateway"]
SG["Pub/Sub Subscriber"]
end
CL -->|"POST /scan"| SA
SA -->|"Dispatch"| VQ
SA -.->|"202 Accepted + job_id"| CL
VQ --> VW
VW -->|"Chain"| RQ
RQ --> RW
RW -->|"Chain"| AQ
AQ --> AW
AW -->|"Chain"| WQ
WQ --> WW
VW & RW & AW & WW -->|"XADD stage"| RS
RS -->|"XREADGROUP"| ER
ER -->|"PUBLISH"| PS
PS -->|"SUBSCRIBE"| SG
SG -->|"SSE data:"| CL
classDef client fill:#e1bee7,stroke:#7b1fa2,stroke-width:2px,color:#000
classDef api fill:#b2dfdb,stroke:#00796b,stroke-width:2px,color:#000
classDef mq fill:#bbdefb,stroke:#1976d2,stroke-width:2px,color:#000
classDef worker fill:#fff9c4,stroke:#f9a825,stroke-width:2px,color:#000
classDef streams fill:#ffccbc,stroke:#e64a19,stroke-width:2px,color:#000
classDef eventbus fill:#b3e5fc,stroke:#0288d1,stroke-width:2px,color:#000
classDef sse fill:#c8e6c9,stroke:#388e3c,stroke-width:2px,color:#000
class CL client
class SA api
class VQ,RQ,AQ,WQ mq
class VW,RW,AW,WW worker
class RS streams
class ER,PS eventbus
class SG sse
| Stage | Worker | ์ค๋ช | LLM |
|---|---|---|---|
| Vision | scan-worker | ํ๊ธฐ๋ฌผ ์ด๋ฏธ์ง ๋ถ๋ฅ (item_class_list.yaml 86๊ฐ ํ๋ชฉ) |
GPT-5.2 Vision |
| Rule | scan-worker | Lite RAG ๊ท์ ๊ฒ์ (situation_tags.yaml 80๊ฐ ์ํฉ) |
- |
| Answer | scan-worker | ๋ถ๋ฆฌ๋ฐฐ์ถ ๊ฐ์ด๋ ์์ฑ (18๊ฐ JSON ๊ท์ ) | GPT-5.2-mini |
| Reward | scan-worker | ์บ๋ฆญํฐ ๋ณด์ ํ์ + PostgreSQL Batch Insert | - |
| ์ปดํฌ๋ํธ | ์ญํ | ์ค์ผ์ผ๋ง |
|---|---|---|
| Redis Streams | ์ด๋ฒคํธ ๋ก๊ทธ (๋ด๊ตฌ์ฑ), 4 shards ๋ถ์ฐ | - |
| Event Router | Streams โ Pub/Sub Fan-out, Consumer Group | KEDA (Pending) |
| Redis Pub/Sub | ์ค์๊ฐ Fan-out, job_id ํด์ ๊ธฐ๋ฐ ์ฑ๋ ์ค๋ฉ (4 channels) |
์ ์ฉ ์ธ์คํด์ค |
| SSE Gateway | Pub/Sub โ Client, State ๋ณต๊ตฌ | KEDA (์ฐ๊ฒฐ ์) |
OpenAI Tier 4, KEDA minReplicas=2 / maxReplicas=5
| VU | ์์ฒญ ์ | ์ฑ๊ณต๋ฅ | Throughput | E2E P95 | ์คํจ | ์ํ |
|---|---|---|---|---|---|---|
| 500 | 1,408 | 99.7% | 351.9 req/m | 108.3s | 4 | โ ์์ |
| 600 | 1,408 | 99.7% | 351.9 req/m | 108.3s | 4 | โ ์์ |
| 700 | 1,496 | 99.2% | 329.1 req/m | 122.3s | 11 | โ ์์ |
| 800 | 1,386 | 99.7% | 367.3 req/m | 144.6s | 4 | โ ์์ |
| 900 | 1,540 | 99.7% | 405.5 req/m | 149.6s | 4 | โญ ๊ถ์ฅ ํ๊ณ |
| 1000 | 1,518 | 97.8% | 373.4 req/m | 173.3s | 33 |
๋ณ๋ชฉ ๋ถ์:
- Celery Probe I/O-bound (LLM API ๋๊ธฐ) ์ทจ์ฝ์ ์๋ณ
- OpenAI Tier 4 TPM 61% ์ฌ์ฉ (์ฌ์ )
- Redis Pub/Sub ์ฑ๋ ์ค๋ฉ์ผ๋ก Hot Key ๋ถ์ฐ
Status: Production Ready (OpenAI Agents SDK + Responses API Fallback)
app.get_graph().draw_mermaid()(์ฐธ๊ณ )
Pipeline Flow:
Intent Classification โ Dynamic Router (Send API) โ 10์ข ์๋ธ์์ด์ ํธ ๋ณ๋ ฌ ์คํ โ Aggregator (๊ฒฐ๊ณผ ์์งยทํ์ ์ปจํ ์คํธ ๊ฒ์ฆ) โ Dynamic Summarization (ํ ํฐ ์๊ณ๊ฐ ์ด๊ณผ ์ OpenCode ์คํ์ผ ์์ถ) โ Answer Node (ํ ํฐ ์คํธ๋ฆฌ๋ฐ)
Dynamic Routing (Send API)๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐํ์์ ๋ณต์ ๋ ธ๋๋ฅผ ๋ณ๋ ฌ ์คํํฉ๋๋ค.
- Multi-Intent Fanout:
additional_intentsโ ๊ฐ๊ฐ ๋ณ๋ ฌ Send - Intent ๊ธฐ๋ฐ Enrichment:
wasteโweather์๋ ์ถ๊ฐ (๋ถ๋ฆฌ๋ฐฐ์ถ + ๋ ์จ ํ) - Conditional Enrichment:
user_location์์ผ๋ฉดweather์๋ ์ถ๊ฐ - Context Compression: ํ ํฐ ์๊ณ๊ฐ ์ด๊ณผ ์
summarize๋ ธ๋์์ ์ด์ ๋ํ ์์ฝ
%%{init: {'flowchart': {'curve': 'linear'}}}%%
graph TD;
__start__([<p>__start__</p>]):::first
intent(intent)
vision(vision)
router{router}
waste_rag(waste_rag)
character(character)
location(location)
weather(weather)
collection_point(collection_point)
bulk_waste(bulk_waste)
recyclable_price(recyclable_price)
image_generation(image_generation)
web_search(web_search)
general(general)
aggregator(aggregator)
summarize(summarize)
answer(answer)
__end__([<p>__end__</p>]):::last
__start__ --> intent;
intent -->|image_url exists| vision;
intent -->|no image| router;
vision --> router;
router -->|WASTE| waste_rag;
router -->|CHARACTER| character;
router -->|LOCATION| location;
router -->|WEATHER| weather;
router -->|COLLECTION_POINT| collection_point;
router -->|BULK_WASTE| bulk_waste;
router -->|RECYCLABLE_PRICE| recyclable_price;
router -->|IMAGE_GENERATION| image_generation;
router -->|WEB_SEARCH| web_search;
router -->|GENERAL| general;
waste_rag --> aggregator;
character --> aggregator;
location --> aggregator;
weather --> aggregator;
collection_point --> aggregator;
bulk_waste --> aggregator;
recyclable_price --> aggregator;
image_generation --> aggregator;
web_search --> aggregator;
general --> aggregator;
aggregator -->|tokens > threshold| summarize;
aggregator -->|tokens <= threshold| answer;
summarize --> answer;
answer --> __end__;
classDef first fill:#b2dfdb,stroke:#00796b,stroke-width:2px
classDef last fill:#ffccbc,stroke:#e64a19,stroke-width:2px
ํ ํฐ ์คํธ๋ฆฌ๋ฐ์ ์ํ Redis Streams + Pub/Sub ์ด์ค ๊ตฌ์กฐ์ ๋๋ค.
flowchart LR
subgraph Worker["๐ค Chat Worker"]
AN["Answer Node<br/>(Token Generator)"]
end
subgraph Streams["๐ Redis Streams"]
RS[("chat:events:{shard}<br/>(XADD)")]
end
subgraph Router["๐ Event Router"]
ER["Consumer Group<br/>(XREADGROUP)"]
RC["Pending Reclaimer<br/>(XCLAIM)"]
end
subgraph State["๐พ State KV"]
SK[("chat:state:{job_id}<br/>(SETEX 30s)")]
end
subgraph PubSub["๐ก Redis Pub/Sub"]
PS[("sse:events:{job_id}<br/>(PUBLISH)")]
end
subgraph Gateway["๐ SSE Gateway"]
SG["Pub/Sub Subscriber<br/>(SUBSCRIBE)"]
end
subgraph Client["๐ค Client"]
CL["Browser/App<br/>(EventSource)"]
end
AN -->|"XADD token"| RS
RS -->|"XREADGROUP"| ER
RS -.->|"XCLAIM (5min idle)"| RC
RC -.->|"reprocess"| ER
ER -->|"SETEX state"| SK
ER -->|"PUBLISH + XACK"| PS
SK -.->|"GET (reconnect)"| SG
PS -->|"SUBSCRIBE"| SG
SG -->|"SSE data:"| CL
classDef worker fill:#fff9c4,stroke:#f9a825,stroke-width:2px,color:#000
classDef streams fill:#ffccbc,stroke:#e64a19,stroke-width:2px,color:#000
classDef router fill:#b3e5fc,stroke:#0288d1,stroke-width:2px,color:#000
classDef state fill:#d1c4e9,stroke:#512da8,stroke-width:2px,color:#000
classDef pubsub fill:#c8e6c9,stroke:#388e3c,stroke-width:2px,color:#000
classDef gateway fill:#b2dfdb,stroke:#00796b,stroke-width:2px,color:#000
classDef client fill:#e1bee7,stroke:#7b1fa2,stroke-width:2px,color:#000
class AN worker
class RS streams
class ER,RC router
class SK state
class PS pubsub
class SG gateway
class CL client
| ์ปดํฌ๋ํธ | ์ญํ | ์ค์ผ์ผ๋ง |
|---|---|---|
| Event Router | Streams โ Pub/Sub Fan-out, State ๊ฐฑ์ , ๋ฉฑ๋ฑ์ฑ ๋ณด์ฅ | KEDA (Pending ๋ฉ์์ง) |
| SSE Gateway | Pub/Sub โ Client, State ๋ณต๊ตฌ, Streams Catch-up | KEDA (์ฐ๊ฒฐ ์) |
| Redis Streams | ์ด๋ฒคํธ ๋ก๊ทธ (๋ด๊ตฌ์ฑ), Consumer Group ์ง์ | ์ค๋ฉ (4 shards) |
| Redis Pub/Sub | ์ค์๊ฐ Fan-out, job_id ํด์ ๊ธฐ๋ฐ ์ฑ๋ ์ค๋ฉ (4 channels) |
์ ์ฉ ์ธ์คํด์ค |
| State KV | ์ต์ ์ํ ์ค๋ ์ท, ์ฌ์ ์ ๋ณต๊ตฌ | Streams Redis ๊ณต์ |
| Intent | ์ค๋ช | Agent | External API / Tool |
|---|---|---|---|
WASTE |
ํ๊ธฐ๋ฌผ ๋ถ๋ฅ/๋ถ๋ฆฌ๋ฐฐ์ถ ์ง๋ฌธ | Waste RAG Agent | Tag-Based Contextual Retrieval |
CHARACTER |
์บ๋ฆญํฐ ๊ด๋ จ ์ง๋ฌธ | Character Agent | gRPC (Character API) |
WEATHER |
๋ ์จ ์ ๋ณด ์์ฒญ | Weather Agent | ๊ธฐ์์ฒญ API (Tool Calling) |
LOCATION |
์์น/์๊ฑฐํจ ๊ฒ์ | Location Agent | Kakao Local API (Tool Calling) |
BULK_WASTE |
๋ํํ๊ธฐ๋ฌผ ๋ฐฐ์ถ ์ง๋ฌธ | Bulk Waste Agent | ์์น๊ตฌ API (Tool Calling) |
RECYCLABLE_PRICE |
์ฌํ์ฉํ ์์ธ ์กฐํ | Recyclable Price Agent | ์์ธ API (Tool Calling) |
COLLECTION_POINT |
์๊ฑฐํจ/์ฌํ์ฉ์ผํฐ ๊ฒ์ | Collection Point Agent | Kakao Local API (Tool Calling) |
IMAGE_GENERATION |
์ด๋ฏธ์ง ์์ฑ ์์ฒญ | Image Generation Agent | Gemini 3 Pro Image + gRPC (Images API) |
WEB_SEARCH |
์น ๊ฒ์ ์์ฒญ | Web Search Agent | OpenAI web_search / Gemini Google Search (Native) |
GENERAL |
์ผ๋ฐ ์ง๋ฌธ | General Agent | - |
| ํญ๋ชฉ | ์ค๋ช |
|---|---|
| LangGraph Multi-Agent | apps/chat_worker/infrastructure/orchestration/langgraph/nodes/์ 10๊ฐ Intent๋ณ Agent ๊ตฌํ. Intent Classification โ Dynamic Router (Send API) โ Aggregator โ Answer Node ํ์ดํ๋ผ์ธ. |
| Intent Classification | LangGraph Intent Node์์ with_structured_output ๊ธฐ๋ฐ 10๊ฐ Intent ๋ถ๋ฅ. Multi-Intent Fanout์ผ๋ก ๋ณต์ Intent ๋ณ๋ ฌ ์ฒ๋ฆฌ. |
| Tool Calling Agents | LLM Native Tool Calling ๊ธฐ๋ฐ 6๊ฐ ๋ ธ๋ โ weather, bulk_waste, recyclable_price, location, collection_point (Kakao Local API), web_search (OpenAI/Gemini Native). |
| Aggregator | ๋ณ๋ ฌ ์คํ๋ ์๋ธ์์ด์ ํธ ๊ฒฐ๊ณผ ์์ง + ํ์ ์ปจํ ์คํธ(weather, location) ๊ฒ์ฆ. Fail-Open Policy๋ก ๋ณด์กฐ ์ ๋ณด ๋๋ฝ ์์๋ ์งํ. |
| Dynamic Summarization | ํ ํฐ ์๊ณ๊ฐ(4K) ์ด๊ณผ ์ OpenCode ์คํ์ผ ์ด์ ๋ํ ์์ฝ. ํต์ฌ ์ ๋ณด ๋ณด์กด + ์ปจํ ์คํธ ์์ถ. |
| ์ด๋ฏธ์ง ์์ฑ | Gemini 3 Pro Image๋ก ์ด๋ฏธ์ง ์์ฑ, gRPC๋ก Images API์ ์ ๋ก๋ ํ CDN URL ๋ฐํ. Character Reference ์ง์. |
| Token Streaming | LangChain LLM ์ง์ ํธ์ถ๋ก ํ ํฐ ๋จ์ ์คํธ๋ฆฌ๋ฐ. Event Router โ Pub/Sub โ SSE Gateway ์ค์๊ฐ ์ ๋ฌ. |
| Checkpoint | Redis Primary + PostgreSQL Async Sync ์ํคํ ์ฒ. Worker๋ Redis์ ์ง์ ์ฐ๊ณ , checkpoint_syncer๊ฐ ๋น๋๊ธฐ๋ก PG์ ์์นด์ด๋ธ. |
| ๋ฉ์์ง ์์ํ | chat-persistence-consumer๊ฐ Redis Streams โ PostgreSQL๋ก ๋ํ ๊ธฐ๋ก ์ ์ฅ. |
| API ๊ตฌ์กฐ | apps/chat/ โ FastAPI + apps/chat_worker/ LangGraph Agent. /api/v1/chat ์๋ํฌ์ธํธ๋ RabbitMQ๋ก TaskIQ Job ๋ฐํ. |
| ํธ๋ ์ด์ฑ | LangSmith ์ฐ๋์ผ๋ก LangGraph ์คํ ํธ๋ ์ด์ค ์์ง. OpenTelemetry E2E ๋ถ์ฐ ํธ๋ ์ด์ฑ. |
- Multi-Intent Fanout: Send API๋ก ๋ณต์ Intent ๋ณ๋ ฌ ์คํ + Enrichment ์๋ ์ถ๊ฐ (waste โ weather)
- Tool Calling ๊ธฐ๋ฐ API ํตํฉ: LLM Native (OpenAI web_search, Gemini Google Search) + Kakao Local API (HTTP)
- gRPC ๋ด๋ถ ํต์ : Character API, Images API (Location gRPC deprecated โ Kakao HTTP ๋์ฒด)
- Token Streaming: LangChain LLM ์ง์ ํธ์ถ๋ก ์ค์๊ฐ ํ ํฐ ์ ๋ฌ
- ์ด๋ฏธ์ง ์์ฑ: Gemini ๊ธฐ๋ฐ ์์ฑ + gRPC CDN ์ ๋ก๋
- Character Reference: ์บ๋ฆญํฐ ์ด๋ฆ ๊ฐ์ง ๋ฐ ์ปจํ ์คํธ ์ ๋ฌ
- Redis Primary Checkpoint: Redis ์ง์ ์ฐ๊ธฐ + PostgreSQL Async Sync
Worker โ PostgreSQL ์ง์ ์ฐ๊ฒฐ๋ก ์ธํ Connection Pool ๊ณ ๊ฐ ๋ฌธ์ ํด๊ฒฐ
flowchart LR
subgraph Worker["๐ค Chat Worker"]
LG["LangGraph<br/>Parallel Nodes"]
RC["RedisCheckpointer<br/>(~1ms write)"]
end
subgraph Redis["๐ Redis"]
RK[("checkpoint:{thread_id}<br/>TTL 24h")]
end
subgraph Syncer["๐ Checkpoint Syncer"]
CS["Batch Processor<br/>(5s interval, 50/batch)"]
end
subgraph PostgreSQL["๐พ PostgreSQL"]
PG[("checkpoints table<br/>(Archive)")]
end
LG -->|"put()"| RC
RC -->|"SET + queue"| RK
RK -->|"poll"| CS
CS -->|"UPSERT batch"| PG
PG -.->|"cold start read"| RC
classDef worker fill:#fff9c4,stroke:#f9a825,stroke-width:2px,color:#000
classDef redis fill:#ffccbc,stroke:#e64a19,stroke-width:2px,color:#000
classDef syncer fill:#b3e5fc,stroke:#0288d1,stroke-width:2px,color:#000
classDef pg fill:#c8e6c9,stroke:#388e3c,stroke-width:2px,color:#000
class LG,RC worker
class RK redis
class CS syncer
class PG pg
| ์ปดํฌ๋ํธ | ์ญํ | ์ฐ๊ฒฐ ํ |
|---|---|---|
| RedisCheckpointer | Worker์์ ์ง์ Redis ์ฐ๊ธฐ (~1ms) | Redis ์ฐ๊ฒฐ |
| ReadThroughCheckpointer | Redis miss ์ PG ์ฝ๊ธฐ + Redis ์น๊ฒฉ | PG 2 conn (์ฝ๊ธฐ ์ ์ฉ) |
| checkpoint_syncer | Redis โ PG ๋น๋๊ธฐ ๋ฐฐ์น ๋๊ธฐํ | PG 5 conn (๋จ์ผ ํ๋ก์ธ์ค) |
๊ฐ์ ํจ๊ณผ: Worker PG ์ฐ๊ฒฐ 192 โ 8 (96% ๊ฐ์), KEDA 10 pods ์ค์ผ์ผ๋ง ์์๋ 45 conn ์ ์ง
| ์๋น์ค | ์ค๋ช | ์ด๋ฏธ์ง/ํ๊ทธ |
|---|---|---|
| auth | JWT ์ธ์ฆ/์ธ๊ฐ (RS256) | docker.io/mng990/eco2:auth-{env}-latest |
| users | ์ฌ์ฉ์ ์ ๋ณด ๊ด๋ฆฌ (gRPC) | docker.io/mng990/eco2:users-{env}-latest |
| scan | Lite RAG + GPT-5.2 Vision ํ๊ธฐ๋ฌผ ๋ถ๋ฅ | docker.io/mng990/eco2:scan-{env}-latest |
| chat | LangGraph Multi-Agent ์ฑ๋ด (10 Intents) | docker.io/mng990/eco2:chat-{env}-latest |
| character | ์บ๋ฆญํฐ ์ ๊ณต | docker.io/mng990/eco2:character-{env}-latest |
| location | ์ง๋/์๊ฑฐํจ ๊ฒ์ | docker.io/mng990/eco2:location-{env}-latest |
| info | ํ๊ฒฝ ๋ด์ค ์กฐํ | docker.io/mng990/eco2:info-{env}-latest |
| images | ์ด๋ฏธ์ง ์ ๋ก๋ (gRPC) | docker.io/mng990/eco2:images-{env}-latest |
| Worker | ๋ ธ๋ | ์ค๋ช | Queue | Scaling |
|---|---|---|---|---|
| scan-worker | worker-ai |
AI ํ์ดํ๋ผ์ธ ์ฒ๋ฆฌ (VisionโRuleโAnswerโReward) | scan.vision, scan.rule, scan.answer, scan.reward |
KEDA (RabbitMQ) |
| character-match-worker | worker-storage |
์บ๋ฆญํฐ ๋งค์นญ ์ฒ๋ฆฌ | character.match |
KEDA (RabbitMQ) |
| character-worker | worker-storage |
์บ๋ฆญํฐ ์์ ๊ถ ์ ์ฅ (batch) | character.reward |
KEDA (RabbitMQ) |
| users-worker | worker-storage |
์ ์ ์บ๋ฆญํฐ ์์ ๊ถ PostgreSQL UPSERT | users.character |
KEDA (RabbitMQ) |
| info-worker | worker-storage |
ํ๊ฒฝ ๋ด์ค ์์ง (Celery Beat) | info.collect_news |
๋จ์ผ ์ธ์คํด์ค |
| celery-beat | worker-storage |
DLQ ์ฌ์ฒ๋ฆฌ ์ค์ผ์ค๋ง | - | ๋จ์ผ ์ธ์คํด์ค |
| Worker | ๋ ธ๋ | ์ค๋ช | Exchange / Queue | Scaling |
|---|---|---|---|---|
| chat-worker | worker-ai |
LangGraph Multi-Agent ์คํ (10 Intents, timeout 120s, retry 2) | chat_tasks โ chat.process |
KEDA (RabbitMQ) |
| checkpoint-syncer | worker-storage |
Redis โ PostgreSQL ์ฒดํฌํฌ์ธํธ ๋ฐฐ์น ๋๊ธฐํ (5s interval) | - | ๋จ์ผ ์ธ์คํด์ค |
| chat-persistence-consumer | worker-storage |
Redis Streams โ PostgreSQL ๋ฉ์์ง ์ ์ฅ | - | ๋จ์ผ ์ธ์คํด์ค |
๐ TaskIQ Worker ์์ธ ์ค์
# chat-worker ์ค์
Exchange: chat_tasks (direct)
Queue: chat.process (DLX, TTL ์ค์ )
Workers: 4 (concurrent)
Max Async Tasks: 10
Timeout: 120s
Retry: 2ํ
# ํธ๋ ์ด์ฑ
- aio-pika Instrumentation (MQ ๋ฉ์์ง ์ถ์ )
- OpenAI/Gemini Instrumentation (LLM API ํธ์ถ)
- LangSmith OTEL (LangGraph โ Jaeger ํตํฉ)JWT ํ ํฐ ๋ฌดํจํ๋ฅผ ์ํ Redis-backed Outbox ํจํด. ๋ถ์ฐ ํ๊ฒฝ์์ ๋ธ๋๋ฆฌ์คํธ ์ด๋ฒคํธ์ At-Least-Once ์ ๋ฌ์ ๋ณด์ฅํฉ๋๋ค.
| Worker | ๋ ธ๋ | ์ค๋ช | ์ ๋ ฅ | ์ถ๋ ฅ |
|---|---|---|---|---|
| auth-worker | worker-storage |
๋ธ๋๋ฆฌ์คํธ ์ด๋ฒคํธ ์์ โ Redis KV ์ ์ฅ | RabbitMQ blacklist.events |
Redis blacklist:{jti} |
| auth-Bus | worker-storage |
Redis Outbox ํด๋ง โ RabbitMQ ์ฌ๋ฐํ | Redis outbox:blacklist |
RabbitMQ blacklist.events |
| Component | ์ค๋ช | Scaling |
|---|---|---|
| event-router | Redis Streams โ Pub/Sub Fan-out, State KV ๊ด๋ฆฌ | KEDA (Streams Pending) |
| sse-gateway | Pub/Sub ๊ตฌ๋ โ SSE ํด๋ผ์ด์ธํธ ์ ๋ฌ | KEDA (์ฐ๊ฒฐ ์) |
๊ฐ ๋๋ฉ์ธ์ ๊ณตํต FastAPI ํ ํ๋ฆฟยทDockerfileยทํ ์คํธ๋ฅผ ๊ณต์ ํ๊ณ , Kustomize overlay์์ ์ด๋ฏธ์ง ํ๊ทธ์ ConfigMap/Secret๋ง ๋ถ๊ธฐํฉ๋๋ค.
Status: RabbitMQ + Celery + KEDA ์ด๋ฒคํธ ๋๋ฆฌ๋ธ ์คํ ์ค์ผ์ผ๋ง ์๋ฃ
flowchart LR
subgraph Client["๐ค Client"]
CL["Browser/App"]
end
subgraph API["๐ Scan API"]
SA["POST /api/v1/scan<br/>Dispatch Chain"]
end
subgraph MQ["๐ฌ RabbitMQ"]
VQ[("scan.vision")]
RQ[("scan.rule")]
AQ[("scan.answer")]
WQ[("scan.reward")]
end
subgraph Workers["๐ง Celery Workers (gevent)"]
VW["Vision Worker<br/>GPT Vision ๋ถ์"]
RW["Rule Worker<br/>RAG ๊ท์ ๊ฒ์"]
AW["Answer Worker<br/>GPT ๋ต๋ณ ์์ฑ"]
WW["Reward Worker<br/>๋ณด์ ํ์ "]
end
subgraph External["๐ค OpenAI API"]
OAI["GPT-4o Vision<br/>GPT-4o-mini"]
end
subgraph Streams["๐ Redis Streams"]
RS[("scan:events:*<br/>(Event Bus๋ก ์ ๋ฌ)")]
end
subgraph DB["๐พ PostgreSQL"]
PG[("๊ฒฐ๊ณผ ์ ์ฅ")]
end
subgraph Scale["โก KEDA"]
KD["ํ ๊ธธ์ด ๊ธฐ๋ฐ<br/>์คํ ์ค์ผ์ผ๋ง"]
end
CL -->|POST| SA
SA -->|Dispatch| VQ
SA -.->|202 Accepted| CL
VQ --> VW
VW -->|API Call| OAI
VW -->|XADD| RS
VW -->|Chain| RQ
RQ --> RW
RW -->|XADD| RS
RW -->|Chain| AQ
AQ --> AW
AW -->|API Call| OAI
AW -->|XADD| RS
AW -->|Chain| WQ
WQ --> WW
WW -->|Batch Insert| PG
WW -->|XADD stage=done| RS
KD -.->|Monitor| MQ
KD -.->|Scale| Workers
classDef client fill:#e1bee7,stroke:#7b1fa2,stroke-width:2px,color:#000
classDef api fill:#b2dfdb,stroke:#00796b,stroke-width:2px,color:#000
classDef mq fill:#bbdefb,stroke:#1976d2,stroke-width:2px,color:#000
classDef worker fill:#fff9c4,stroke:#f9a825,stroke-width:2px,color:#000
classDef external fill:#ffcc80,stroke:#e65100,stroke-width:2px,color:#000
classDef streams fill:#ffccbc,stroke:#e64a19,stroke-width:2px,color:#000
classDef db fill:#c8e6c9,stroke:#388e3c,stroke-width:2px,color:#000
classDef scale fill:#b3e5fc,stroke:#0288d1,stroke-width:2px,color:#000
class CL client
class SA api
class VQ,RQ,AQ,WQ mq
class VW,RW,AW,WW worker
class OAI external
class RS streams
class PG db
class KD scale
๐ Sequence Diagram (์์ธ ํ๋ฆ)
sequenceDiagram
participant Client
participant ScanAPI as Scan API
participant RabbitMQ
participant KEDA
participant VisionWorker as Vision Worker
participant RuleWorker as Rule Worker
participant AnswerWorker as Answer Worker
participant RewardWorker as Reward Worker
participant RedisStreams as Redis Streams
participant PostgreSQL
Client->>ScanAPI: POST /api/v1/scan
ScanAPI->>RabbitMQ: Dispatch Chain (job_id)
ScanAPI-->>Client: 202 Accepted {job_id}
KEDA->>RabbitMQ: ํ ๊ธธ์ด ๋ชจ๋ํฐ๋ง
KEDA->>VisionWorker: Scale Up (๋ฉ์์ง ์ฆ๊ฐ ์)
RabbitMQ->>VisionWorker: scan.vision queue
VisionWorker->>VisionWorker: GPT Vision ๋ถ์
VisionWorker->>RedisStreams: XADD stage=vision
VisionWorker->>RabbitMQ: Chain โ scan.rule
RabbitMQ->>RuleWorker: scan.rule queue
RuleWorker->>RuleWorker: RAG ๊ท์ ๊ฒ์
RuleWorker->>RedisStreams: XADD stage=rule
RuleWorker->>RabbitMQ: Chain โ scan.answer
RabbitMQ->>AnswerWorker: scan.answer queue
AnswerWorker->>AnswerWorker: GPT ๋ต๋ณ ์์ฑ
AnswerWorker->>RedisStreams: XADD stage=answer
AnswerWorker->>RabbitMQ: Chain โ reward.character
RabbitMQ->>RewardWorker: reward.character queue
RewardWorker->>PostgreSQL (Batch): ๋ณด์ ์ ์ฅ
RewardWorker->>RedisStreams: XADD stage=done
| ์ปดํฌ๋ํธ | ์ญํ | Queue | ์ค์ผ์ผ๋ง |
|---|---|---|---|
| scan-worker | Vision ๋ถ์, RAG ๊ฒ์, ๋ต๋ณ ์์ฑ, ๋ณด์ ํ์ | scan.vision, scan.rule, scan.answer, scan.reward |
KEDA (ํ ๊ธธ์ด) |
| character-match-worker | ์บ๋ฆญํฐ ๋งค์นญ ์ฒ๋ฆฌ | character.match |
KEDA (ํ ๊ธธ์ด) |
| character-worker | ์บ๋ฆญํฐ ์์ ๊ถ ์ ์ฅ (batch) | character.reward |
KEDA (ํ ๊ธธ์ด) |
| celery-beat | DLQ ์ฌ์ฒ๋ฆฌ ์ค์ผ์ค๋ง (5๋ถ ์ฃผ๊ธฐ) | - | ๋จ์ผ ์ธ์คํด์ค |
| RabbitMQ | AMQP ๋ฉ์์ง ๋ธ๋ก์ปค | vhost: eco2 |
Quorum Queue |
flowchart LR
subgraph Pods["Kubernetes Pods"]
API["API Pods<br/>(auth, scan, chat...)"]
Workers["Celery Workers<br/>(scan, character-match, character)"]
Infra["Infra Pods<br/>(istio, argocd...)"]
end
subgraph FluentBit["Fluent Bit (DaemonSet)"]
Tail["Tail Input<br/>(/var/log/containers/*.log)"]
Parser["Parser<br/>(JSON, regex)"]
Filter["Filter<br/>(kubernetes metadata)"]
Output["Output<br/>(es plugin)"]
end
subgraph EFK["EFK Stack"]
ES[("Elasticsearch<br/>(3 nodes)")]
Kibana["Kibana<br/>(UI)"]
end
API -->|stdout/stderr| Tail
Workers -->|stdout/stderr| Tail
Infra -->|stdout/stderr| Tail
Tail --> Parser
Parser --> Filter
Filter --> Output
Output -->|HTTP/9200| ES
ES --> Kibana
classDef pods fill:#326CE5,stroke:#fff,color:white
classDef fluent fill:#009688,stroke:#fff,color:white
classDef efk fill:#FF9800,stroke:#fff,color:white
class API,Workers,Infra pods
class Tail,Parser,Filter,Output fluent
class ES,Kibana efk
| ์ปดํฌ๋ํธ | ์ญํ | ์ค์ |
|---|---|---|
| Fluent Bit | ๋ก๊ทธ ์์ง ๋ฐ ํฌ์๋ฉ (DaemonSet) | /var/log/containers/*.log ์์ง, JSON ํ์ฑ |
| Elasticsearch | ๋ก๊ทธ ์ ์ฅ ๋ฐ ์ธ๋ฑ์ฑ | 3-node cluster, ์ธ๋ฑ์ค: logstash-YYYY.MM.DD |
| Kibana | ๋ก๊ทธ ๊ฒ์ ๋ฐ ์๊ฐํ | Discover, Dashboard, Alerting |
{
"timestamp": "2025-12-22T10:30:00.000Z",
"level": "INFO",
"logger": "scan.vision_task",
"message": "Vision analysis completed",
"task_id": "550e8400-e29b-41d4-a716-446655440000",
"user_id": "123e4567-e89b-12d3-a456-426614174000",
"duration_ms": 2340,
"kubernetes": {
"namespace": "scan",
"pod_name": "scan-worker-5d8f9b7c4-x2k9p",
"container_name": "scan-worker"
}
}Cluster : kubeadm Self-Managed (25 Nodes)
GitOps :
Layer0 - Terraform (AWS ์ธํ๋ผ)
Layer1 - Ansible (kubeadm, CNI)
Layer2 - ArgoCD App-of-Apps Sync-wave + Kustomize/Helm
Layer3 - GitHub Actions + Docker Hub
Architecture :
Edge Layer - Route 53, AWS ALB, Istio Ingress Gateway
Service Layer - auth, users, scan, character, location, chat, info, images
Integration Layer :
- Event Bus - Redis Streams + Pub/Sub + State KV, Event Router, SSE Gateway
- Worker (Storage) - auth-worker, auth-Bus, users-worker, character-worker, info-worker, chat-persistence-consumer
- Worker (AI) - scan-worker (VisionโRuleโAnswerโReward), chat-worker (LangGraph Multi-Agent)
- KEDA (Event-driven Autoscaling)
Persistence Layer - PostgreSQL, Redis (Blacklist/State/Streams/Pub-Sub/Cache ๋ถ๋ฆฌ)
Platform Layer - ArgoCD, Istiod, KEDA, Observability (Prometheus, Grafana, EFK, Jaeger, LangSmith)
Network : Calico CNI + Istio Service Mesh (mTLS)
Node Isolation :
- worker-storage - Taint: domain=worker-storage:NoSchedule (Persistence ์ ๊ทผ Worker ์ ์ฉ)
- worker-ai - Taint: domain=worker-ai:NoSchedule (AI/OpenAI/Google API ํธ์ถ Worker ์ ์ฉ)- Terraform์ผ๋ก AWS ์ธํ๋ผ๋ฅผ ๊ตฌ์ถํฉ๋๋ค.
- Ansible๋ก ๊ตฌ์ถ๋ AWS ์ธํ๋ผ๋ฅผ ์ฎ์ด K8s ํด๋ฌ์คํฐ๋ฅผ ๊ตฌ์ฑํ๊ณ , ArgoCD root-app์ ์ค์นํฉ๋๋ค.
- ๋ชจ๋ ์ปดํฌ๋ํธ๋ ArgoCD root-app๊ณผ sync๋ ์ํ์ด๋ฉฐ, root-app์ develop ๋ธ๋์น๋ฅผ ๋ฐ๋ผ๋ด ๋๋ค.
- develop ๋ธ๋์น์ push๊ฐ ๋ฐ์ํ๋ฉด CI ํ์ดํ๋ผ์ธ์ ๊ฑฐ์ณ ํ ์คํธ, ๋์ปค ์ด๋ฏธ์ง ํจํค์ง, ํ๋ธ ์ ๋ก๋๊น์ง ์ํํฉ๋๋ค.
- ArgoCD root-app์ develop ๋ธ๋์น์ ๋ณ๊ฒฝ์ฌํญ์ด ๊ฐ์ง๋๋ฉด ํด๋น ํํธ๋ฅผ ์ ๋ฐ์ดํธํด ์ฝ๋ ๋ณ๊ฒฝ์ด ํด๋ฌ์คํฐ๋ก ๋ฐ์๋ฉ๋๋ค.
ArgoCD App-of-Apps ํจํด ๊ธฐ๋ฐ GitOps. ๋ชจ๋ ๋ฆฌ์์ค๋ sync-wave๋ก ์์กด์ฑ ์์ ๋ณด์ฅ.
| Wave | ๋ ์ด์ด | ๋ฆฌ์์ค |
|---|---|---|
| 0-10 | ํ๋ซํผ | CRD, Namespace, RBAC, Istio, NetworkPolicy, Secrets |
| 15-32 | ์ธํ๋ผ | ALB, Monitoring, PostgreSQL, Redis, RabbitMQ |
| 35-50 | ์ ํ๋ฆฌ์ผ์ด์ | KEDA, APIs, Workers, Event Router, Routing |
- App-of-Apps: ๋ฃจํธ ์ฑ โ ApplicationSet ์์ฑ โ
sync-wave๊ฐ์ผ๋ก ๋ฐฐํฌ ์์ ๊ฐ์ - Sync Hook: PostSync Job์ผ๋ก DB ๋ง์ด๊ทธ๋ ์ด์ ์๋ ์คํ
- CI/CD: ์ฝ๋ ๋ณ๊ฒฝ โ GitHub Actions โ Docker Hub โ ArgoCD Auto-Sync
-
Redis Pub/Sub ์ฑ๋ ์ค๋ฉ & ๋ถํ ํ ์คํธ VU 1000 โ (New!)
- job_id ํด์ ๊ธฐ๋ฐ ์ฑ๋ ์ค๋ฉ:
sse:events:{job_id}โsse:events:{hash(job_id) % 4}Hot Key ๋ถ์ฐ - KEDA ScaledObject ์ต์ ํ: minReplicas 1โ2 (Cold Start ๋ฐฉ์ง), maxReplicas 3โ5
- VU 500-1000 ๋ถํ ํ ์คํธ ์๋ฃ: VU 900๊น์ง 99.7% ์ฑ๊ณต๋ฅ , VU 1000์์ 97.8% ๋ฌ์ฑ
- ๋ณ๋ชฉ ๋ถ์: Celery Probe I/O-bound(LLM API) ์ทจ์ฝ์ ์๋ณ, OpenAI Tier 4 TPM 61% ์ฌ์ฉ (์ฌ์ )
- job_id ํด์ ๊ธฐ๋ฐ ์ฑ๋ ์ค๋ฉ:
-
OpenAI Agents SDK Migration โ
- Primary + Fallback ๊ตฌ์กฐ: Agents SDK ์คํจ ์ Responses API๋ก ์๋ ์ ํ
- 6๊ฐ Function Calling ๋ ธ๋: web_search, bulk_waste, weather, recyclable_price, location, collection_point
- Streaming Safety:
_yieldedํ๋๊ทธ๋ก ๋ถ๋ถ ๋ฐ์ดํฐ ์ ์ก ์ fallback ๋ฐฉ์ง - google-genai 1.60.0: system_instruction, FunctionCallingConfigMode enum ์ ์ฉ
-
Redis Primary Checkpoint ์ํคํ ์ฒ โ (New!)
- Connection Pool ๊ณ ๊ฐ ํด๊ฒฐ: Worker โ PG ์ง์ ์ฐ๊ฒฐ ์ ๊ฑฐ, 192 โ 8 conn (96% ๊ฐ์)
- Redis Primary: Worker๊ฐ Redis์ ์ง์ ์ฐ๊ธฐ (~1ms)
- PG Async Sync: checkpoint_syncer๊ฐ 5์ด ๊ฐ๊ฒฉ ๋ฐฐ์น ๋๊ธฐํ
- Cold Start Fallback: ReadThroughCheckpointer๋ก Redis miss ์ PG ์ฝ๊ธฐ + ์น๊ฒฉ
-
Event Bus ์์ ์ฑ ๊ฐ์ โ (New!)
- ACK Policy ์์ : ์ฒ๋ฆฌ ์คํจ ์ XACK ์คํต โ Reclaimer ์ฌ์ฒ๋ฆฌ
- ๋ฉํฐ๋๋ฉ์ธ Reclaimer: scan/chat ๋ณ๋ ฌ XAUTOCLAIM
- Redis ์ธ์คํด์ค ๋ผ์ฐํ ์์ : ProgressNotifier โ get_redis_streams()
-
LangGraph Multi-Agent ์ํคํ ์ฒ โ
- 10๊ฐ Intent ๋ถ๋ฅ: WASTE, CHARACTER, LOCATION, BULK_WASTE, RECYCLABLE_PRICE, COLLECTION_POINT, WEATHER, IMAGE_GENERATION, WEB_SEARCH, GENERAL
- ์ด๋ฏธ์ง ์์ฑ: Gemini 3 Pro Image + gRPC CDN Upload, Character Reference ์ง์
- Token Streaming: LangChain LLM ์ง์ ํธ์ถ, Event Router Unicode ์์
- ๋ฉ์์ง ์์ํ: chat-persistence-consumer (Redis Streams โ PostgreSQL)
- ๋ถ์ฐ ํธ๋ ์ด์ฑ: LangSmith ์ฐ๋, OpenTelemetry E2E ํธ๋ ์ด์ฑ
-
Info ์๋น์ค ํ๋ก๋น์ ๋ โ (New!)
- Info API/Worker: 3-Tier Architecture (FastAPI + Celery Beat + PostgreSQL + Redis)
- NewsData API ์ฐ๋: ํ๊ฒฝ/์๋์ง/AI ๋ด์ค ์๋ ์์ง
- Claude Code Skills: chat-agent-flow ๋ฑ ํ๋ก์ ํธ ํนํ ๊ฐ์ด๋
-
Clean Architecture ๋ง์ด๊ทธ๋ ์ด์ โ (New!)
- ๋๋ ํ ๋ฆฌ ๊ตฌ์กฐ ์ ํ:
domains/โapps/๋ง์ด๊ทธ๋ ์ด์ - RabbitMQ Named Exchange: reward.events Fanout Exchange, Cross-Domain Task Routing
- CI/CD ํ์ดํ๋ผ์ธ ์ ๋น: apps/ ๊ฒฝ๋ก ๊ธฐ๋ฐ ํธ๋ฆฌ๊ฑฐ
- ๋๋ ํ ๋ฆฌ ๊ตฌ์กฐ ์ ํ:
-
Event Bus Layer + AI ํ์ดํ๋ผ์ธ โ
- Redis Streams(๋ด๊ตฌ์ฑ) + Pub/Sub(์ค์๊ฐ) + State KV(๋ณต๊ตฌ) 3-tier ์ด๋ฒคํธ ์ํคํ ์ฒ
- Event Router: Consumer Group(
XREADGROUP)์ผ๋ก Streams ์๋น, Pub/Sub Fan-out, ๋ฉฑ๋ฑ์ฑ ๋ณด์ฅ - SSE Gateway: Pub/Sub ๊ตฌ๋ ๊ธฐ๋ฐ ์ค์๊ฐ ์ ๋ฌ, State ๋ณต๊ตฌ, Streams Catch-up
- Celery Chain(VisionโRuleโAnswerโReward): GPT-5.2 Vision + GPT-5.2-mini ์กฐํฉ
๐ ๋ถํ ํ ์คํธ ๊ฒฐ๊ณผ (Scan Pipeline) - OpenAI Tier 4, KEDA min=2/max=5
| VU | ์์ฒญ ์ | ์ฑ๊ณต๋ฅ | Throughput | E2E P95 | ์คํจ | ์ํ |
|---|---|---|---|---|---|---|
| 500 | 1,408 | 99.7% | 351.9 req/m | 108.3s | 4 | โ ์์ |
| 600 | 1,408 | 99.7% | 351.9 req/m | 108.3s | 4 | โ ์์ |
| 700 | 1,496 | 99.2% | 329.1 req/m | 122.3s | 11 | โ ์์ |
| 800 | 1,386 | 99.7% | 367.3 req/m | 144.6s | 4 | โ ์์ |
| 900 | 1,540 | 99.7% | 405.5 req/m | 149.6s | 4 | โญ ๊ถ์ฅ ํ๊ณ |
| 1000 | 1,518 | 97.8% | 373.4 req/m | 173.3s | 33 |
๊ฐ์ ์ฌํญ (v1.1.1):
- Redis Pub/Sub ์ฑ๋ ์ค๋ฉ (
job_idํด์ ๊ธฐ๋ฐ) โ Hot Key ๋ถ์ฐ - KEDA ScaledObject ์กฐ์ : minReplicas 1โ2, maxReplicas 3โ5
- Cold Start ๋ฐฉ์ง๋ก ์คํจ์จ 37.7% ๊ฐ์ (VU 1000 ๊ธฐ์ค 53๊ฑดโ33๊ฑด)
-
KEDA ์ด๋ฒคํธ ๋๋ฆฌ๋ธ ์คํ ์ค์ผ์ผ๋ง โ
- scan-worker: RabbitMQ ํ ๊ธธ์ด ๊ธฐ๋ฐ ์๋ ์ค์ผ์ผ๋ง (2-5 replicas, Cold Start ๋ฐฉ์ง)
- chat-worker: RabbitMQ chat.process ํ ๊ธฐ๋ฐ ์ค์ผ์ผ๋ง
- event-router: Redis Streams pending ๋ฉ์์ง ๊ธฐ๋ฐ ์ค์ผ์ผ๋ง
- Prometheus Adapter ์ฐ๋์ผ๋ก ์ปค์คํ ๋ฉํธ๋ฆญ ๊ธฐ๋ฐ HPA ๊ตฌํ
-
24-node ํด๋ฌ์คํฐ ํ์ฅ โ
- ์ ๊ท ๋ ธ๋: chat-worker, info, info-worker, chat-persistence-consumer ์ ์ฉ ๋ ธ๋ ์ถ๊ฐ
- Redis ์ธ์คํด์ค ๋ถ๋ฆฌ: Streams(๋ด๊ตฌ์ฑ) / Pub/Sub(์ค์๊ฐ) / Cache(LRU)
- Grafana ๋์๋ณด๋: 24-node ์ ์ฒด ๋ชจ๋ํฐ๋ง ๋์๋ณด๋
๐ ์ด์ฝ์์ฝ(Ecoยฒ) ๋ฐฑ์๋/์ธํ๋ผ ๊ฐ๋ฐ ๋ธ๋ก๊ทธ
์ฃผ์ ๊ธฐ์ ๋ฌธ์:
- VU 1000 ๋ถํ ํ ์คํธ (Tier 4) - KEDA ์ต์ ํ, Celery Probe ๋ถ์
- OpenAI Agents SDK Migration - Primary + Fallback ์ด์ค ๊ตฌ์กฐ
- Redis Primary + PG Async Sync Checkpoint - Connection Pool ๊ณ ๊ฐ ํด๊ฒฐ
- Event Router & SSE Gateway ์์ ์ฑ ๊ฐ์ - ACK Policy, Reclaimer
- Redis Streams Bug Fix - ProgressNotifier ๋ผ์ฐํ ์์
- Optimistic Update & Eventual Consistency - ํ๋ก ํธ์๋ ์ฐ๋
- โ Redis Pub/Sub ์ฑ๋ ์ค๋ฉ: job_id ํด์ ๊ธฐ๋ฐ Hot Key ๋ถ์ฐ (4 shards)
- โ KEDA ScaledObject ์ต์ ํ: minReplicas 2, maxReplicas 5 (Cold Start ๋ฐฉ์ง)
- โ VU 500-1000 ๋ถํ ํ ์คํธ ์๋ฃ: VU 900๊น์ง 99.7% ์ฑ๊ณต๋ฅ
- โ ๋ณ๋ชฉ ๋ถ์ ์๋ฃ: Celery Probe I/O-bound ์ทจ์ฝ์ ์๋ณ
- โ OpenAI Tier 4 ๊ฒ์ฆ: TPM 61% ์ฌ์ฉ, Rate Limit 0๊ฑด
- โ LangGraph Multi-Agent ์ํคํ ์ฒ ์๋ฃ (10๊ฐ Intent ๋ถ๋ฅ)
- โ OpenAI Agents SDK Migration: Primary + Responses API Fallback ์ด์ค ๊ตฌ์กฐ
- โ 6๊ฐ Function Calling ๋ ธ๋: web_search, bulk_waste, weather, recyclable_price, location, collection_point
- โ Redis Primary Checkpoint: Worker PG ์ฐ๊ฒฐ 96% ๊ฐ์ (192 โ 8)
- โ Gemini ์ด๋ฏธ์ง ์์ฑ ํ์ดํ๋ผ์ธ + gRPC CDN Upload
- โ Event Bus ์์ ์ฑ: ACK Policy ์์ , ๋ฉํฐ๋๋ฉ์ธ Reclaimer
- โ 24-node ํด๋ฌ์คํฐ ํ์ฅ: Grafana ๋์๋ณด๋ ์ถ๊ฐ
- โ ๋ถ์ฐ ํธ๋ ์ด์ฑ: LangSmith + OpenTelemetry E2E
- โ Info API/Worker 3-Tier Architecture ์๋ฃ
- โ NewsData API ์ฐ๋ ํ๊ฒฝ ๋ด์ค ์์ง
- โ Claude Code Skills ๋์ (chat-agent-flow ๋ฑ)
- โ Celery Beat ์์ ํ (standalone sidecar)
- โ
domains/โapps/๊ตฌ์กฐ ์ ํ ์๋ฃ - โ RabbitMQ Named Exchange ์ด๋ฒคํธ ๋ผ์ฐํ (reward.events Fanout)
- โ CI/CD ํ์ดํ๋ผ์ธ ์ ๋น (apps/ ๊ฒฝ๋ก ๊ธฐ๋ฐ)
- โ DB/Redis ์ฐ๊ฒฐ ์ ๊ทํ
- โ Redis Streams + Pub/Sub + State KV ๊ธฐ๋ฐ Event Bus Layer ์๋ฃ
- โ Event Router, SSE Gateway ์ปดํฌ๋ํธ ๊ฐ๋ฐ ์๋ฃ
- โ KEDA ์ด๋ฒคํธ ๋๋ฆฌ๋ธ ์คํ ์ค์ผ์ผ๋ง ์ ์ฉ (scan-worker, event-router, character-match-worker)
- โ Celery ๋น๋๊ธฐ AI ํ์ดํ๋ผ์ธ ์๋ฃ (VisionโRuleโAnswerโReward)
- โ EFK ๋ก๊น ํ์ดํ๋ผ์ธ (Fluent Bit โ Elasticsearch โ Kibana)
- โ ๋ถ์ฐ ํธ๋ ์ด์ฑ (Jaeger + OpenTelemetry + Kiali)
- โ Alertmanager ์๋ฆผ ์์คํ (Slack)
- โ Istio Service Mesh Migration ์๋ฃ
- โ gRPC ๋ด๋ถ ํต์ Migration ์๋ฃ
- โ Auth-Offloading ์๋ฃ, ๋๋ฉ์ธ๋ณ ๋ ๋ฆฝ์ฑ ํ๋ณด
- โ ext-authz ์ฑ๋ฅ ํ๋ (Grafana: VU 2500, RPS 1200, p99 200-300ms)
- โ Terraform ยท Ansible bootstrap ยท ArgoCD Sync-wave
- โ GitOps Sync-Wave ์ฌ์ ๋ ฌ (00~70) + upstream Helm/CRD ๋ถ๋ฆฌ
- โ Docker Hub ์ด๋ฏธ์ง ํ์ดํ๋ผ์ธ + External Secrets ์ด์
- โ API ๊ฐ๋ฐ ์๋ฃ, ํ๋ก ํธ-๋ฐฑ-AI ์ฐ๋ ์๋ฃ


