Files
web-page-backend/docs/superpowers/specs/2026-05-18-nas-windows-distributed-architecture-design.md
gahusb 90f6af6ab3 docs(arch): NAS↔Windows 분산 아키텍처 통합 design spec
박재오 7결정 + Obsidian 3개 문서(7결정 통합/API 부하/역할 분담)를
실행 가능한 형태로 정리.

12개 SP 분할 (Track A Quick Win 2건 + Track B Infrastructure 10건),
의존성 그래프, 시간대 조건부 우선순위(평일 비휴장일만 트레이딩 HIGH),
Windows Render Worker 통합 패턴 (인스타·음악·영상 셋이 같은 구조),
Redis 큐 컨벤션, SMB direct write + NAS internal webhook,
X-WebAI-Key / X-Internal-Key 분리, 3-layer 차단(IP 화이트리스트 +
Tailscale + 헤더), Suno+영상 API 키 Windows 이전 명세.

첫 plan 대상: Track A (SP-A1 web-ai 캐시 TTL + SP-A2 NAS stock
TTLCache, ~40분 작업, V2 재시작 시 NAS 인바운드 70% 감소).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 21:24:37 +09:00

22 KiB
Raw Blame History

NAS ↔ Windows 분산 아키텍처 — Design Spec

Date: 2026-05-18 Author: CEO (with Claude) Scope: web-backend + web-ai + 신규 web-ai-services (Windows WSL2 컨테이너 모음)


1. 배경 & 목적

NAS Synology J4025 (Celeron 2C/2.0GHz, 18GB)에서 11개 docker 컨테이너가 CPU를 과점유. 진단 결과 가장 큰 원인은 외부 인바운드 API 호출 빈도 (web-ai signal_v1/v2가 분당 12회 NAS stock 호출) + insta-lab Playwright Chromium의 동시 launch 비용이었다.

박재오 통찰: "NAS = 24/7 표출·게이트웨이 / Windows = 트레이딩 메인 + 트리거 기반 컴퓨팅". 박재오가 이미 7건의 의사결정을 마쳤고 1주 셋업 가이드도 정리되어 있다 (Obsidian Vault/raw/2026-05-18-Windows-NAS-아키텍처-7결정-통합.md).

본 spec은 그 위에 실행 단위 분할(SP) + 의존성 그래프 + 통합 패턴 + 데이터 플로우를 정리해서 실제 구현 plan으로 진입 가능한 형태로 만든다.

박재오 7결정 (수용된 결정 사항)

  1. d+b 조합 — Windows 작업 감지 큐 정지 + 트레이딩 우선순위 High
  2. insta-lab Playwright 1순위 이전 (NAS → Windows)
  3. 트리거 B(비동기) + C(예약) — 즉시 응답 X, task_id 발급 + 폴링
  4. 외부 영상 생성 도구 (Runway·Sora·Veo·Pika·Kling·Luma)
  5. Redis NAS 컨테이너 — 24/7 안정 큐
  6. 옵션 4 하이브리드 — 트레이딩 Native Python / 신규 WSL2 Docker Engine
  7. NSSM — Windows Service 도구 (자동 시작·우선순위)

2. 전체 아키텍처

[사용자 브라우저]
   ↓ HTTPS
[NAS Synology J4025] ─── 24/7 안정 · 표출 · 게이트웨이 · 상태(state)
  ├─ frontend (nginx :8080)            React SPA
  ├─ redis (:6379)            ⭐ NEW    24/7 큐 + 캐시
  ├─ stock (:18500)            +TTLCache  메타 + KIS data + WebAI gateway
  ├─ insta-lab (:18700)        분할 후   카피 생성 + DB + Redis push
  ├─ music-lab (:18600)        분할 후   메타 + Redis push (Suno/MusicGen 미실행)
  ├─ video-lab (:18XXX)       ⭐ NEW    영상 게이트웨이 + Redis push
  ├─ agent-office (:18900)              텔레그램 라우팅 + scheduler
  ├─ lotto / realestate-lab / personal / packs-lab / travel-proxy
  └─ deployer (:19010)
                              ↓ Redis BLPOP / 직접 HTTP webhook
