본문으로 건너뛰기

SA · 확장 후 — 시스템 구조

갱신일 2026-08-12 · 기준: 확장 개요 · 현재 판 SA 이 문서가 갖는 것: 컨테이너 · 경계별 프로토콜과 인증 · 런타임 흐름 넷 · 시간 상수 · 확장 천장. 여기 없는 것: 화면(IA) · 모듈 배치(AA) · AWS 리소스(CA) · 저장소 소유권(DA) · 물리 스키마(ERD) · 그래프 스키마(온톨로지) · 스트림 개선과 게이트 설계(실시간 경로).

⚠︎ 이건 계획이지 현황이 아니다. 지금 서 있는 것은 현재 판 SA가 갖는다.

1. 뼈대 한 줄이 깨진다 — public edge가 둘이 된다

현재 판 전체가 이 한 줄 위에 서 있다. "server가 유일한 public edge다." ai에 CORS 미들웨어도 인증도 없는 근거이고, 경계가 다섯뿐이던 근거다. MCP 서버(D8)가 이 전제를 깬다 — 기계 클라이언트가 브라우저 밖에서 들어온다.

전제 위에 서 있던 것전제가 깨진 뒤어디서
ai는 애플리케이션 인증을 안 갖는다 — 보안그룹이 막는다"내부니까 괜찮다"가 근거를 잃는다. ⑥ 에 인증을 넣는다§3
인증·감사 주체는 쿠키 세션 하나PAT이 두 번째 주체가 된다 — 범위 · 만료 · 상한. 감사 주체도 (user_id, token_id) 쌍이 된다§3 · §8
사람 속도가 자연 rate limit 이었다루프가 초당 수십 번 두드린다. 인증으로는 못 막는다§3 · §10
쓰기는 사람이 승인 버튼을 누른다누를 사람이 없는 호출이 생긴다. 토큰 범위가 버튼을 대신한다§3 · §7

오디오는 계속 server를 지난다(D1 · 확장 개요 §5). 브라우저 → Soniox 직결은 버렸다 — egress를 아끼는 대신 전사 출처가 클라이언트가 되고 STT 크레덴셜이 브라우저로 내려간다. 경계가 늘지 않는 것도 이 결정의 이득이다.

2. 컨테이너

speech는 평소 0대다. 회의가 끝날 때만 뜬다 — GPU를 쓰면서 원가가 안 터지는 유일한 방법이다(CA §6).

"저장소 다섯"의 정의는 DA §1 표다heymoa · heymoa_ai · 벡터 · Neo4j · S3. 그림에서 앞 둘은 Postgres 한 칸에 들어 있다. 브로커·큐는 그 다섯 밖이지만 저장소 칸에 그렸다 — 인스턴스 밖에 상태가 사는 순간부터 백업·장애·용량을 저장소처럼 봐야 한다. enableSimpleBroker 가 JVM 안이라 "인스턴스 1대"가 기능 정확성의 조건이었던 것이 여기서 풀린다. 브로커·큐 소유권도 그 표가 갖는다 — 원본/파생은 휘발, 주인은 server, 재생성은 해당 없음.

3. 경계 — 다섯에서 열다섯으로

확장 개요의 "경계 열하나"는 web↔server(①~④)와 S3(⑩⑪)를 각각 하나로 센 값이다. 여기서는 프로토콜과 인증이 다르면 다른 줄로 센다.

