본문으로 건너뛰기

[Gradio] 큐 성능 개선 — MCP 응답 속도 향상을 위한 구조 리팩터링

PR 링크: gradio-app/gradio#12296 상태: Merged | 변경: +428 / -365

들어가며

Gradio의 MCP(Model Context Protocol) 통합에서 도구 호출 시 발생하는 여러 성능 병목을 해소하기 위한 대규모 리팩터링이다. 기존 call_tool 메서드는 단일 함수에 모든 로직(인자 준비, HTTP 클라이언트 생성, progress 처리, 결과 파싱)이 뒤섞여 있었고, 클라이언트 초기화에서 analytics와 SSL 검증 같은 불필요한 오버헤드가 있었다.

핵심 코드 분석

1. 클라이언트 초기화 최적화

MCP 서버가 내부 루프백으로 자기 자신을 호출하는 구조에서, analytics 전송과 SSL 인증서 검증은 순수한 낭비다.

Before:

self._client_instance = Client(
    self.local_url or root_url, download_files=False, verbose=False
)

After:

self._client_instance = Client(
    self.local_url or root_url,
    download_files=False,
    verbose=False,
    analytics_enabled=False,
    ssl_verify=False,
)

analytics_enabled=False로 HuggingFace analytics 전송을 비활성화하고, ssl_verify=False로 localhost 호출 시 불필요한 인증서 검증을 건너뛴다.

2. 단일 함수를 역할별 메서드로 분리

기존에 100줄이 넘는 call_tool 메서드를 4개의 독립 메서드로 분리했다.

Before (단일 함수):

async def call_tool(self, name, arguments):
    # 인자 준비 (30줄)
    selected_tools = self.get_selected_tools_from_request()
    _, filedata_positions = self.get_input_schema(name)
    processed_kwargs = self.convert_strings_to_filedata(arguments, filedata_positions)
    endpoint_name = self.tool_to_endpoint.get(name)
    # ... 검증 로직 ...
    # HTTP 호출 + progress 처리 + 에러 처리 (70줄)
    step = 0
    output = {"data": []}
    job = client.submit(...)
    async for update in job:
        if update.type == "status" and progress_token is not None:
            # progress 포맷팅 인라인 (30줄)
            if update.code in [Status.JOINING_QUEUE, Status.STARTING]:
                message = "Joined server queue."
            # ...

After (분리된 메서드):

def _prepare_tool_call_args(self, name, arguments):
    """인자 준비 및 검증"""
    ...

async def _execute_tool_without_progress(self, job):
    """progress 없는 fast path"""
    result = await run_sync(job.result)
    return [result]

@staticmethod
def _format_progress_message(update: StatusUpdate) -> str | None:
    """상태 업데이트 포맷팅"""
    ...

async def _execute_tool_with_progress(self, job, progress_token):
    """progress 포함 streaming path"""
    ...

3. State 처리 메서드 시그니처 수정

pop_returned_state에서 파라미터명이 inputs에서 components로 변경되고, outputs에 대해서도 올바르게 동작하도록 수정되었다.

Before:

@staticmethod
def pop_returned_state(
    inputs: Sequence["Component | BlockContext"], data: list
) -> list:
    for i, input_component_type in enumerate(inputs):
        if isinstance(input_component_type, State):
            data.pop(i)
    return data

After:

@staticmethod
def pop_returned_state(
    components: Sequence["Component | BlockContext"], data: Any
) -> list:
    for i, component_type in enumerate(components):
        if isinstance(component_type, State):
            data.pop(i)
    return data

왜 이게 좋은가

  • 클라이언트 오버헤드 제거: analytics + SSL 비활성화로 초기 연결 시간 단축
  • 코드 가독성 향상: 100줄 단일 함수를 역할별 4개 메서드로 분리하여 테스트와 유지보수가 용이해짐
  • progress 분기 최적화: queued가 아닌 이벤트에서 불필요한 progress 처리를 건너뛰는 fast path 도입
  • 후속 최적화 기반: 이 리팩터링이 #12961(direct call 최적화)의 토대가 됨

정리

이 PR은 단순 성능 패치가 아니라, MCP 호출 경로 전체를 구조적으로 정리한 리팩터링이다. 변경량(+428/-365)이 크지만 대부분은 기존 인라인 코드를 메서드로 추출한 것이며, 실질적인 동작 변경은 클라이언트 초기화 옵션 추가와 실행 경로 분리 두 가지다. 후속 PR(#12961)에서 direct call을 도입할 수 있었던 것도 이 구조 개선 덕분이다.

참고 자료

⚠️ 알림: 이 분석은 AI가 실제 코드 diff를 기반으로 작성했습니다.

댓글

관련 포스트

PR Analysis 의 다른글