[Windows AI Server 192.168.45.59] ─── 트레이딩 최우선 · 트리거 컴퓨팅
  ├─ 🔵 Native Python (NSSM HIGH priority)
  │   ├─ signal_v2 (:8001)    ⭐ 트레이딩 절대 우선
  │   ├─ Ollama qwen3:14b (:11435)
  │   └─ MusicGen (:8765)
  └─ 🟢 WSL2 + Docker Engine (NORMAL priority)
      ├─ insta-render (:18710)  ⭐ NEW   Playwright Chromium pool
      ├─ music-render (:18711)  ⭐ NEW   Suno API + MusicGen orchestration
      ├─ video-render (:18712)  ⭐ NEW   외부 영상 API gateway (6 provider)
      └─ task-watcher                   박재오 작업 감지 + 시간대 분기

핵심 원칙

  1. NAS = state(DB) + view(nginx 미디어 서빙), Windows = stateless compute
  2. 트레이딩 절대 우선 — 시간대 조건부 (아래 §3 참조)
  3. 무거운 작업 시간대 분리 — 데드존 23:3004:30 + 주말·휴장일 = 골든타임

3. 시간대별 우선순위 모드

모드 조건 signal_v2 task-watcher 정책
🔴 트레이딩 평일 비휴장일 07:0016:30 NSSM HIGH, polling 활성 박재오 활동 감지 시 queue:paused SET
🟡 일반 평일 16:3023:30 (NXT) NSSM HIGH 유지 (5분 폴링 가벼움) 박재오 활동 감지 시 SET
🟢 자유 주말·휴장일 + 평일 23:3004:30 자동 idle (휴장일 polling 미실행) queue:paused DEL 유지 — 큐 항상 활성

구현 위치

  • signal_v2의 휴장일 인식: web-ai CHECK_POINT #7 holidays.json 자동 동기화 항목. 휴장일·주말에 polling 자체 미실행.
  • 휴장일 단일 소스: web-backend/stock/app/holidays.json 정본. NAS stock이 GET /api/stock/holidays로 노출. signal_v2 + task-watcher가 매일 00:00 갱신.
  • task-watcher 시간대 분기: current_mode() 함수가 30초 폴링마다 모드 판정 → queue:paused 토글.

4. Sub-project 카탈로그 (12개)

