일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 |
- Spring Error
- redis
- IntelliJ
- docker
- JavaScript
- Jenkins
- 도커
- codedeploy error
- java bigdecimal
- codebuild
- aws cicd
- kubeflow
- chartjs
- Airflow
- aws
- Kafka
- chart.js
- bootstrap
- COALESCE
- node
- or some instances in your deployment group are experiencing problems.
- SQL
- PostgreSQL
- codedeploy
- Spring
- codepipeline
- AWS CI/CD 구축하기
- Flux
- VPN
- Python
- Today
- Total
Small Asteroid Blog
[Python] Gradio streaming, 로컬환경에서는 되고 왜 쿠버환경에서는 안 됐을까? 본문
gradio 에서 textbox 를 사용해 텍스트를 스트리밍 형태로 출력하고 싶었다.
로컬 환경에서 배포하면 스트리밍으로 출력이 되는데
동일한 코드를 쿠버 환경에 배포하면 스트리밍으로 출력이 되지 않고 한번에 출력 결과가 나왔다.
Gradio 5.x 버전 부터는 stream=True 의 옵션이 없어져서 해당 옵션을 주어도 스트리밍으로 출력되지 않았다.
streaming 동작 조건
1. 함수가 yield를 통해 데이터를 작은 단위로 반환해야 함
2. chunk된 결과가 클라이언트에게 바로 flush되어야 함
3. 웹 서버 (FastAPI, Uvicorn) 와 프록시 (Nginx, Ingress, LoadBalancer 등)가 chunked encoding을 제대로 지원해야 함
로컬환경에서는 왜 스트리밍처럼 보였을까?
로컬에서는 웹 서버와 클라이언트 사이의 거리가 짧고, Nginx나 Ingress 등의 중간 버퍼링 계층이 없기 때문에 flush 타이밍이 빠르다.
→ 따라서 yield로 나눠 보낸 데이터가 바로 렌더링되어 스트리밍처럼 보였다.
반면, 쿠버네티스 환경에서는 Ingress Controller나 Nginx가 기본적으로 버퍼링을 하기 때문에, 일정 크기 이상의 데이터가 쌓여야만 클라이언트로 전송된다.
→ 결국 스트리밍이 작동하지 않고, 모든 출력이 한 번에 몰려서 표시되는 문제가 발생했다.
진짜 스트리밍처럼 보이게 하려면, 코드를 수정해 yield로 데이터 chunk를 작게 나눠 반환하는 방법으로
해결 방법
Nginx Ingress 버퍼링 설정 비활성화
metadata:
annotations:
# ─────── SSE 필수 ───────
nginx.ingress.kubernetes.io/proxy-buffering: "off"
nginx.ingress.kubernetes.io/proxy-request-buffering: "off"
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" # 1시간
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
# ─────── HTTP/2 → 1.1 강제(브라우저 ↔ Ingress) ───────
nginx.ingress.kubernetes.io/use-http2: "false"
nginx.ingress.kubernetes.io/proxy-http-version: "1.1" # ngress-→ pod 구간을 HTTP/1.1
nginx.ingress.kubernetes.io/connection-proxy-header: "keep-alive"
Dockerfile 에서 stdout 버퍼링 비활성화
ENV PYTHONUNBUFFERED=1
yield 단위 + sleep 지연 추가
너무 큰 chunk 를 한번에 보내면 버퍼링이 발생할 수 있기 때문에 문자 단위로 yield 하면서 sleep 을 주어 브라우저에 렌더링 하도록 유도했다.
sleep 을 0.01 이라도 주지 않으면 스트리밍 출력이 안된다.
async def stream_textbox_output(...):
for char in content: # for문으로 content 를 나눠서 반환하는게 포인트
yield char
await asyncio.sleep(0.01)
Lesson Learned
단순한 기능 구현뿐만 아니라 인프라 계층이 UI/UX 계층에도 영향을 주는 것을 확인할 수 있었다.
스트리밍 UI 를 구현할 때는 end-to-end 흐름을 고려해서 작업해야겠다.
'백엔드 > Python' 카테고리의 다른 글
[Python] Python 버전 지정 방식의 차이점: "^3.xx" vs ">=3.xx" (0) | 2025.07.28 |
---|---|
[Python] 대세는 requirements.txt 대신 uv + pyproject.toml (0) | 2025.07.17 |
[python]가상환경에서 모듈 에러 ModuleNotFoundError (0) | 2025.06.05 |
python DotMap (0) | 2025.05.22 |
[Python] OpenAI 결과를 고정된 JSON 포맷(Structured Output)으로 받는 방법 (0) | 2025.03.21 |