Skip to content

Qdrant에서 disk에 리뷰 17273개 업로드 시 CPU 500% 스레드 32개 사용

OhJin-Soo edited this page Feb 4, 2026 · 3 revisions

Q. Qdrant에서 disk에 리뷰 17273개 올리는데 CPU 500%, 스레드 32개 사용. 왜지?

A.

스레드 32개 + CPU 500%


왜 “Qdrant disk 업로드”가 Python CPU 500%, 스레드 32개가 되나?

핵심

disk는 ‘저장 위치’일 뿐이고, 업로드 과정에서 보통 아래 작업들이 같이 일어나:

  1. 임베딩 생성(가장 흔한 원인)
  2. 배치/병렬 업서트(클라이언트 단에서 동시 요청)
  3. 토크나이저/BLAS/OpenMP 같은 네이티브 라이브러리의 멀티스레딩
  4. (별개로) Qdrant 서버 쪽에서도 인덱스(HNSW) 구축으로 CPU 사용

그런데 너는 “파이썬 프로세스가 CPU 500%”라고 했지? → 보통 1) 또는 2) + 3) 조합이야.


1) 스레드 32개는 “기본 ThreadPoolExecutor” 숫자일 확률이 높음

파이썬에서 이런 코드 패턴 있지 않아?

  • asyncio.to_thread(...)
  • loop.run_in_executor(None, ...)
  • concurrent.futures.ThreadPoolExecutor()를 명시 안 하고 기본값 사용
  • 혹은 내부 라이브러리가 threadpool을 씀

이때 기본 스레드 수가 보통:

min(32, (os.cpu_count() or 1) + 4)

이라서 딱 32개가 자주 나온다.

즉, 너의 파이프라인이 **“업로드를 비동기/병렬로 밀어넣는 구조”**면 클라이언트가 스레드를 32개까지 만들고, 그중 일부가 계속 CPU를 쓸 수 있어.


2) CPU 500%는 “파이썬이 CPU 5코어를 동시에 쓰는 중”이라는 뜻

500% = 5코어 풀가동. 파이썬은 GIL 때문에 “원래” CPU 병렬이 안 된다고들 하지만…

GIL이 있어도 병렬이 되는 대표 케이스

  • 토크나이저(HuggingFace tokenizers): Rust/네이티브로 GIL 해제 + 내부 병렬
  • NumPy/BLAS(MKL/OpenBLAS): 내부적으로 스레드 사용
  • PyTorch CPU 연산: intra-op / inter-op thread 사용
  • 압축/해시/JSON 등 네이티브 확장: GIL 해제 가능

그래서 “파이썬 프로세스가 멀티코어를 쓰는 것처럼” 보일 수 있어.


3) “리뷰 17,373개”면 업로드 자체보다 임베딩 생성이 더 무겁기 쉬움

Qdrant에 올릴 때 보통 흐름이:

  1. 리뷰 텍스트 전처리
  2. 임베딩 모델로 벡터화
  3. 배치로 upsert

여기서 CPU를 가장 많이 태우는 건 대개 2)야. 특히:

  • CPU 임베딩(또는 토크나이징이 병목)
  • 배치가 크고 동시성이 크면 토크나이저가 스레드를 많이 씀

4) “disk”라도 인덱스 구축/세그먼트 처리 때문에 CPU를 쓴다

이건 Qdrant 서버 프로세스 쪽 CPU가 오르는 이유고, 네가 본 “파이썬 CPU 500%”랑은 별개일 수 있지만 같이 발생할 수 있어.

  • 업서트가 들어오면 Qdrant는 세그먼트/인덱스 업데이트를 함
  • 특히 HNSW 인덱싱(설정에 따라) CPU 사용이 큼

바로 확인할 수 있는 체크리스트

A) 정말 임베딩 생성이 CPU를 먹는지?

업서트 직전/직후에 로그로 시간 찍어봐:

  • “임베딩 생성 시간”
  • “Qdrant upsert 시간”

임베딩이 대부분이면 원인 확정.

B) 스레드 32개가 어디서 생겼는지?

  • ps -o nlwp <pid> 로 스레드 수 확인
  • htop에서 Threads 보기(스레드 이름이 보이면 더 좋음)
  • 파이썬 코드에 to_thread / run_in_executor / threadpool 사용 흔적 찾기

해결/완화 방법 (바로 적용 가능한 것들)

1) “스레드 32”가 asyncio threadpool이라면: 스레드 수 제한

import asyncio
from concurrent.futures import ThreadPoolExecutor

executor = ThreadPoolExecutor(max_workers=8)
loop = asyncio.get_event_loop()
loop.set_default_executor(executor)

또는 to_thread를 남발하지 말고, 동시 작업 수를 Semaphore로 제한.

2) 토크나이저 병렬이 과하면 끄기

환경변수:

export TOKENIZERS_PARALLELISM=false

3) MKL/OpenBLAS/OpenMP 스레드 제한

