태스크별 모델 전환을 동시성 프로토콜로 취급하기
요약
AI 태스크 실행 중 모델을 전환할 때 발생할 수 있는 동시성 문제를 분산 작업 관점에서 분석합니다. 요청 순서와 완료 순서가 일치하지 않아 발생하는 레이스 컨디션을 방지하기 위해 단조적 생성(monotonic generation) 방식을 통한 해결책을 제안합니다.
핵심 포인트
- 모델 전환은 단순 설정 변경이 아닌 분산 작업의 성격을 가짐
- 요청 순서와 완료 순서의 불일치로 인해 의도치 않은 모델이 적용될 수 있음
- MonkeyCode 사례를 통해 명시적인 동시성 제어 계약의 필요성 확인
- 단조적 생성(generation) 번호를 도입하여 최신 요청만 반영되도록 보장
실행 중인 AI 태스크의 모델을 변경하는 것은 단순한 설정 업데이트가 아닙니다. 이는 다음과 같은 분산 작업(distributed operation)입니다:
현재 태스크 읽기 -> 자격 증명/설정 준비 -> 재시작 요청 -> 결과 수신 -> 활성 모델 영속화
만약 두 번의 전환이 겹치면, 완료 순서가 요청 순서와 달라질 수 있습니다. 시스템에는 어떤 의도가 우선권을 가질지에 대한 규칙이 필요합니다.
구체적인 사례
c58bcd4 커밋에서, MonkeyCode는 TaskModelSwitch에 from/to 모델 ID, 요청 ID, load-session 플래그, 성공 여부, 메시지, 세션 ID 및 타임스탬프와 함께 모델 전환 시도를 기록합니다.
검토된 task use case는 전환 기록을 생성하고, taskflow에 대상 모델 구성으로 재시작할 것을 요청하며, 응답을 기반으로 전환 기록과 태스크 모델을 완료합니다. 동반된 테스트들은 성공 및 실패 경로를 모두 다룹니다.
이 소스 리뷰를 통해, 저는 명시적인 compare-and-swap 생성이나 중첩된 요청에 대한 태스크별 직렬화(serialization) 계약을 확인할 수 없었습니다. 이것이 악용 가능한 레이스(race) 조건이 존재한다는 것을 증명하는 것은 아닙니다. 직렬화가 배포 환경의 다른 곳이나 taskflow 경계에 존재할 수도 있기 때문입니다. 다만, 이는 동시성 의미론(concurrency semantics)에 대해 명시적인 테스트와 계약이 필요함을 의미합니다.
마지막 완료가 불안정한 이유
요청 A가 모델 A를 선택하고, 이어서 요청 B가 모델 B를 선택한다고 가정해 봅시다:
시간(time) ->
A: 요청 ---- 재시작 ---------------- 완료
B: 요청 -- 재시작 -- 완료
각각의 성공적인 완료가 자신의 모델을 기록한다면, B가 먼저 적용되고 나중에 A가 이를 덮어쓰게 됩니다. 네트워크 타이밍이 반대가 되면 결과도 바뀝니다.
함께 제공된 시뮬레이터는 이러한 순서 의존성(order dependence)을 가시적으로 보여줍니다:
export function naiveCompletionOrder(completions) {
let model = "initial";
for (const completion of completions) {
...
[A, B]는 B에서 종료됩니다. [B, A]는 A에서 종료됩니다. 호출자의 최신 의도(intent)는 규칙의 일부가 아닙니다.
단조적 생성(monotonic generation) 추가
각 요청을 수락할 때 생성(generation)을 할당합니다:
A -> generation 41
B -> generation 42
완료(Completion)는 해당 생성(generation)이 태스크의 현재 요청된 생성(generation)과 일치할 때만 활성 상태(active state)를 업데이트할 수 있습니다:
UPDATE tasks
SET active_model_id = :model,
applied_generation = :generation
...
업데이트된 행(rows)이 0개라는 것은 해당 작업이 대체(superseded)되었음을 의미합니다. 이력을 유지하되, 오래된 상태(stale state)를 적용하지 마십시오.
동반 아티팩트(companion artifact)에 포함된 최소한의 GenerationSwitch 클래스가 이 규칙을 구현합니다. 다음을 실행하세요:
node test-model-switch.mjs
예상 출력:
PASS naive result depends on completion order; generation guard preserves newest request
이 테스트는 의도적으로 요청 B를 A보다 먼저 완료하며, 늦게 도착한 A가 superseded(대체됨)로 표시되는 동안 모델 B가 활성 상태로 유지됨을 증명합니다.
생성(Generations)은 프로토콜의 일부일 뿐입니다
다음 사항들을 모두 정의하십시오:
| 고려 사항 | 계약 (Contract) |
|---|---|
| 중복 요청 (Duplicate request) | 동일한 요청 ID는 두 번째 재시작 없이 동일한 작업을 반환함 |
| ... |
직렬화(Serialization)는 유효한 대안입니다: 태스크별 잠금(lock)을 획득하거나 전환(switch)을 큐(queue)에 넣으십시오. 이는 중첩(overlap)을 단순화하지만, 임대 만료(lease expiry), 장애 복구(crash recovery), 공정성(fairness), 그리고 사용자에게 보이는 "전환 중(switch in progress)" 상태가 필요합니다. 생성 가드(generation guard)는 오래된 워커(stale workers)에 대한 방어 수단으로서 여전히 유용합니다.
제어된 인터리빙(interleavings)으로 검증
단위 테스트(Unit tests)는 자격 증명 생성(credential creation), 전환 기록 생성(switch-record creation), 재시작 디스패치(restart dispatch), 재시작 응답(restart response), 그리고 영속성(persistence) 단계에서 작업을 일시 중지해야 합니다. 모든 관련 순서에 따라 A와 B를 해제하십시오. 다음을 단언(Assert)합니다:
- 가장 최근에 수락된 의도(intent)가 유일하게 적용된 생성(generation)이어야 합니다.
- 모든 시도는 감사(auditable) 가능해야 합니다.
- 중복된 요청 ID(request ID)가 중복 재시작을 유발해서는 안 됩니다.
- 실패가 이전의 유효한 모델을 삭제할 수 없습니다.
- 런타임 재시작과 데이터베이스 업데이트 사이의 충돌(crash) 이후에도 조정(reconciliation)이 수렴해야 합니다.
불변량(invariant)은 간결합니다:
활성 모델(active model) == 대체되지 않은 가장 큰 생성(generation)에 대한 성공적인 결과
모델 전환을 프로토콜로 취급하면, UI 레이블, 감사 기록(audit records), 재시도(retries), 토큰, 그리고 지속성(persistence) 단계가 모두 무엇이 "현재"인지에 대해 일치할 수 있습니다.
공개 사항: 저는 MonkeyCode 프로젝트에 기여하고 있습니다. 현재의 필드와 흐름은 커밋
c58bcd4에 연결된 소스를 기반으로 합니다. 동시성 위험(concurrency risk)은 제한된 소스 검토 문제이며 확인된 취약점은 아닙니다. 생성/CAS 시뮬레이터는 로컬에서 테스트되었습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기