SP 명칭 트랙 위치 소요
SP-A1 web-ai 캐시 TTL 증가 A web-ai/signal_v2/stock_client.py 10분
SP-A2 NAS stock TTLCache A web-backend/stock/app/* 30분
SP-1 NAS Redis 컨테이너 B (Base) web-backend/docker-compose.yml 30분
SP-2 Windows WSL2 + Docker Engine B (Base) (Windows AI) 2h
SP-3 insta-render Windows 서비스 B web-ai-services/insta-render/ (신규) 4h
SP-4 NAS insta-lab 분할 B web-backend/insta-lab 2h
SP-5 music-render Windows 서비스 B web-ai-services/music-render/ (신규) 3h
SP-6 NAS music-lab 분할 B web-backend/music-lab 2h
SP-7 video-render Windows 서비스 B web-ai-services/video-render/ (신규) 3h
SP-8 NAS video-lab 신설 B web-backend/video-lab/ (신규 컨테이너) 2h
SP-9 NSSM 자동 시작 + 우선순위 B (Windows) 1h
SP-10 task-watcher (시간대 + 활동 감지) B web-ai-services/task-watcher/ (신규) 2h

총 작업시간: ~22.5h (1주 일정에 부합)

의존성 그래프

A 트랙 (병행, ~40분)
  SP-A1 ─╮
         ├── V2 재시작 시 효과
  SP-A2 ─╯

B 트랙 Base (Day 1~2)
  SP-1 (Redis) ─┐
                ├── 인스타·음악·영상 3 트랙 모두 의존
  SP-2 (WSL2) ──┘

인스타 트랙 (Day 3~4)
  SP-3 (insta-render) ──→ SP-4 (NAS insta-lab 분할)

음악 트랙 (Day 4~5)
  SP-5 (music-render) ──→ SP-6 (NAS music-lab 분할)

영상 트랙 (Day 5~6)
  SP-7 (video-render) ──→ SP-8 (NAS video-lab 신설)

인프라 마무리 (Day 6~7)
  SP-9 (NSSM) ──→ SP-10 (task-watcher)

Critical Path

SP-1 ∥ SP-2SP-3SP-4SP-9SP-10 (최단 약 11.5h)

병렬화: SP-1(NAS)·SP-2(Windows)는 다른 머신이라 동시 진행. 인스타·음악·영상 트랙은 패턴이 같아 한 번 정착 후 빠르게 복제.


5. 통합 패턴 — "Windows Render Worker"

인스타·음악·영상 3 트랙이 완전히 같은 패턴. 한 번만 정의하고 3번 재사용한다.

시퀀스

사용자 ─POST /api/{kind}/generate ...──→ NAS {kind}-lab
                                            │
                                            ├─ DB.create_task() → task_id
                                            ├─ Redis RPUSH queue:{kind}-render {task_id, params, ...}
                                            └─ 200 {task_id}                          ─→ 사용자
                                            
                                        [Windows {kind}-render]
                                            │ (queue:paused 체크 후 BLPOP queue:{kind}-render)
                                            │
                                            ├─ POST /api/internal/{kind}/update
                                            │  {status: "processing", progress: 30}    ─→ NAS DB update
                                            │
                                            ├─ 무거운 작업 (Playwright / Suno / Runway 등)
                                            │   결과 파일 → /mnt/nas/data/{kind}/{id}/{file}  (SMB direct write)
                                            │
                                            └─ POST /api/internal/{kind}/update
                                               {status: "succeeded", progress: 100,
                                                result_path: "/media/{kind}/{id}/{file}"} ─→ NAS DB update

사용자 ─GET /api/{kind}/tasks/{task_id}──→ NAS {kind}-lab
                                            └─ DB.get_task() → {status, progress, result_path}
                                                                                       ─→ 사용자 (폴링)

4가지 미세 개선 (반영됨)

  1. 결과물 저장: SMB direct write (/mnt/nas/data/) — 별도 HTTP upload 단계 제거
  2. NAS 알림: Windows → NAS internal webhook (POST /api/internal/{kind}/update) — NAS polling 부담 0
  3. 사용자 응답: 폴링 유지 (YAGNI, 미래 SSE 검토)
  4. 인증 키 분리: X-WebAI-Key(read, web-ai→NAS) vs X-Internal-Key(write, Windows→NAS)

6. Redis 키 컨벤션

종류 TTL 용도
queue:insta-render list (없음) 인스타 카드 렌더 작업 큐
queue:music-render list (없음) 음악 생성 작업 큐
queue:video-render list (없음) 영상 생성 작업 큐
queue:paused string "1" 600s task-watcher가 set/del. worker가 BLPOP 전 확인
(옵션) cache:stock:* string (json) 120~600s NAS stock Redis 캐시 (SP-A2와 별개 옵션)

큐 payload 표준 (JSON)

{
  "task_id": "uuid-...",
  "kind": "insta|music|video",
  "params": { ... },
  "submitted_at": "2026-05-18T08:30:00+09:00"
}

Worker는 BLPOP queue:{kind}-render (1초 timeout) → queue:paused 체크 → 처리.


7. NAS 볼륨 레이아웃 + nginx 서빙

실 파일 시스템

/volume1/docker/webpage/data/
├── insta/{slate_id}/01.png ~ 10.png
├── music/{track_id}/{file}.mp3
└── video/{video_id}/{file}.mp4

WSL2 마운트

# WSL2 /etc/fstab
//gahusb.synology.me/docker/webpage/data /mnt/nas cifs username=...,vers=3.0,uid=1000,_netdev 0 0

nginx 서빙

https://gahusb.synology.me/media/insta/{id}/01.png
                          /music/{id}/...
                          /video/{id}/...

→ nginx location /media/ 블록은 /volume1/docker/webpage/data/를 alias로 서빙 (기존 패턴).


8. NAS internal webhook 명세

Endpoint

POST /api/internal/{kind}/update (kind ∈ insta|music|video)

인증 — 3-layer 차단

  1. nginx IP 화이트리스트 (Layer 1·2):
    location /api/internal/ {
        allow 192.168.45.0/24;     # LAN 화이트리스트
        allow 100.64.0.0/10;       # Tailscale CGNAT 대역
        deny all;
        ...
    }
    
  2. X-Internal-Key 헤더 검증 (Layer 3): verify_internal_key dependency

Payload

{
  "task_id": "uuid-...",
  "status": "processing|succeeded|failed",
  "progress": 0-100,
  "result_path": "/media/insta/123/01.png",     // succeeded일 때만, nginx 경로
  "error": "exception message"                    // failed일 때만
}

NAS 측 처리

  1. tasks 테이블 row update (status, progress, result_path, error)
  2. (옵션) Redis PUBLISH task:{id} — 미래 SSE 통합 시 활용
  3. 200 응답 (또는 401 if invalid key)

인증 키 정책

방향 권한 위치
X-WebAI-Key web-ai → NAS read-only (GET /api/webai/*) NAS .env + web-ai .env
X-Internal-Key Windows worker → NAS write-only (POST /api/internal/*) NAS .env + Windows .env

분리 사유: Principle of Least Privilege, 독립 로테이션, 감사 로그 명확성.

인증 helper (NAS 공통 모듈, web-backend/_shared/auth.py 또는 각 컨테이너 복제)

from fastapi import Header, HTTPException
import os

async def verify_internal_key(x_internal_key: str = Header(...)):
    expected = os.getenv("INTERNAL_API_KEY")
    if not expected or x_internal_key != expected:
        raise HTTPException(401, "Invalid X-Internal-Key")

# 라우터 사용
@app.post("/api/internal/insta/update", dependencies=[Depends(verify_internal_key)])
async def insta_update(payload: InternalUpdate): ...

기존 verify_webai_key 패턴(메모리 reference_webai_auth_pattern.md)을 복제.


9. Suno + 외부 영상 API 키 이전

NAS .env에서 다음 키들을 제거 → Windows .env로 이전:

NAS 이전 Windows 이후
SUNO_API_KEY music-lab music-render
RUNWAY_API_KEY (없음) video-render
OPENAI_API_KEY (Sora) (있을 수도) video-render
GEMINI_API_KEY (Veo) (없음) video-render
PIKA_API_KEY / KLING_API_KEY / LUMA_API_KEY (없음) video-render

→ NAS music-lab + video-lab은 외부 API 호출 코드를 가지지 않음. Redis push만.


10. SP 상세 명세

SP-A1 — web-ai 캐시 TTL 증가 (10분)

파일: web-ai/signal_v2/stock_client.py

변경:

# 변경 전
PORTFOLIO_TTL = 60
NEWS_TTL      = 300
SCREENER_TTL  = 60

# 변경 후
PORTFOLIO_TTL = 180   # 3분
NEWS_TTL      = 600   # 10분
SCREENER_TTL  = 300   # 5분

효과: 분당 12 → 34 호출 (70% 감소), 캐시 hit ratio 050% → 6680%

SP-A2 — NAS stock TTLCache (30분)

파일: web-backend/stock/app/* (webai endpoint 위치 확인 후)

from cachetools import TTLCache

_PORTFOLIO_CACHE = TTLCache(maxsize=1, ttl=120)
_NEWS_CACHE      = TTLCache(maxsize=10, ttl=600)
_SCREENER_CACHE  = TTLCache(maxsize=10, ttl=180)

@app.get("/api/webai/portfolio", dependencies=[Depends(verify_webai_key)])
async def portfolio():
    if "result" in _PORTFOLIO_CACHE:
        return _PORTFOLIO_CACHE["result"]
    result = await compute_portfolio()
    _PORTFOLIO_CACHE["result"] = result
    return result

3 endpoint 적용: /api/webai/portfolio · /api/webai/news-sentiment · /api/stock/screener/run. cachetools 의존성 requirements.txt 확인.

효과: V1+V2 동시 호출도 NAS에서 1회 계산. KIS·LLM 재호출 방지.

SP-1 — NAS Redis 컨테이너 (30분)

파일: web-backend/docker-compose.yml에 추가

  redis:
    image: redis:7-alpine
    container_name: redis
    restart: unless-stopped
    ports:
      - "6379:6379"
    volumes:
      - ${RUNTIME_PATH}/redis-data:/data
    command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 60s
      timeout: 5s
      retries: 3

검증: docker exec redis redis-cli PINGPONG

SP-2 — Windows WSL2 + Docker Engine + Tailscale (2h)

박재오 Windows AI Server에서 (관리자 PowerShell):

wsl --install -d Ubuntu-22.04
# 재부팅 후
wsl -d Ubuntu-22.04

WSL2 안:

# Docker Engine
sudo apt update && sudo apt install -y ca-certificates curl gnupg
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list
sudo apt update && sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo usermod -aG docker $USER

# Tailscale
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up

# NAS SMB mount
sudo mkdir -p /mnt/nas
echo "//gahusb.synology.me/docker/webpage/data /mnt/nas cifs username=...,vers=3.0,uid=1000,_netdev 0 0" | sudo tee -a /etc/fstab
sudo mount -a

검증: docker ps, tailscale status, ls /mnt/nas

SP-3 — insta-render Windows 서비스 (4h)

디렉토리: C:\Users\jaeoh\Desktop\workspace\web-ai-services\insta-render\

insta-render/
├── Dockerfile
├── docker-compose.yml
├── requirements.txt
├── .env
├── main.py
├── worker.py
└── card_renderer.py    # 기존 NAS insta-lab/app/card_renderer.py 이식

핵심 로직:

  • worker.py: Redis BLPOP queue:insta-render (paused 체크)
  • card_renderer.py: Browser pool (init_browser/shutdown_browser) + render_slate
  • main.py: 시작 시 browser init + worker async task spawn
  • 완료 시 /mnt/nas/data/insta/{slate_id}/ 저장 + NAS webhook POST /api/internal/insta/update

SP-4 — NAS insta-lab 분할 (2h)

파일: web-backend/insta-lab/app/main.py + app/card_renderer.py

변경:

# 변경 전 — NAS에서 직접 렌더
async def _bg_render(task_id: str, slate_id: int):
    async with RENDER_SEMAPHORE:
        await card_renderer.render_slate(slate_id, ...)

# 변경 후 — Redis 큐에 push만
import redis.asyncio as aioredis
redis_client = aioredis.from_url(os.getenv("REDIS_URL", "redis://redis:6379"))

async def _bg_render(task_id: str, slate_id: int):
    payload = {"task_id": task_id, "kind": "insta",
               "params": {"slate_id": slate_id, "theme": "hedgy75"},
               "submitted_at": datetime.now(KST).isoformat()}
    await redis_client.rpush("queue:insta-render", json.dumps(payload))

추가: POST /api/internal/insta/update endpoint (Windows webhook 수신). 삭제: card_renderer.py Playwright 코드 (Browser pool, Semaphore 등), requirements.txt에서 playwright 제거, Dockerfile에서 Chromium install 제거.

SP-5 — music-render Windows 서비스 (3h)

디렉토리: web-ai-services/music-render/

  • Suno API client (외부 SaaS, polling 1~5분)
  • MusicGen local call (Windows localhost:8765)
  • Redis BLPOP queue:music-render
  • 결과 mp3 → /mnt/nas/data/music/{track_id}/{file}.mp3
  • NAS webhook POST /api/internal/music/update

SUNO_API_KEY Windows .env에 단독 보관.

SP-6 — NAS music-lab 분할 (2h)

Suno 호출 코드 + MusicGen 호출 코드 삭제. _bg_generate 함수를 Redis push로 변경. POST /api/internal/music/update endpoint 추가.

SP-7 — video-render Windows 서비스 (3h)

디렉토리: web-ai-services/video-render/

6 provider gateway (Runway·Sora·Veo·Pika·Kling·Luma) — provider 선택은 payload에서. 각 외부 API 호출 + 결과 mp4 다운로드 → /mnt/nas/data/video/{id}/. NAS webhook.

SP-8 — NAS video-lab 신설 (2h)

새 docker 컨테이너. web-backend/video-lab/:

  • app/main.py: 2 endpoint
    • POST /api/video/generate → Redis push queue:video-render + task_id 반환
    • GET /api/video/tasks/{id} → DB 조회
  • app/db.py: video_tasks 테이블 (sqlite)
  • POST /api/internal/video/update (Windows webhook)
  • Dockerfile, requirements, docker-compose.yml entry

매우 가벼움 (NAS CPU 부담 미미).

SP-9 — NSSM 자동 시작 + 우선순위 (1h)

Windows AI에서 NSSM 다운로드 후:

# 트레이딩 (Native, HIGH)
nssm install signal_v2 "C:\Python312\python.exe" "-m uvicorn main:app --host 0.0.0.0 --port 8001"
nssm set signal_v2 AppDirectory "C:\Users\jaeoh\Desktop\workspace\web-ai\signal_v2"
nssm set signal_v2 Priority HIGH_PRIORITY_CLASS
nssm set signal_v2 AppStartup AUTO

# WSL2 Docker (NORMAL)
nssm install wsl_docker "wsl" "-d Ubuntu-22.04 -- sudo service docker start && cd /workspace/web-ai-services && docker compose up -d"
nssm set wsl_docker Priority NORMAL_PRIORITY_CLASS
nssm set wsl_docker AppStartup AUTO

nssm start signal_v2
nssm start wsl_docker

SP-10 — task-watcher (2h)

디렉토리: web-ai-services/task-watcher/

WSL2 Docker 컨테이너. 30초마다:

  1. current_mode() 판정 (시간대 + holidays.json 체크 + KST 시각)
  2. is_user_active() 판정 (마우스/키보드 idle < 5분 또는 게임 process 감지)
  3. 모드 + 활동 → queue:paused 토글
    • mode == "free"DEL queue:paused
    • mode != "free" and activeSET queue:paused 1 EX 600
    • mode != "free" and idleDEL queue:paused

11. 데이터 플로우 검증 — 인스타 사례 end-to-end

1. 사용자 클릭 "카드 생성"
   POST /api/insta/slates/123/render
       ↓ NAS insta-lab
2. NAS insta-lab
   - db.create_task("slate_render", {slate_id: 123}) → task_id="t-abc"
   - redis.rpush("queue:insta-render", {task_id: "t-abc", kind: "insta", params: {slate_id: 123, theme: "hedgy75"}})
   - 응답 {task_id: "t-abc"}
       ↓ 즉시 사용자
3. Windows insta-render worker
   - redis.blpop("queue:insta-render", 1)
   - paused 체크 → 통과
   - webhook(processing, 10%) → NAS DB update
   - Playwright 카드 10장 렌더 → /mnt/nas/data/insta/123/01.png..10.png
   - webhook(processing, 90%) 진행률 보고
   - webhook(succeeded, 100, result_path="/media/insta/123/01.png") → NAS DB update
4. 사용자 폴링
   GET /api/insta/tasks/t-abc → {status: "succeeded", result_path: "/media/insta/123/01.png"}
   브라우저에서 <img src="/media/insta/123/01.png" /> 렌더

12. Out of Scope

  • V1/V2 재시작 결정 (사용자 보류, 두 process 정지 유지)
  • NAS 하드웨어 업그레이드 (#12 보류)
  • 컨테이너 리소스 제한 cpus 0.5 (#11 박재오 진행 금지)
  • SSE/WS push 모델 (YAGNI, 폴링 유지)
  • Grafana 모니터링 (NAS 자산 활용 옵션, 향후)

13. 위험 요소

위험 완화
Windows 재부팅 시 worker 중단 NSSM AppStartup AUTO + WSL2 자동 시작 (SP-9)
Windows ↔ NAS 네트워크 단절 task가 큐에 남음, NAS 측 timeout 처리 (예: 30분 timeout → failed)
박재오 게임·작업 중 worker 충돌 task-watcher queue:paused (SP-10) + NORMAL priority
Suno API rate limit music-render 내부에서 retry + 큐 직렬 처리
SMB 마운트 실패 WSL2 부팅 시 mount -a, 실패 시 alarm (로그)
Redis 다운 docker restart unless-stopped + healthcheck. 다운 시 모든 worker idle (NAS는 응답 계속)
키 노출 3-layer 차단 (IP 화이트리스트 + nginx + X-Internal-Key)

14. 첫 plan 작성 대상

옵션 A — Track A만 (사용자 선택 확정):

  • SP-A1: web-ai 캐시 TTL 증가 (10분)
  • SP-A2: NAS stock TTLCache (30분)

이 plan은 즉시 NAS CPU 70% 감소 효과 (V2 재시작 시). Track B는 별도 spec/plan으로 차후 진행.

차후 plan 작성 순서 권장:

  1. Plan-A (이번) — SP-A1 + SP-A2
  2. Plan-B-Base — SP-1 + SP-2
  3. Plan-B-Insta — SP-3 + SP-4 (1순위 패턴 정착)
  4. Plan-B-Music — SP-5 + SP-6
  5. Plan-B-Video — SP-7 + SP-8
  6. Plan-B-Infra — SP-9 + SP-10

15. 참고

  • 박재오 7결정 통합: Obsidian Vault/raw/2026-05-18-Windows-NAS-아키텍처-7결정-통합.md
  • API 부하 해결: Obsidian Vault/raw/2026-05-18-NAS-Window-AI-API-부하-해결방안.md
  • 역할 분담 최적화: Obsidian Vault/raw/2026-05-18-NAS-Windows-역할-분담-최적화.md
  • web-backend CHECK_POINT.md (즉시·중기·장기 + 7결정 매핑)
  • web-ai CHECK_POINT.md (Phase 진행도)
  • 기존 인증 패턴: 메모리 reference_webai_auth_pattern.md