-
Notifications
You must be signed in to change notification settings - Fork 0
Feature : 분산 시스템 세션 개선 - Redis Pub/sub #23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 13 commits
95679f8
4e1c7d0
1ba9e2a
eaf1959
5f1f9dc
992e21a
98522a2
a1364dd
90529c3
d8455dd
625cc80
15adfec
f16b34c
4a56676
48f34c6
c548522
295337c
095564f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -3,13 +3,23 @@ | |||||
| # =========================================== | ||||||
| # Copy this file to .env and fill in your actual values | ||||||
|
|
||||||
| ENVIRONMENT=development | ||||||
| DEBUG_MODE=true | ||||||
| LOG_LEVEL=INFO | ||||||
| LOG_FORMAT=json | ||||||
| DATA_PATH=../data | ||||||
|
|
||||||
| # Database Configuration | ||||||
| DB_CONNECTION_STRING=Server=host.docker.internal,1433;Database=ProjectVG;User Id=sa;Password=YOUR_DB_PASSWORD;TrustServerCertificate=true;MultipleActiveResultSets=true | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. DB 연결 문자열은 따옴표로 감싸기 특수문자 포함 값은 인용이 안전합니다. dotenv-linter 경고도 해소됩니다. -DB_CONNECTION_STRING=Server=host.docker.internal,1433;Database=ProjectVG;User Id=sa;Password=YOUR_DB_PASSWORD;TrustServerCertificate=true;MultipleActiveResultSets=true
+DB_CONNECTION_STRING="Server=host.docker.internal,1433;Database=ProjectVG;User Id=sa;Password=YOUR_DB_PASSWORD;TrustServerCertificate=true;MultipleActiveResultSets=true"📝 Committable suggestion
Suggested change
🧰 Tools🪛 dotenv-linter (3.3.0)[warning] 13-13: [ValueWithoutQuotes] This value needs to be surrounded in quotes (ValueWithoutQuotes) 🤖 Prompt for AI Agents |
||||||
| DB_PASSWORD=YOUR_DB_PASSWORD | ||||||
|
|
||||||
| # Redis Configuration | ||||||
| REDIS_CONNECTION_STRING=host.docker.internal:6380 | ||||||
|
|
||||||
| # Distributed System Configuration | ||||||
| DISTRIBUTED_MODE=false | ||||||
| SERVER_ID= | ||||||
|
|
||||||
| # External Services | ||||||
| LLM_BASE_URL=http://host.docker.internal:7930 | ||||||
| MEMORY_BASE_URL=http://host.docker.internal:7940 | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -10,5 +10,25 @@ | |||||||||||||||||||||||||||||
| "JWT": { | ||||||||||||||||||||||||||||||
| "Issuer": "ProjectVG", | ||||||||||||||||||||||||||||||
| "Audience": "ProjectVG" | ||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||
| "DistributedSystem": { | ||||||||||||||||||||||||||||||
| "Enabled": true, | ||||||||||||||||||||||||||||||
| "ServerId": "api-server-001", | ||||||||||||||||||||||||||||||
| "HeartbeatIntervalSeconds": 30, | ||||||||||||||||||||||||||||||
| "CleanupIntervalMinutes": 5, | ||||||||||||||||||||||||||||||
| "ServerTimeoutMinutes": 2 | ||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||
|
Comment on lines
+14
to
+20
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chainServerId 고정값은 인스턴스 충돌 유발 — 인스턴스별 유일값 보장 필요
또한 간격/단위 정합성 확인:
빠른 개선안(정리 주기 단축): - "CleanupIntervalMinutes": 5,
+ "CleanupIntervalMinutes": 1,추가 권장(선택):
중복 ServerId 사용 가능성 점검: 🏁 Script executed: #!/bin/bash
rg -n -C2 '"ServerId"\s*:\s*"' --hiddenLength of output: 1942 ServerId 하드코딩 제거 — 인스턴스별 유일값 필요 ProjectVG.Api/appsettings.json에 "ServerId": "api-server-001"이 하드코딩되어 있어 다중 인스턴스에서 충돌/세션 오염 발생 가능. 문서에도 동일 예시가 존재함.
빠른 개선안: - "CleanupIntervalMinutes": 5,
+ "CleanupIntervalMinutes": 1,수정 위치: ProjectVG.Api/appsettings.json (약 lines 14–18); 문서 예시: docs/distributed-system/README.md, docs/architecture/chat-system-flow.md. 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
| "LLM": { | ||||||||||||||||||||||||||||||
| "BaseUrl": "http://localhost:7930" | ||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||
| "MEMORY": { | ||||||||||||||||||||||||||||||
| "BaseUrl": "http://localhost:7940" | ||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||
| "TTS": { | ||||||||||||||||||||||||||||||
| "BaseUrl": "https://supertoneapi.com" | ||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||
|
Comment on lines
+21
to
+29
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 🧩 Analysis chain외부 서비스 BaseUrl만으로는 운영 리스크 — 타임아웃/재시도 등 구성 노출 권장 LLM/MEMORY/TTS 섹션에 최소 다음을 함께 노출하면 운영 안정성이 향상됩니다:
예시(권장 키; 실제 반영은 코드 지원 필요):
BaseUrl 사용부에서 슬래시 정규화/타임아웃이 적용되는지 확인: 🏁 Script executed: #!/bin/bash
rg -n -C3 -i '(LLM|MEMORY|TTS).*BaseUrl|HttpClient|Timeout|Circuit|Retry' --type cs --hiddenLength of output: 35983 운영 리스크: LLM/MEMORY/TTS 외부 클라이언트 — 타임아웃/재시도/회로차단 구성 노출 및 BaseUrl 슬래시 정규화 필요
|
||||||||||||||||||||||||||||||
| "ConnectionStrings": { | ||||||||||||||||||||||||||||||
| "DefaultConnection": "Server=localhost,1433;Database=ProjectVG;User Id=sa;Password=ProjectVG123!;TrustServerCertificate=true;MultipleActiveResultSets=true", | ||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain심각: DB 자격증명(비밀번호) 커밋됨 — 즉시 제거 및 비밀 회수 필요
권고 조치:
즉시 적용 가능한 임시 수정(기본값에서 비밀번호 제거; 실제 값은 환경변수로 오버라이드): - "DefaultConnection": "Server=localhost,1433;Database=ProjectVG;User Id=sa;Password=ProjectVG123!;TrustServerCertificate=true;MultipleActiveResultSets=true",
+ "DefaultConnection": "Server=localhost,1433;Database=ProjectVG;TrustServerCertificate=true;MultipleActiveResultSets=true",비밀 노출 여부 점검 스크립트: 🏁 Script executed: #!/bin/bash
# 민감 문자열 및 연결문자열 유사 패턴 탐지
rg -n -C1 -i '(Password=|User Id=sa|ProjectVG123!)|DefaultConnection' --hiddenLength of output: 155 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "Searching for 'DefaultConnection' (fixed string)..."
rg -nF --hidden --no-ignore 'DefaultConnection' -S || true
echo
echo "Searching for 'Password=' (fixed string)..."
rg -nF --hidden --no-ignore 'Password=' -S || true
echo
echo "Searching for 'User Id=sa' (fixed string)..."
rg -nF --hidden --no-ignore 'User Id=sa' -S || true
echo
echo "Showing ProjectVG.Api/appsettings.json (if present):"
if [ -f ProjectVG.Api/appsettings.json ]; then
nl -ba ProjectVG.Api/appsettings.json | sed -n '1,200p'
else
echo "File not found: ProjectVG.Api/appsettings.json"
fiLength of output: 3207 즉시 조치 필요: 평문 DB 비밀번호(sa:ProjectVG123!)가 커밋됨 — 즉시 제거·회수 및 이력 정리 다음 파일에서 평문 연결문자열 확인(수정 필요):
권장 즉시 조치:
임시 패치(앱설정에서 비밀번호 제거 — 실제 값은 환경변수로 주입): - "DefaultConnection": "Server=localhost,1433;Database=ProjectVG;User Id=sa;Password=ProjectVG123!;TrustServerCertificate=true;MultipleActiveResultSets=true",
+ "DefaultConnection": "Server=localhost,1433;Database=ProjectVG;TrustServerCertificate=true;MultipleActiveResultSets=true",추가 확인: .env.example 및 문서에 예시 연결문자열 존재(민감값 포함 여부 점검). 모든 변경 후 CI/CD 및 배포 비밀도 갱신. 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
| "Redis": "projectvg-redis:6379" | ||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chainRedis 보안 설정 누락(인증/TLS) — 프로덕션 대비 강화 필요 현재
개발/로컬은 유지하되, 비슷한 설정이 다른 파일에 하드코딩되지 않았는지 점검: 🏁 Script executed: #!/bin/bash
rg -n -C2 -i 'redis.*(password|ssl|rediss|:6379)' --hiddenLength of output: 1835 Redis 보안(인증/TLS) 미설정 — 프로덕션 적용 전 즉시 수정 필요 문제: ProjectVG.Api/appsettings.json에 인증/암호화 없이 Redis가 하드코딩되어 있습니다: 조치(우선순위)
ProjectVG.Api/appsettings.json, docs/deployment/ci-cd-setup.md, ProjectVG.Infrastructure/Persistence/Session/RedisSessionStorage.cs에서 우선 수정 필요. 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| using Microsoft.Extensions.DependencyInjection; | ||
| using Microsoft.Extensions.Configuration; | ||
| using ProjectVG.Application.Services.Auth; | ||
| using ProjectVG.Application.Services.Character; | ||
| using ProjectVG.Application.Services.Chat; | ||
|
|
@@ -12,12 +13,14 @@ | |
| using ProjectVG.Application.Services.Credit; | ||
| using ProjectVG.Application.Services.Users; | ||
| using ProjectVG.Application.Services.WebSocket; | ||
| using ProjectVG.Application.Services.MessageBroker; | ||
| using ProjectVG.Application.Services.Server; | ||
|
|
||
| namespace ProjectVG.Application | ||
| { | ||
| public static class ApplicationServiceCollectionExtensions | ||
| { | ||
| public static IServiceCollection AddApplicationServices(this IServiceCollection services) | ||
| public static IServiceCollection AddApplicationServices(this IServiceCollection services, IConfiguration configuration) | ||
| { | ||
| // Auth Services | ||
| services.AddScoped<IAuthService, AuthService>(); | ||
|
|
@@ -69,13 +72,34 @@ public static IServiceCollection AddApplicationServices(this IServiceCollection | |
| // Conversation Services | ||
| services.AddScoped<IConversationService, ConversationService>(); | ||
|
|
||
| // Session Services | ||
| services.AddSingleton<IConnectionRegistry, ConnectionRegistry>(); | ||
|
|
||
| // WebSocket Services | ||
| services.AddScoped<IWebSocketManager, WebSocketManager>(); | ||
| // Distributed System Services | ||
| AddDistributedServices(services, configuration); | ||
|
|
||
| return services; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// 분산 시스템 관련 서비스 등록 | ||
| /// </summary> | ||
| private static void AddDistributedServices(IServiceCollection services, IConfiguration configuration) | ||
| { | ||
| var distributedEnabled = configuration.GetValue<bool>("DistributedSystem:Enabled", false); | ||
|
|
||
| if (distributedEnabled) | ||
| { | ||
| // 분산 환경 서비스 | ||
| services.AddSingleton<IMessageBroker, DistributedMessageBroker>(); | ||
| services.AddSingleton<IWebSocketManager, DistributedWebSocketManager>(); | ||
| } | ||
| else | ||
| { | ||
| // 단일 서버 환경 서비스 | ||
| services.AddSingleton<IMessageBroker, LocalMessageBroker>(); | ||
| services.AddSingleton<IWebSocketManager, WebSocketManager>(); | ||
|
||
| } | ||
|
|
||
| // WebSocket 연결 관리 | ||
| services.AddSingleton<IConnectionRegistry, ConnectionRegistry>(); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| using System.Text.Json; | ||
|
|
||
| namespace ProjectVG.Application.Models.MessageBroker | ||
| { | ||
| public class BrokerMessage | ||
| { | ||
| public string MessageId { get; set; } = Guid.NewGuid().ToString(); | ||
| public string MessageType { get; set; } = string.Empty; | ||
| public string? TargetUserId { get; set; } | ||
| public string? TargetServerId { get; set; } | ||
| public string? SourceServerId { get; set; } | ||
| public DateTime Timestamp { get; set; } = DateTime.UtcNow; | ||
| public string Payload { get; set; } = string.Empty; | ||
| public Dictionary<string, string> Headers { get; set; } = new(); | ||
|
|
||
| public static BrokerMessage CreateUserMessage(string userId, object payload, string? sourceServerId = null) | ||
| { | ||
| return new BrokerMessage | ||
| { | ||
| MessageType = "user_message", | ||
| TargetUserId = userId, | ||
| SourceServerId = sourceServerId, | ||
| Payload = JsonSerializer.Serialize(payload), | ||
| Headers = new Dictionary<string, string> | ||
| { | ||
| ["content-type"] = "application/json" | ||
| } | ||
| }; | ||
| } | ||
|
|
||
| public static BrokerMessage CreateServerMessage(string serverId, object payload, string? sourceServerId = null) | ||
| { | ||
| return new BrokerMessage | ||
| { | ||
| MessageType = "server_message", | ||
| TargetServerId = serverId, | ||
| SourceServerId = sourceServerId, | ||
| Payload = JsonSerializer.Serialize(payload), | ||
| Headers = new Dictionary<string, string> | ||
| { | ||
| ["content-type"] = "application/json" | ||
| } | ||
| }; | ||
| } | ||
|
|
||
| public static BrokerMessage CreateBroadcastMessage(object payload, string? sourceServerId = null) | ||
| { | ||
| return new BrokerMessage | ||
| { | ||
| MessageType = "broadcast_message", | ||
| SourceServerId = sourceServerId, | ||
| Payload = JsonSerializer.Serialize(payload), | ||
| Headers = new Dictionary<string, string> | ||
| { | ||
| ["content-type"] = "application/json" | ||
| } | ||
| }; | ||
| } | ||
|
|
||
| public T? DeserializePayload<T>() | ||
| { | ||
| try | ||
| { | ||
| return JsonSerializer.Deserialize<T>(Payload); | ||
| } | ||
| catch | ||
| { | ||
| return default; | ||
| } | ||
| } | ||
|
|
||
| public string ToJson() | ||
| { | ||
| return JsonSerializer.Serialize(this); | ||
| } | ||
|
|
||
| public static BrokerMessage? FromJson(string json) | ||
| { | ||
| try | ||
| { | ||
| return JsonSerializer.Deserialize<BrokerMessage>(json); | ||
| } | ||
| catch | ||
| { | ||
| return null; | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| namespace ProjectVG.Application.Models.Server | ||
| { | ||
| public class ServerInfo | ||
| { | ||
| public string ServerId { get; set; } = string.Empty; | ||
| public DateTime StartedAt { get; set; } | ||
| public DateTime LastHeartbeat { get; set; } | ||
| public int ActiveConnections { get; set; } | ||
| public string Status { get; set; } = "healthy"; | ||
| public string? Environment { get; set; } | ||
| public string? Version { get; set; } | ||
|
|
||
| public ServerInfo() | ||
| { | ||
| } | ||
|
|
||
| public ServerInfo(string serverId) | ||
| { | ||
| ServerId = serverId; | ||
| StartedAt = DateTime.UtcNow; | ||
| LastHeartbeat = DateTime.UtcNow; | ||
| ActiveConnections = 0; | ||
| Status = "healthy"; | ||
| } | ||
|
|
||
| public void UpdateHeartbeat() | ||
| { | ||
| LastHeartbeat = DateTime.UtcNow; | ||
| } | ||
|
|
||
| public void UpdateConnectionCount(int count) | ||
| { | ||
| ActiveConnections = count; | ||
| } | ||
|
|
||
| public bool IsHealthy(TimeSpan timeout) | ||
| { | ||
| return DateTime.UtcNow - LastHeartbeat < timeout; | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ENVIRONMENT vs ASPNETCORE_ENVIRONMENT 값 충돌
두 변수가 서로 다른 값을 갖고 있어 실제 실행 환경이 오인될 수 있습니다. 하나로 통일하세요.
(또는 ENVIRONMENT 키를 제거/주석 처리하고 ASPNETCORE_ENVIRONMENT만 사용)
Also applies to: 50-50
🧰 Tools
🪛 dotenv-linter (3.3.0)
[warning] 7-7: [UnorderedKey] The DEBUG_MODE key should go before the ENVIRONMENT key
(UnorderedKey)
[warning] 9-9: [UnorderedKey] The LOG_FORMAT key should go before the LOG_LEVEL key
(UnorderedKey)
[warning] 10-10: [UnorderedKey] The DATA_PATH key should go before the DEBUG_MODE key
(UnorderedKey)
🤖 Prompt for AI Agents