[Open WebUI] 채팅 메시지 마이그레이션을 스트리밍+배치 처리로 전환하여 메모리 폭발 방지
PR 링크: open-webui/open-webui#21542 상태: Merged | 변경: +78 / -38
들어가며
Open WebUI의 채팅 메시지 테이블 마이그레이션(8452d01d26d7)은 기존 채팅 데이터를 새 chat_message 테이블로 백필하는 작업을 수행합니다. 기존 구현은 .fetchall()로 모든 채팅 행을 메모리에 로드한 후, 각 메시지마다 개별 INSERT + SAVEPOINT를 실행했습니다. 대규모 인스턴스에서는 수백만 행이 메모리에 올라가고, 메시지당 SAVEPOINT 오버헤드가 누적되어 마이그레이션이 극도로 느려졌습니다.
핵심 코드 분석
Before: 전체 로드 + 건별 INSERT
# 모든 채팅을 메모리에 로드
chats = conn.execute(
sa.select(chat_table.c.id, chat_table.c.user_id, chat_table.c.chat)
.where(~chat_table.c.user_id.like("shared-%"))
).fetchall()
for chat_row in chats:
for message in messages:
savepoint = conn.begin_nested() # 메시지마다 SAVEPOINT
try:
conn.execute(sa.insert(chat_message_table).values(...))
savepoint.commit()
except Exception:
savepoint.rollback()
After: 스트리밍 + 배치 INSERT
BATCH_SIZE = 5000
# 서버 사이드 커서로 스트리밍
result = conn.execute(
sa.select(...)
.execution_options(yield_per=1000, stream_results=True)
)
messages_batch = []
for chat_row in result:
for message in messages:
messages_batch.append({...})
if len(messages_batch) >= BATCH_SIZE:
inserted, failed = _flush_batch(conn, chat_message_table, messages_batch)
messages_batch.clear()
def _flush_batch(conn, table, batch):
"""벌크 INSERT 시도, 실패 시 건별 fallback"""
savepoint = conn.begin_nested()
try:
conn.execute(sa.insert(table), batch) # 벌크 INSERT
savepoint.commit()
return len(batch), 0
except Exception:
savepoint.rollback()
# 건별 fallback으로 문제 행만 건너뜀
for msg in batch:
sp = conn.begin_nested()
try:
conn.execute(sa.insert(table).values(**msg))
sp.commit()
except Exception:
sp.rollback()
왜 이게 좋은가
- 메모리 효율:
yield_per=1000과stream_results=True로 서버 사이드 커서를 사용하여 전체 데이터를 메모리에 올리지 않는다. - SAVEPOINT 오버헤드 감소: 5000건 단위 벌크 INSERT로 SAVEPOINT 횟수를 1/5000로 줄인다.
- graceful fallback: 벌크 INSERT 실패 시 건별 fallback으로 문제 행만 건너뛰어 마이그레이션 전체가 중단되지 않는다.
참고 자료
관련 포스트
PR Analysis 의 다른글
- 이전글 [vllm] NGram GPU 구현 - 비동기 스케줄러 호환 GPU 기반 N-gram Drafting
- 현재글 : [Open WebUI] 채팅 메시지 마이그레이션을 스트리밍+배치 처리로 전환하여 메모리 폭발 방지
- 다음글 [Grafana Loki] Thor(V2) 쿼리 엔진에 결과 캐시 미들웨어 추가
댓글