목차 열기
06. 스웜 인프라
에이전트 여러 개를 동시에 돌리는 것 자체는 어렵지 않습니다. 하지만 서로 덜 충돌하면서도 서로의 성공을 활용하려면 단순 병렬 실행 이상의 구조가 필요합니다. 이를 위해 관측 · 결정 · 반영 세 가지 역할을 명시적으로 분리했습니다.
3단 역할 분리 (발행–구독 패턴)
| 역할 | 담당 | 특징 |
|---|---|---|
| 관측 | 이벤트 버스 | 구독자별 큐 분리, 가득 차면 오래된 이벤트부터 버림, 블로킹 없는 발행 |
| 결정 | LLM 코디네이터 | 이벤트를 버퍼에 모으고 조건 맞을 때만 LLM 호출 |
| 반영 | 공유 전략 저장소 | 읽기는 잠금 없이, 쓰기는 잠금 사용 |
핵심 이점
에이전트는 LLM 응답을 기다리지 않습니다. 이벤트 발행 후 즉시 다음 동작을 계속하고, LLM은 백그라운드에서 처리됩니다. 에이전트가 결정이 필요한 순간에는 최신 전략을 그냥 읽기만 하면 됩니다.
이벤트 버스
구조
[에이전트 1~N]
↓ publish(event)
[이벤트 버스]
├─→ [구독자 A 큐] → 코디네이터
├─→ [구독자 B 큐] → 모니터
└─→ [구독자 C 큐] → 감사 로거
특성
| 특성 | 내용 |
|---|---|
| 패턴 | 발행-구독 (Pub/Sub) |
| 구독자 큐 | 각 구독자별로 독립된 큐 보유 |
| 드롭 정책 | 큐 가득 참 시 가장 오래된 이벤트부터 제거 |
| 발행 특성 | 블로킹 없음 — 에이전트는 발행 후 즉시 다음 동작 계속 |
| 상관관계 방지 | 한 구독자의 느린 처리가 다른 구독자에 영향 없음 |
이벤트 구조
| 필드 | 타입 | 설명 |
|---|---|---|
ts_ms | integer | 이벤트 발생 시각 (Unix ms) |
event | string | 이벤트 종류 |
flow_state | string | 당시 FlowState |
agent_id | integer | 발행 에이전트 순번 |
session_id | string | 세션 식별자 |
| 그 외 | object | 이벤트별 추가 정보 |
감사 로그 동시 기록
이벤트 버스로 발행되는 순간 구조화된 로그 파일에도 동시에 기록됩니다. 발표-구독 오버헤드와 영구 기록을 분리된 경로로 처리합니다.
공유 전략 저장소
에이전트가 결정 지점에서 최신 전략을 읽는 in-memory 저장소.
비대칭 동기화 — 잠금 없는 읽기 / 잠금 쓰기
| 연산 | 빈도 | 동기화 방식 | 지연 |
|---|---|---|---|
| 전략 읽기 | 매우 잦음 (매 결정 지점) | 잠금 없음 | 마이크로초 |
| 전략 쓰기 | 드뭄 (LLM 응답 수신 시) | 잠금 사용 | 수 ms |
왜 비대칭인가
| 이유 | 설명 |
|---|---|
| 읽기 잠금의 비용 | 매 결정 지점마다 잠금 경합 시 ms 단위 지연 누적 → 결정 지점이 느려짐 |
| 쓰기 원자성의 필요 | LLM 응답 처리 시 여러 필드를 한 번에 원자적으로 업데이트해야 함 |
| 메모리 일관성 완화 | 약간 오래된 전략을 잠깐 읽어도 무방 (LLM 응답 주기가 어차피 수초) |
설계 원칙: "완벽한 동기화보다 빠른 반응 속도"
저장 항목
| 필드 | 의미 | 변경 주체 |
|---|---|---|
| zone | 에이전트별 목표 구역 | 코디네이터 |
| party_size | 에이전트별 예매 인원 | 코디네이터 |
| seat_order | 좌석 선택 순서 (앞열·좌/우 우선 등) | 코디네이터 |
| zone_blacklist | 차단된 구역 목록 | 코디네이터 |
| excluded_seats | 다른 에이전트가 이미 확보한 좌석 | 좌석 확보 이벤트 (결정론) |
| vqa_gate | 동시 챌린지 풀이 제한 세마포어 | 런처 초기화 |
에이전트의 "결정 지점"
에이전트는 다음 시점에 공유 전략 저장소를 읽습니다.
| 지점 | 읽는 값 |
|---|---|
| S1 Pre-entry | party_size (드롭다운 사전 설정) |
| S4 Section 진입 | zone, party_size |
| S4R Recommend 진입 | party_size |
| S5 Seat 선택 | excluded_seats, seat_order, party_size |
| S5R Accept | party_size |
런처
스웜 전체의 생명주기를 관리합니다.
주요 책임
| 책임 | 내용 |
|---|---|
| 스웜 초기화 | 구성 파일 로드, 에이전트·코디네이터·모니터 객체 생성 |
| 이벤트 버스·전략 저장소 생성 | 스웜 공유 자원 준비 |
| 에이전트 병렬 시작 | 에이전트 N개를 비동기로 동시 실행 |
| 시간차 투입 | 에이전트 순번별 start_delay_ms 주입 |
| 백그라운드 태스크 관리 | 코디네이터·모니터 수명 관리 |
| 종료 처리 | 모든 에이전트 terminal 진입 후 요약 파일 생성 |
스웜 실행 순서
1. 구성 파일 로드
2. 이벤트 버스 생성
3. 공유 전략 저장소 생성
4. 코디네이터 생성 (비활성 플래그 없을 때)
5. 모니터 생성 (비활성 플래그 없을 때)
6. 감사 로거 구독자 연결
7. 코디네이터·모니터 백그라운드 태스크 시작
8. 에이전트 N개 비동기 병렬 시작
├─ agent 0: 즉시
├─ agent 1: start_delay_ms × 1 뒤
├─ ...
└─ agent N-1: start_delay_ms × (N-1) 뒤
9. 모든 에이전트 terminal 대기
10. 자동 평가 → 요약 파일 저장
11. 종료
좌석 확보 성공 이벤트의 특수 처리
좌석 확보는 LLM 판단 없이 결정론 사이드 이펙트가 즉시 실행되는 유일한 경우입니다.
[에이전트 A] 좌석 확보 성공 (PAYMENT_PAGE_REACHED)
↓ 이벤트 버스로 발행
[코디네이터 수신]
↓
[1] 결정론 사이드 이펙트 즉시 실행 (LLM 호출 전)
→ 확보된 좌석 라벨을 다른 모든 에이전트의 excluded_seats에 추가
↓
[2] LLM 호출 (구역 블랙리스트 갱신 등 넓은 판단)
왜 LLM 전에 먼저 처리하나
- LLM 응답을 기다리는 1~수 초 동안 다른 에이전트가 같은 좌석을 시도하면 충돌(409) 발생
- "LLM이 판단할 만한 것"과 "결정론으로 즉시 처리할 것"을 분리
- 지연을 최소화해 이선좌 사고 방지
스웜 내 동시 챌린지 풀이 제한
문제
| 상황 | 위험 |
|---|---|
| 한 스웜 안의 여러 에이전트가 같은 시간대에 챌린지를 풀면 | "비정상적으로 동기화된 풀이 패턴"으로 보일 수 있음 |
해결 — 세마포어 기반 직렬화
| 매개변수 | 기본값 | 의미 |
|---|---|---|
vqa-concurrency | 1 | 스웜 내 동시 챌린지 풀이 가능 에이전트 수 |
vqa-start-jitter-ms | 0 | 에이전트별 시작 시각 결정론적 jitter |
동시 풀이 수가 에이전트 수를 초과하지 않도록 런타임에서 자동 보정됩니다.
참조
- 05-vqa-solver — 챌린지 풀이 세부
- 07-llm-coordinator — LLM 코디네이터 동작
- 11-events-reference — 이벤트 종류 전체