Files
dap-was-dapms/docs/discussions/DISC-20260827-001-request-driven-ttl-refresh.md
janghw ec517e88ec
All checks were successful
Deploy Gateway / deploy (push) Successful in 2m38s
initialize 시 routing hint 직렬화 방식변경
2026-09-16 14:14:44 +09:00

254 lines
9.4 KiB
Markdown

# DISC-20260827-001 요청 기반 TTL Refresh 설계 메모
- 상태: Open
- 논의 기준일: 2026-08-27
- 보안 등급: 내부용
- 적용 여부: 적용, scheduler polling 제거 후 요청 시점 TTL refresh 사용
이 문서는 MCP가 포털 registry와 Tool Server manifest를 주기적으로 polling하지 않고, Agent 요청 시점에 필요한 경우만 갱신하는 방안을 보존한다. 현재 구현은 이 방향을 적용해 기동 preload 이후 scheduler polling을 수행하지 않는다.
## 이름
요청 기반 TTL Refresh(Request-driven TTL Refresh)
## 배경
현재 MCP는 서버 기동 시 포털과 Tool Server를 조회하고, 이후 scheduler를 통해 포털 registry와 Tool Server manifest를 주기적으로 다시 호출한다. 이 방식은 단순하지만 요청이 없어도 외부 호출이 계속 발생한다.
검토 중인 대안은 scheduler와 배치를 사용하지 않고, Agent가 `tools/list` 또는 `tools/call`을 요청할 때 마지막 확인 시각을 기준으로 TTL이 만료되었는지 판단한 뒤 필요한 외부 API만 다시 호출하는 방식이다.
## 목표
1. 포털 registry와 Tool Server manifest의 주기 scheduler 호출을 제거하거나 비활성화한다.
2. 요청이 없는 route에 대해서는 외부 호출을 발생시키지 않는다.
3. TTL 안에서는 기존 in-memory snapshot을 즉시 사용한다.
4. TTL이 지난 첫 요청에서 포털과 Tool Server 원천을 best-effort로 재확인한다.
5. refresh 실패 시 기존 snapshot을 유지해 요청 경로의 안정성을 보존한다.
## 비목표
1. Redis Stream이나 Pub/Sub 기반 실시간 이벤트 동기화는 이 문서의 직접 구현 대상이 아니다.
2. 포털이나 Tool Server에 revision 전용 API를 새로 요구하지 않는다.
3. Agent가 Tool을 선택하는 방식이나 Tool Service의 업무 권한 처리는 변경하지 않는다.
4. MCP가 Tool을 자동 선택하거나 대체 Tool을 추천하지 않는다.
## 기본 아이디어
서버 기동 시에는 현재와 같이 최초 원천 조회를 수행한다.
```text
MCP 기동
-> 포털 registry API 호출
-> routeKey별 Tool Server endpoint 목록 확보
-> Tool Server manifest API 호출
-> routeKey별 Tool 목록과 실행 endpoint 확보
-> in-memory snapshot 저장
-> 마지막 확인 시각 저장
```
이후에는 scheduler가 아니라 Agent 요청이 들어왔을 때 TTL을 확인한다.
```text
Agent 요청: POST /mcp/{routeKey}
-> routeKey 검증
-> 포털 registry 마지막 확인 시각 확인
-> Tool Server manifest 마지막 확인 시각 확인
-> TTL 미만이면 기존 memory snapshot 사용
-> TTL 초과이면 기존 API를 다시 호출해 revision 비교
-> 변경 있으면 snapshot 교체
-> 변경 없으면 마지막 확인 시각만 갱신
-> 요청 처리 계속 진행
```
## 포털 처리 방식
포털에는 revision 전용 API가 없다고 가정한다. 따라서 서버 기동 시 호출하던 포털 registry API를 TTL 만료 시 다시 호출하고, 응답 body 안의 registry revision을 비교한다.
```text
포털 TTL 만료
-> 기존 포털 registry API 호출
-> registryRevision 비교
-> 같으면 endpoint snapshot 유지, lastCheckedAt 갱신
-> 다르면 endpoint snapshot 교체
-> 포털 변경이 있었던 route는 Tool Server manifest 강제 재조회 대상이 됨
```
포털 API 호출 실패 시 정책은 다음과 같다.
```text
기존 endpoint snapshot 있음
-> WARN 로그만 남김
-> 기존 endpoint snapshot 유지
기존 endpoint snapshot 없음
-> Redis portal registry fallback 시도
-> Redis도 없으면 registry unavailable
```
## Tool Server 처리 방식
Tool Server에도 revision 전용 API가 없다고 가정한다. 따라서 서버 기동 시 호출하던 Tool Server manifest API를 TTL 만료 시 다시 호출하고, 응답 body 안의 manifest revision 또는 그에 준하는 값을 비교한다.
```text
Tool manifest TTL 만료
-> 기존 Tool Server manifest API 호출
-> manifest revision 비교
-> 같으면 Tool snapshot 유지, lastCheckedAt 갱신
-> 다르면 Tool snapshot 교체
```
Tool Server manifest 호출 실패 시 정책은 다음과 같다.
```text
기존 Tool snapshot 있음
-> WARN 로그만 남김
-> 기존 Tool snapshot 유지
기존 Tool snapshot 없음
-> Redis route별 Tool snapshot fallback 시도
-> Redis도 없으면 registry unavailable
```
## 포털 변경과 Tool manifest 변경의 관계
포털과 Tool Server는 서로 다른 원천을 가진다.
```text
포털
-> routeKey별 Tool Server serviceDomain, manifestPath, serviceKey 관리
Tool Server manifest
-> Tool name, title, description, inputSchema, 실행 endpoint, manifest revision 관리
```
포털 revision이 바뀌면 Tool Server endpoint나 manifestPath가 바뀌었을 수 있으므로, Tool manifest TTL이 아직 남아 있어도 영향 route의 manifest는 다시 조회하는 것이 안전하다.
반대로 포털 revision이 같아도 Tool Server 내부 Tool 목록은 바뀔 수 있으므로, Tool manifest TTL이 만료되면 manifest API는 별도로 다시 호출해야 한다.
## 요청 처리 정책
`tools/list``tools/call` 모두 실제 처리 전에 같은 fresh check를 수행하는 방향을 검토한다.
```text
tools/list
-> ensureFreshIfExpired(routeKey)
-> listTools(routeKey)
-> Agent에 Tool 목록 반환
tools/call
-> ensureFreshIfExpired(routeKey)
-> findEnabledTool(routeKey, toolName)
-> inputSchema 검증
-> Tool Server 호출
```
이렇게 해야 오래된 Tool 목록과 오래된 실행 endpoint를 동시에 줄일 수 있다.
## 동시 요청 처리
TTL이 지난 시점에 같은 route로 요청이 여러 개 몰리면 외부 API 중복 호출이 발생할 수 있다. route별 single-flight를 적용해 첫 요청 하나만 refresh를 수행하고 나머지 요청은 같은 결과를 기다리거나 기존 snapshot을 사용하도록 정책을 정해야 한다.
우선 검토안은 다음과 같다.
```text
같은 route refresh 진행 중
-> 다른 요청은 진행 중인 refresh 결과를 기다림
-> refresh timeout은 짧게 유지
-> 실패하면 기존 snapshot으로 진행
```
## 실패 처리 원칙
TTL refresh는 최신화를 위한 보조 동작이지, 기존 정상 snapshot을 비우는 동작이 아니다.
```text
원천 조회 성공
-> revision 비교
-> 변경 시 memory snapshot 교체
-> Redis 저장은 best-effort
원천 조회 실패 + 기존 memory 있음
-> 기존 memory 유지
-> 요청 처리 계속
원천 조회 실패 + 기존 memory 없음
-> Redis fallback 시도
-> Redis도 실패하면 registry unavailable
```
## stale Tool 보정과의 관계
이미 검토한 404/410 stale Tool refresh는 유지한다.
```text
Tool call 중 upstream 404 또는 410 발생
-> 삭제되었거나 더 이상 제공되지 않는 Tool일 수 있음
-> TTL과 무관하게 해당 route manifest refresh 시도
-> 현재 요청은 Tool 실행 실패로 응답
-> 다음 요청부터 최신 snapshot 사용 가능
```
이 기능은 삭제된 Tool 호출에 대한 즉시 보정이고, 요청 기반 TTL Refresh는 추가·수정·endpoint 변경까지 포함한 일반 갱신 정책이다.
## 장점
1. 요청이 없는 동안 포털과 Tool Server 호출이 발생하지 않는다.
2. scheduler thread와 주기 설정 부담이 줄어든다.
3. route별로 실제 사용되는 대상만 갱신할 수 있다.
4. 기존 in-memory snapshot, Redis fallback, single-flight 구조를 재사용할 수 있다.
5. Redis Stream 이벤트 기반 구조로 가기 전 중간 단계로 적용하기 쉽다.
## 단점과 주의점
1. TTL이 지난 뒤 첫 Agent 요청은 외부 API 확인 때문에 느려질 수 있다.
2. 변경 반영은 즉시가 아니라 최대 TTL만큼 지연될 수 있다.
3. revision 전용 API가 없으므로 변경 확인만 하려 해도 기존 API 전체 body를 다시 받아야 한다.
4. 같은 route 요청이 동시에 들어올 때 중복 refresh 방어가 필요하다.
5. scheduler가 사라지면 요청이 전혀 없는 route는 갱신되지 않는다.
## 설정 후보
```yaml
mcp:
registry:
refresh-mode: request-ttl
refresh-ttl-seconds: 300
refresh-on-startup: true
portal:
refresh-ttl-seconds: 300
discovery:
manifest-refresh-ttl-seconds: 300
```
현재 구현 설정 이름은 `refresh-ttl-seconds`다. scheduler 모드는 유지하지 않는다.
## 구현 후보
후보 메서드 이름은 다음과 같다.
```java
registryService.ensureFreshIfExpired(routeKey);
```
예상 호출 위치는 다음과 같다.
```text
ToolsListHandler.handle()
ToolExecutionService.execute()
```
기존 scheduler는 제거하고 기동 preload만 남긴다.
## 확정 전 확인할 질문
1. TTL 기본값은 5분으로 할지, 운영에서 별도 값으로 둘지.
2. TTL 만료 시 refresh를 요청이 기다릴지, 기존 snapshot으로 먼저 응답하고 비동기 refresh할지.
3. 포털 registry revision 필드명과 Tool Server manifest revision 필드명이 무엇인지.
4. 포털 revision 변경 시 모든 route를 다시 볼지, 변경된 route만 볼 수 있는지.
5. scheduler 모드는 제거하는 것으로 확정했다.
6. Redis 도입 후 이 방식은 fallback으로 남길지, 이벤트 기반으로 대체할지.
## 현재 판단
이 방식은 Redis Stream 이벤트 기반 구조가 도입되기 전까지 외부 호출량을 줄이고 route별 최신성을 일정 수준 유지하는 중간 구현이다.