Skip to content

Commit 992e21a

Browse files
committed
docs: 세션 관리 문서 추가
1 parent 5f1f9dc commit 992e21a

File tree

1 file changed

+341
-0
lines changed

1 file changed

+341
-0
lines changed
Lines changed: 341 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,341 @@
1+
# ProjectVG Chat 시스템 및 WebSocket 세션 관리 흐름
2+
3+
## 개요
4+
5+
이 문서는 ProjectVG API 서버의 Chat 시스템과 분산 WebSocket 세션 관리의 전체 흐름을 상세히 설명합니다. HTTP Chat 요청 처리부터 분산 환경에서의 실시간 메시지 전달까지의 전체 과정을 다룹니다.
6+
7+
## 1. Chat 로직 실행 흐름
8+
9+
### 📌 HTTP Chat 요청 흐름
10+
11+
```
12+
Unity Client ──HTTP POST──→ ChatController ──→ ChatService ──→ Background Pipeline
13+
│ /api/v1/chat EnqueueChatRequestAsync
14+
│ │
15+
└──WebSocket 연결──────────────────────────────────┘
16+
17+
┌─────────────▼─────────────┐
18+
│ Chat Processing Pipeline │
19+
│ (Background Task) │
20+
└─────────────┬─────────────┘
21+
22+
┌─────────▼─────────┐
23+
│ 결과를 WebSocket │
24+
│ 으로 실시간 전송 │
25+
└───────────────────┘
26+
```
27+
28+
### 📋 Chat Processing Pipeline 상세
29+
30+
```csharp
31+
// 1. ChatController.ProcessChat (ProjectVG.Api/Controllers/ChatController.cs:22)
32+
[HttpPost]
33+
[JwtAuthentication]
34+
public async Task<IActionResult> ProcessChat([FromBody] ChatRequest request)
35+
{
36+
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
37+
var command = new ChatRequestCommand(userGuid, request.CharacterId, request.Message, request.RequestAt, request.UseTTS);
38+
39+
// 2. 즉시 응답 후 백그라운드 처리
40+
var result = await _chatService.EnqueueChatRequestAsync(command);
41+
return Ok(result);
42+
}
43+
44+
// 3. ChatService Background Pipeline (7단계)
45+
private async Task ProcessChatRequestInternalAsync(ChatProcessContext context)
46+
{
47+
// 1. 검증 → ChatRequestValidator
48+
// 2. 전처리 → UserInputAnalysisProcessor, MemoryContextPreprocessor
49+
// 3. 액션 처리 → UserInputActionProcessor
50+
// 4. LLM 처리 → ChatLLMProcessor (Cost Tracking 데코레이터)
51+
// 5. TTS 처리 → ChatTTSProcessor (Cost Tracking 데코레이터)
52+
// 6. 결과 처리 → ChatResultProcessor
53+
// 7. 성공/실패 → ChatSuccessHandler/ChatFailureHandler
54+
}
55+
```
56+
57+
## 2. WebSocket 연결 과정 분석
58+
59+
### 🔌 WebSocket 연결 흐름
60+
61+
```
62+
Unity Client ──WebSocket(/ws)──→ WebSocketMiddleware ──→ JWT 검증 ──→ 연결 등록
63+
│ InvokeAsync:31 │
64+
│ │
65+
└──Query Parameter: ?token={jwt} 또는 Authorization Header───────┘
66+
67+
┌───────▼───────┐
68+
│ 1. JWT 검증 │
69+
│ 2. 기존 연결 │
70+
│ 정리 │
71+
│ 3. 새 연결 │
72+
│ 등록 │
73+
│ 4. 세션 루프 │
74+
│ 시작 │
75+
└───────────────┘
76+
```
77+
78+
### 📋 WebSocket 연결 과정 상세
79+
80+
```csharp
81+
// 1. WebSocketMiddleware.InvokeAsync (ProjectVG.Api/Middleware/WebSocketMiddleware.cs:31)
82+
public async Task InvokeAsync(HttpContext context)
83+
{
84+
if (context.Request.Path != "/ws") {
85+
await _next(context);
86+
return;
87+
}
88+
89+
// 2. JWT 토큰 검증 (Line 44)
90+
var userId = ValidateAndExtractUserId(context);
91+
if (userId == null) {
92+
context.Response.StatusCode = 401;
93+
return;
94+
}
95+
96+
// 3. WebSocket 연결 수락 (Line 50)
97+
var socket = await context.WebSockets.AcceptWebSocketAsync();
98+
99+
// 4. 연결 등록 (Line 51)
100+
await RegisterConnection(userId.Value, socket);
101+
102+
// 5. 세션 루프 시작 (Line 52)
103+
await RunSessionLoop(socket, userId.Value.ToString());
104+
}
105+
106+
// 6. 연결 등록 과정 (Line 94)
107+
private async Task RegisterConnection(Guid userId, WebSocket socket)
108+
{
109+
// 기존 연결 정리
110+
if (_connectionRegistry.TryGet(userId.ToString(), out var existing) && existing != null) {
111+
await _webSocketService.DisconnectAsync(userId.ToString());
112+
}
113+
114+
// 새 연결 등록
115+
var connection = new WebSocketClientConnection(userId.ToString(), socket);
116+
_connectionRegistry.Register(userId.ToString(), connection); // 로컬 레지스트리
117+
await _webSocketService.ConnectAsync(userId.ToString()); // 분산 세션 관리
118+
}
119+
```
120+
121+
## 3. 세션 저장 및 사용 추적
122+
123+
### 🗃️ 분산 세션 관리 시스템
124+
125+
```
126+
로컬 메모리 Redis (분산 저장소)
127+
┌─────────────────┐ ┌─────────────────────────┐
128+
│ ConnectionRegistry│ │ session:user:{userId} │
129+
│ ConcurrentDict │◄──────────► │ { │
130+
│ [userId] = conn │ │ "ConnectionId": "...", │
131+
│ │ │ "ServerId": "api-001", │
132+
└─────────────────┘ │ "ConnectedAt": "...", │
133+
│ "LastActivity": "..." │
134+
│ } │
135+
│ │
136+
│ user:server:{userId} │
137+
│ = "api-server-001" │
138+
└─────────────────────────┘
139+
```
140+
141+
### 📋 세션 저장 과정
142+
143+
```csharp
144+
// 1. DistributedWebSocketManager.ConnectAsync (ProjectVG.Application/Services/WebSocket/DistributedWebSocketManager.cs:35)
145+
public async Task<string> ConnectAsync(string userId)
146+
{
147+
// 2. Redis에 세션 정보 저장 (Line 39)
148+
await _sessionStorage.CreateAsync(new SessionInfo
149+
{
150+
SessionId = userId,
151+
UserId = userId,
152+
ConnectedAt = DateTime.UtcNow
153+
});
154+
155+
// 3. 분산 브로커 채널 구독 (Line 47)
156+
if (_distributedBroker != null)
157+
{
158+
await _distributedBroker.SubscribeToUserChannelAsync(userId);
159+
}
160+
}
161+
162+
// 4. DistributedMessageBroker.SubscribeToUserChannelAsync (Line 148)
163+
public async Task SubscribeToUserChannelAsync(string userId)
164+
{
165+
// Redis 채널 구독
166+
var userChannel = $"user:{userId}";
167+
await _subscriber.SubscribeAsync(userChannel, OnUserMessageReceived);
168+
169+
// 사용자-서버 매핑 설정
170+
await _serverRegistration.SetUserServerAsync(userId, _serverId);
171+
}
172+
```
173+
174+
### 🔍 세션 사용 시점
175+
176+
1. **연결 시**: ConnectionRegistry (로컬) + Redis 세션 저장
177+
2. **메시지 전송 시**: Redis에서 사용자가 어느 서버에 있는지 조회
178+
3. **연결 해제 시**: 로컬 레지스트리 + Redis 세션 정리
179+
180+
## 4. 분산환경에서의 Chat 도메인 흐름
181+
182+
### 🌐 분산 Chat 메시지 전달 흐름
183+
184+
```
185+
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
186+
│ Unity Client │ │ API Server A │ │ API Server B │
187+
│ │ │ (요청 처리) │ │ (사용자 연결) │
188+
└─────────────────┘ └─────────────────┘ └─────────────────┘
189+
│ │ │
190+
│ 1. HTTP Chat 요청 │ │
191+
├──────────────────────►│ │
192+
│ │ │
193+
│ 2. 즉시 응답 (202) │ │
194+
│◄──────────────────────┤ │
195+
│ │ │
196+
│ │ 3. LLM 서비스 호출 │
197+
│ ├─────────────────────► │
198+
│ │ 외부 서비스
199+
│ │ 4. Chat 응답 받음 │
200+
│ │◄─────────────────────┤
201+
│ │ │
202+
│ │ 5. Redis Pub/Sub │
203+
│ │ user:{userId} │
204+
│ ├──────────────────────►│
205+
│ │ Redis
206+
│ │ │
207+
│ │ │ 6. Redis Sub 수신
208+
│ │ │◄────────────────
209+
│ │ │
210+
│ 7. WebSocket 실시간 │ │
211+
│ 결과 전송 │ │
212+
│◄──────────────────────┼───────────────────────┤
213+
│ │ │
214+
```
215+
216+
### 📋 분산 Chat 처리 상세
217+
218+
```csharp
219+
// 1. Server A에서 Chat 요청 처리
220+
// ChatService.EnqueueChatRequestAsync → Background Pipeline 실행
221+
222+
// 2. Pipeline 완료 후 결과 전송
223+
// ChatSuccessHandler에서 분산 메시지 전송
224+
await _distributedWebSocketManager.SendToUserAsync(userId, chatResult);
225+
226+
// 3. DistributedMessageBroker.SendToUserAsync (Line 66)
227+
public async Task SendToUserAsync(string userId, object message)
228+
{
229+
// 로컬에 사용자가 있는지 확인
230+
var isLocalActive = _webSocketManager.IsSessionActive(userId);
231+
232+
if (isLocalActive) {
233+
// 로컬 직접 전송
234+
await SendLocalMessage(userId, message);
235+
return;
236+
}
237+
238+
// 사용자가 어느 서버에 있는지 Redis에서 조회
239+
var targetServerId = await _serverRegistration.GetUserServerAsync(userId);
240+
241+
if (string.IsNullOrEmpty(targetServerId)) {
242+
_logger.LogWarning("사용자가 연결된 서버를 찾을 수 없음: {UserId}", userId);
243+
return;
244+
}
245+
246+
// 해당 서버로 Redis Pub/Sub 메시지 전송
247+
var brokerMessage = BrokerMessage.CreateUserMessage(userId, message, _serverId);
248+
var userChannel = $"user:{userId}";
249+
await _subscriber.PublishAsync(userChannel, brokerMessage.ToJson());
250+
}
251+
252+
// 4. Server B에서 Redis 메시지 수신
253+
// DistributedMessageBroker.OnUserMessageReceived (Line 187)
254+
private async void OnUserMessageReceived(RedisChannel channel, RedisValue message)
255+
{
256+
var brokerMessage = BrokerMessage.FromJson(message!);
257+
258+
// 로컬에서 해당 사용자가 연결되어 있는지 확인
259+
var isLocalActive = _webSocketManager.IsSessionActive(brokerMessage.TargetUserId);
260+
261+
if (isLocalActive) {
262+
var payload = brokerMessage.DeserializePayload<object>();
263+
await SendLocalMessage(brokerMessage.TargetUserId, payload);
264+
}
265+
}
266+
```
267+
268+
## Redis 키 구조
269+
270+
### 🔑 핵심 Redis 키 구조
271+
272+
```redis
273+
# 사용자 세션 정보
274+
session:user:12345 = {
275+
"ConnectionId": "conn_abc123",
276+
"ServerId": "api-server-001",
277+
"ConnectedAt": "2024-01-15T10:30:00Z",
278+
"LastActivity": "2024-01-15T10:45:00Z"
279+
}
280+
281+
# 사용자-서버 매핑
282+
user:server:12345 = "api-server-001"
283+
284+
# 서버 등록 정보
285+
server:api-server-001 = {
286+
"ServerId": "api-server-001",
287+
"StartedAt": "2024-01-15T10:00:00Z",
288+
"LastHeartbeat": "2024-01-15T10:45:30Z",
289+
"ActiveConnections": 25,
290+
"Status": "healthy"
291+
}
292+
293+
# 활성 서버 목록
294+
servers:active = {"api-server-001", "api-server-002", "api-server-003"}
295+
296+
# Redis Pub/Sub 채널
297+
user:12345 # 특정 사용자 메시지
298+
server:api-001 # 특정 서버 메시지
299+
broadcast # 전체 브로드캐스트
300+
```
301+
302+
## 핵심 컴포넌트
303+
304+
### 주요 클래스 및 파일
305+
306+
| 컴포넌트 | 파일 위치 | 역할 |
307+
|----------|-----------|------|
308+
| `ChatController` | `ProjectVG.Api/Controllers/ChatController.cs:22` | HTTP Chat 요청 접수 |
309+
| `ChatService` | `ProjectVG.Application/Services/Chat/ChatService.cs` | Chat 처리 파이프라인 orchestration |
310+
| `WebSocketMiddleware` | `ProjectVG.Api/Middleware/WebSocketMiddleware.cs:31` | WebSocket 연결 관리 |
311+
| `DistributedWebSocketManager` | `ProjectVG.Application/Services/WebSocket/DistributedWebSocketManager.cs:35` | 분산 WebSocket 세션 관리 |
312+
| `DistributedMessageBroker` | `ProjectVG.Application/Services/MessageBroker/DistributedMessageBroker.cs:66` | Redis Pub/Sub 메시지 브로커 |
313+
| `ConnectionRegistry` | `ProjectVG.Application/Services/Session/ConnectionRegistry.cs` | 로컬 연결 레지스트리 |
314+
315+
### Chat Processing Pipeline 단계
316+
317+
1. **ChatRequestValidator**: 입력 검증
318+
2. **UserInputAnalysisProcessor**: 사용자 의도 분석
319+
3. **MemoryContextPreprocessor**: 메모리 컨텍스트 수집
320+
4. **UserInputActionProcessor**: 액션 처리
321+
5. **ChatLLMProcessor**: LLM 호출 (Cost Tracking 적용)
322+
6. **ChatTTSProcessor**: TTS 처리 (Cost Tracking 적용)
323+
7. **ChatResultProcessor**: 결과 처리
324+
8. **ChatSuccessHandler/ChatFailureHandler**: 성공/실패 처리
325+
326+
## 💡 핵심 요약
327+
328+
1. **Chat 요청**: HTTP로 즉시 응답 → 백그라운드 파이프라인 → WebSocket으로 결과 전송
329+
2. **WebSocket 연결**: JWT 검증 → 로컬 레지스트리 등록 → Redis 세션 저장 → 채널 구독
330+
3. **세션 관리**: 로컬(ConnectionRegistry) + 분산(Redis) 이중 저장
331+
4. **분산 메시지**: Redis Pub/Sub로 서버 간 실시간 메시지 라우팅
332+
5. **서버 발견**: Redis 기반 서버 등록/헬스체크/정리 시스템
333+
334+
이 시스템을 통해 여러 API 서버 인스턴스가 실행되어도 사용자는 어느 서버에서든 Chat 요청을 보낼 수 있고, WebSocket으로 실시간 결과를 받을 수 있습니다.
335+
336+
## 관련 문서
337+
338+
- [분산 시스템 개요](../distributed-system/README.md)
339+
- [WebSocket 연결 관리](./websocket_connection_management.md)
340+
- [WebSocket HTTP 아키텍처](./websocket_http_architecture.md)
341+
- [REST API 엔드포인트](../api/rest-endpoints.md)

0 commit comments

Comments
 (0)