export OMP_NUM_THREADS=4
export MKL_NUM_THREADS=4
export OPENBLAS_NUM_THREADS=4

(이거 하나로 CPU 폭주가 깔끔히 잡히는 경우 많아.)

4) Qdrant 업서트 동시성/배치 크기 조절

  • batch size를 너무 크게 잡으면 클라이언트/서버 둘 다 부하
  • 동시 업서트 요청을 2~8 정도로 제한해도 총 처리시간이 오히려 좋아질 때가 많음(큐 폭주 방지)

네 상황에서 “가장 그럴듯한 1순위 원인”

기본 ThreadPoolExecutor(최대 32) + 임베딩/토크나이저/BLAS 멀티스레딩 조합.

  • 스레드가 딱 32인 게 너무 강한 힌트야.

원하면, 네가 “업로드 코드(embedding 생성 + upsert 부분)”를 그대로 붙여줘. 그러면 어느 라인이 스레드/CPU를 만드는지동시성/배치 파라미터를 어디까지 줄이면 좋은지를 딱 짚어서 수정안을 바로 써줄게.


Q. 정상인가?

A.

지금 정보만 놓고 보면 “이상 현상”이라기보단, 흔히 나오는 정상 범주일 가능성이 큽니다. 다만 언제는 괜찮고, 언제는 문제인지 기준이 딱 있어요. 그 기준으로 바로 판정해볼게요.


괜찮은 경우

아래 중 2~3개 이상이면 정상/의도된 부하로 보는 게 맞아요.

  • 업로드(임베딩 생성 + upsert)가 진행되는 동안에만 CPU 500%가 올라갔다가 끝나면 떨어진다
  • 전체 처리 시간이 예상대로 줄거나(또는 합리적), CPU를 더 쓰는 만큼 속도가 나왔다
  • 머신이 8코어 이상이고, 500% = 5코어 사용이라서 “풀로드”가 아니다
  • 메모리 스왑이 없고(메모리 여유), 시스템이 끊기지 않는다
  • Qdrant도 정상 응답하고, 오류/타임아웃이 없다

👉 이 경우는 “리소스를 써서 일을 빨리 끝내는 배치 작업”이라 괜찮은 설계야.


문제일 수 있는 경우

아래 중 하나라도 해당하면 튜닝/제어가 필요해요.

  • 업로드가 끝났는데도 CPU 500%가 계속 유지
  • 요청 수가 많지 않은데도 CPU가 지속적으로 고정
  • CPU만 올라가고 처리 속도는 안 나옴 (throughput/latency 개선 없음)
  • 다른 서비스(서버, DB, 모니터링)가 같이 돌고 있는데 응답이 느려지거나 타임아웃
  • 스레드 32가 매번 생기면서 시스템 전체가 불안정 (context switch 폭증, load average 급등)
  • 스왑 발생, iowait 상승, 디스크 대기 증가(특히 on-disk에서)

👉 이건 보통

  • 스레드풀 과다
  • 토크나이저/BLAS/OpenMP 스레딩 중첩(“스레드 안에 스레드”)
  • 업서트 동시성 과다 중 하나야.

지금 네 케이스를 “가장 현실적으로” 판정하면

리뷰 17,373개 업로드는 배치 작업이고, CPU 500% + 스레드 32는 “기본 스레드풀 + 네이티브 병렬화” 패턴이라서

  • 배치 중에만 발생했고
  • 서비스를 안 망가뜨렸고
  • 업로드가 정상 종료됐다면

➡️ 괜찮은 상태(정상 범주) 로 보는 게 맞아.


바로 결론을 내리는 3가지 체크 (너가 지금 당장 볼 수 있는 것)

  1. 업로드 끝난 뒤 CPU가 내려갔나?

    • 내려갔다 ✅ → 정상
    • 안 내려간다 ❌ → 문제 가능성 큼
  2. 처리 시간이 “CPU를 쓴 만큼” 줄었나?

    • 줄었다 ✅ → 정상 최적화
    • 안 줄었다 ❌ → 스레드 폭주/병목 재조정 필요
  3. iowait / swap이 발생했나?

    • 없다 ✅ → 괜찮음
    • 있다 ❌ → on-disk/배치/동시성 튜닝 필요

“문제였다면” 가장 값싼 해결책 2개

(코드 크게 안 바꾸고 잡히는 경우가 많음)

  1. 토크나이저 병렬 끄기
export TOKENIZERS_PARALLELISM=false
  1. 네이티브 라이브러리 스레드 제한
export OMP_NUM_THREADS=4
export MKL_NUM_THREADS=4
export OPENBLAS_NUM_THREADS=4

한 줄 요약

  • 배치 업로드 중에만 CPU 500%/스레드 32가 뜬 거면: 괜찮다(정상).
  • 업로드 끝나도 지속되거나, 속도 이득 없이 시스템만 흔들면: 문제다(튜닝 필요).

🛠️ Troubleshooting

LLM


Monitoring

Clone this wiki locally