#구간프로토콜인증신규
web → serverREST /v1/**HttpOnly 쿠키
web ↔ serverSTOMP over WS (제어 · 팬아웃)쿠키 (핸드셰이크)
web ← serverSSE (채팅)쿠키
web → serverSTOMP — 오디오 (Int16 PCM)쿠키인코딩만 바뀜 — Opus는 CA §8 확인 후
server ↔ SonioxWebSocket서버 보관 API 키공급자 교체
server → aiREST /internal/v1/** + SSE 소비대칭 내부 토큰 또는 mTLS ← 지금 없음변경
ai → serverREST /internal/v1/**X-Internal-Token
server → speechSQS 메시지IAM (큐 정책) + 잡 토큰🆕
speech → serverREST callback내부 토큰 (⑦과 같은 규약)🆕
speech → S3GetObject태스크 role · gateway endpoint🆕
server → S3멀티파트 업로드태스크 role🆕
외부 에이전트 → mcpMCP (HTTP · stdio)PAT (범위 · 만료 · 상한)🆕
mcp → serverREST /v1/**PAT 그대로 전달🆕
server ↔ 브로커STOMP 릴레이 — Amazon MQ (RabbitMQ)브로커 크레덴셜🆕
ai → Neo4jBolt읽기 전용 계정🆕

브로커는 Amazon MQ로 확정됐다(RabbitMQ 엔진 · CA §3). Redis는 후보에서 빠졌다 — STOMP 릴레이가 그대로 붙는 쪽이 릴레이 코드를 안 고친다.

/internal/v1/** 는 방향 전용 접두사가 아니다. ⑥⑦⑨ 가 같은 네임스페이스를 쓴다(server ↔ ai 계약).

⑥ — 보안그룹으로는 더 이상 안 된다

서비스가 다섯이 되고 태스크가 오토스케일로 뜨고 지면 보안그룹 규칙이 사람이 따라갈 수 있는 물건이 아니게 된다. 게다가 §1에서 "내부니까 괜찮다"의 근거 자체가 사라졌다.

  • 대칭 내부 토큰X-Internal-Token 을 ⑥ 에도. 채택. ⑦⑨ 가 이미 이 규약이니 셋을 같은 모양으로 맞추는 게 제일 싸다.
  • mTLS — 더 강하지만 인증서 발급·회전 체계를 새로 들여야 한다. 서비스 다섯 규모에서 값이 안 맞는다.
  • 보안그룹만 유지는 버린다. 규칙 수가 서비스² 로 늘고 감사에 "누가 호출했나"가 안 남는다. 바꿀 때는 양쪽을 같은 변경으로 올린다 — server 클라이언트가 헤더를 싣기 전에 ai에서 검증을 켜면 분석과 채팅이 전부 401로 죽는다.

⑫⑬ — 기계 클라이언트는 다른 종류의 위험이다

위험방어
에이전트 루프토큰별 토큰버킷 (분당 · 일당). 인증은 이걸 못 막는다
응답이 크다 — LLM은 "이 회의 전사 전부"를 원한다응답 크기 상한 + 커서 페이지네이션 강제. 전문은 명시 요청일 때만
읽기가 무겁다 — 그래프 순회 + 벡터 검색이 한 호출에읽기 복제본 라우팅 · 순회 상한
재시도가 공격이 된다429 + Retry-After 로 백오프를 유도한다
쓰기에는 승인자가 없다기본 범위는 읽기 전용. 쓰기·도구 실행은 별도 범위로만
폭주가 옆을 죽인다mcp를 별도 서비스로 둔 값이 여기서 회수된다 — 회의 중인 사람의 WebSocket은 다른 태스크에 있다
토큰을 20개 발급하면 상한이 20배가 된다 — 토큰별 상한만으로는 워크스페이스가 스스로 상한을 곱한다워크스페이스 총량 상한을 토큰 상한 위에 하나 더 건다. 토큰 상한은 오작동 방지, 총량 상한은 테넌트 공정성

①~④ — 사람 edge 에도 상한이 있다

§1이 깬 전제가 여기서 값을 요구한다 — 사람 속도는 더 이상 rate limit이 아니다. 브라우저 콘솔 루프는 PAT를 안 쓰고 쿠키로 들어온다.

상한강제 지점
계정당 동시 WS5앱 (STOMP 핸드셰이크에서 거절)
노트당 진행 중 채팅 턴1앱 — chat.lock 계약을 상한으로 승격
재분석계정당 분당 1 · 노트당 시간당 3
오디오 프레임40–100ms 배치 기준 초당 상한. 초과하면 그 스트림을 끊는다앱 (릴레이)
로그인 · 초대IP+계정 5분 10회WAF rate rule

WAF rate rule은 로그인·초대만 건다. 나머지는 전부 앱이다 — 워크스페이스 스코프와 노트 소유를 ALB가 판단하지 못한다. IA §11의 월 10회는 원가 한도이지 초당 상한이 아니다. 둘은 다른 축이고 같은 수로 안 맞춘다.

4. 흐름 1 — 전사 (S1 · 10:00~10:45)

오디오가 server에서 두 갈래로 갈린다. 실시간 전사(⑤)와 배치 보관(⑪). 브라우저는 STOMP로 올리기만 한다 — 인코딩이 Opus로 바뀌는 것 말고 클라이언트 구조 변경이 없다. 직결이었다면 전사 원문이 클라이언트가 보낸 텍스트가 되고 STT 크레덴셜이 브라우저로 내려간다. 그 대신 릴레이 대역폭을 우리가 진다 — 1만에서 3.2MB/s, 10만에서 32MB/s. uploadId 를 DB에 두는 이유는 §9에서 회수된다.

5. 흐름 2 — 회의 중 에이전트 개입 (S1 · 10:23:14)

이서연: "그냥 STT 공급자 ElevenLabs로 다시 가시죠" → 2026-07-22 회의의 결정 "STT는 Soniox로 간다"C3 충돌.

발화→카드 p95 8초. 구간별 예산과 초 단위 타임라인은 실시간 §3 · 게이트는 실시간 §4 · 개입 형태는 실시간 §6이 갖는다.

틱을 ai에 두지 않았다. 세션 상태를 ai 프로세스에 두면 인스턴스 고정이 필요해지고 "ai는 무상태 재시작이 가능해야 한다"가 깨진다(D13). server는 이미 세그먼트를 저장하므로 새 스트림 소비자가 필요 없다.

6. 흐름 3 — 회의 종료 파이프라인 (S1 10:45 · S4 Spot 중단)

규칙이유
단계마다 부분 결과를 커밋한다화면이 단계별로 채워진다(IA §5)
Spot 중단은 그 잡만 재시도②단계만 다시 돈다. ①은 끝났고 ④는 아직 안 갔다
Postgres 먼저, 그래프 나중순서가 반대면 그래프가 정본이 된다 — DA §1

S4에서 사용자가 보는 것은 "누가 말했는지 정리 중" 하나뿐이다. Spot 중단도 콜드스타트 4분도 그 문구가 흡수한다. 요약은 예정대로 나온다.

7. 흐름 4 — MCP 조회 (S3)

박준호가 IDE에서 TranscriptionStompConfig.kt 를 보다가 "왜 SimpleBroker 지?"

  • mcp는 DB에 직접 안 붙고 권한은 server가 다시 본다. 직결하면 권한 로직이 두 벌이 되고, mcp만 믿으면 edge 하나가 뚫릴 때 전부 뚫린다.
  • 근거 타임스탬프를 반드시 싣는다. 근거 없는 답은 에이전트가 그대로 코드 주석에 박아 넣는다.

8. 시간 상수 — 다시 잡아야 하는 것

상수지금확장 후
워치독1분 주기 / 10분 고정단계별로 — ① 2분 · ② 오디오 길이 × 계수 · ③ 2분 · ④ 10분 · ⑤ 5분 (제안값)하나로 묶으면 GPU 배치엔 짧고 요약엔 길다
speech 잡 타임아웃오디오 길이 × 계수 + 콜드스타트 여유계수는 실측 후(AA §3.3)
access token TTL30분30분 + 스트림 위 갱신2시간 회의의 마지막 90분이 만료 토큰으로 열려 있다. 재핸드셰이크가 곧 녹음 중단이라 갱신 경로가 필수다
Soniox 세션 수명회의 길이를 견디거나 무중단 롤오버ElevenLabs는 3,600초 상한이 실측됐다. Soniox도 상한이 있다고 가정하고 45분·2시간을 둘 다 견디게 짠다
SSE keepalive≤20초 (구현 10초)그대로ALB idle보다 짧아야 한다 (§9)
MCP 토큰 상한분당 · 일당 (제안값 분당 60 · 일당 5,000)기계 클라이언트에는 상한이 인증만큼 중요하다
팬아웃 구독자 prefetch없음 (JVM 브로커)50외부 브로커는 밀어 넣는다. 상한이 없으면 느린 구독자 하나가 브로커 메모리를 먹는다
구독자당 미확인1,000건 또는 8MB (추정)넘으면 그 구독을 끊는다. 관전자 하나 때문에 회의 전체 팬아웃이 죽는 것보다 낫다
끊긴 구독의 복구새로고침클라이언트가 REST로 재동기전사는 이미 DB에 있다. 브로커에 재전송을 요구하지 않는다

짧은 쪽이 긴 쪽보다 먼저 끊으면 안 된다. 현재 판의 이 원칙은 그대로다. 항목이 늘 때마다 축을 다시 정렬한다.

단계 타임아웃의 정본은 이 표다. AA §3.3은 시도 상한과 백오프만 갖는다.

팬아웃은 lossy 다. 정본은 Postgres 이고 브로커는 지연 단축 장치다. 10만에서 동시 구독 2,000이라 이 전제를 안 박으면 브로커가 정본처럼 취급되고, 그 순간 느린 구독자 하나가 회의 전체를 인질로 잡는다.

9. S5 — 배포 중에 회의가 3건 돌고 있다

14:00 배포, 진행 중 회의 3건. 배포 동작은 CA §13이 갖는다 — 새 세션 안 받음 · task scale-in protection · 재연결 인계. 여기는 시간 상수만 본다.

상수이유
ALB idle timeoutWS 유휴 60초 · STOMP heartbeat 10초보다 길게관전자 STOMP는 유휴할 수 있다. heartbeat가 idle을 리셋하는 게 전제
deregistration delay (draining)45분 = 2,700초 (상한 3,600초)단독으로는 못 버틴다 — 2시간 회의가 상한을 넘는다. 실제로 회의를 지키는 장치는 task scale-in protection(CA §13)
태스크 stopTimeout120초 (Fargate 상한 · CA §13)45분 회의와 자릿수가 다르다. 그래서 CA가 task scale-in protection을 쓴다

세 상수 중 어느 것도 회의 길이를 못 덮는다. 그래서 마지막 방어선은 시간이 아니라 인계다 — 강제 종료돼도 브라우저가 재연결하고 DB의 uploadId 로 다른 태스크가 멀티파트를 이어받는다(§4). 인계가 없으면 위 표는 전부 희망사항이고, 배포 말고도 Spot 회수·AZ 장애·OOM이 같은 일을 한다.

재기동 시 전역 전사 세션을 끊는 현재 동작(APP-189)은 이 전환보다 먼저 걷는다. 안 걷으면 새 태스크가 옛 태스크의 라이브 세션을 죽인다.

10. 새 구조의 확장 천장

옛 천장을 걷으면 다음 천장이 나온다. 걸리는 순서는 확장 개요 §4가 갖는다.

천장언제넘는 방법
GPU 처리량정각 300건 동시 종료 (10만)지연 + 지터로 평탄화 → 그다음 인스턴스 수
Neo4j 쓰기그래프 투영이 회의마다 돈다투영을 비동기 배치로. 실시간일 이유가 없다
그래프 순회프로젝트가 커지면 이웃이 폭발한다깊이·팬아웃 상한 → 온톨로지 §7
벡터 재색인임베딩이 늘면 HNSW 빌드가 길어진다증분 색인 → 전용 스토어
Postgres 쓰기세그먼트 + 화자 + 카드 + 결정 + 액션 아이템배치 INSERT → 읽기 복제본 분리 → 샤딩
MCP 폭주에이전트 하나가 루프를 돈다토큰별 상한이 1차 방어, 서비스 격리가 2차
외부 rate limitOpenAI · Soniox여기서부터 우리 인프라 문제가 아니다. 큐잉과 공급자 이중화

11. 실패 모드 — 경계가 끊기면 무엇이 죽나

끊긴 경계degrade죽는 것S1에서 보이는 것
④ 오디오그 회의 녹음재연결까지의 발화가 빈다. 지금도 그렇다
⑤ Soniox오디오는 계속 S3로 쌓인다실시간 전사"전사 일시 중단". 오디오가 사니 사후 복구 여지가 있다
⑥ server→ai전사 · 녹음 · 팬아웃 정상요약 · 채팅 · 회의 중 카드회의는 멀쩡히 돈다. 파이프라인 ④가 대기
⑦ ai→serverai는 일했는데 결과가 안 돌아온다결과 영속화워치독이 유일한 방어선. 현재 판의 "10시간 조용한 실패"가 재발하는 자리
⑧⑨ speech④⑤ 는 돈다화자 분리 · 화자 매칭전원 화자 1~4 로 남고 요약은 나온다
⑩⑪ S3실시간 전사 정상화자 분리 영구 불가⑪ 이 끊기면 사후 복구 경로 자체가 없다
⑫⑬ MCP브라우저 경로 전부 정상에이전트 조회박준호만 영향
⑭ 브로커 — 끊김세그먼트는 DB에 커밋된다팬아웃 — 실시간 화면 전부새로고침하면 전사가 다 보인다. 끊긴 걸 아무도 모른다
⑭ 브로커 — 적체미확인 상한을 넘긴 구독을 끊고 나머지는 산다(§8)그 구독자의 실시간 화면느린 관전자만 REST로 재동기. 회의 전체는 안 죽는다
⑮ Neo4j벡터 검색만으로 답한다회의 간 맥락 · C3 충돌 감지10:23:14 카드가 안 뜬다. 아무도 모른다
한 계정 폭주 (①~④)그 계정만 429없다옆 사람 회의는 그대로 돈다. 상한은 §3
Postgres전부유일하게 전면 장애인 경계

⑭ 와 ⑮ 만 조용히 degrade 한다. 팬아웃 성공률과 그래프 조회 성공률을 지표로 세우지 않으면 이 둘은 관측되지 않는다.

12. 설계 전제

  • public edge는 둘이다. 어느 경계도 "내부니까 괜찮다"를 근거로 쓰지 않는다. ai·speech도 애플리케이션 인증을 갖는다.
  • 오디오는 server를 지난다. egress와 릴레이 부하를 지는 대신 전사 출처가 서버에 남고 STT 크레덴셜이 밖으로 안 나간다.
  • ai는 무상태다. 회의 세션 상태를 ai 프로세스에 두지 않는다. 30초 틱은 server가 돌고, 그래프 조회는 한 호출에 한 번으로 끝낸다.
  • 정본은 Postgres. 그래프도 벡터도 지우고 다시 만든다. S3 오디오만 예외라서 7일만 산다. 기계 클라이언트에는 상한이 인증만큼 중요하다 — 정상 인증된 폭주를 인증으로는 못 막는다.
  • 끊김은 기본값이다. 배포 · Spot 회수 · AZ 장애가 같은 일을 한다. 재연결 인계가 없으면 나머지는 희망사항이다. 조용히 degrade 하는 경계에는 지표를 세운다(§11).

13. 미확인

무엇지금 아는 것확인해야 할 것
Soniox 세션 수명 · 재연결 규약ElevenLabs 3,600초 상한만 실측상한이 있는지, 무중단 롤오버가 되는지. §8이 여기서 정해진다
pyannote 처리 시간 대 오디오 길이없다§8의 계수. §10 첫 천장의 위치
인스턴스당 릴레이 스트림 수시간당 230MB 까지만sendBufferSize 4MB 재산정 시점 · 릴레이를 언제 떼는지
구독자당 미확인 상한1,000건 또는 8MB는 추정실제 카드·세그먼트 메시지 크기 분포. 상한이 낮으면 멀쩡한 구독자를 끊는다
⑥ 인증 방식 확정⑦은 토큰, ⑥은 없음대칭 토큰인지 mTLS 인지. ADR 자리이고 결정 주체가 아직 없다
⑫ PAT vs OAuth · 상한 수치없다. §8은 제안값뿐외부 에이전트 생태계가 무엇을 기대하는지. 실사용 분포 전에는 상한도 추측이다
①~④ 사람 edge 상한 수치축 다섯은 §3에서 정했다동시 WS 5 · 재분석 분당 1이 정상 사용을 막는지. 화면 여러 개를 여는 사용자가 흔하다