Skip to Content

잠드는 스레드

기다림은 어떻게 비용이 되는가

스레드 풀은 꽉 찼는데 CPU는 3%다. 아무도 일하지 않는데 서버는 죽어 있다 — 이 이상한 장면 뒤에는 “기다리는 동안 스레드를 어떻게 할 것인가”를 30년간 다르게 답해온 커널과 언어의 역사가 있다.

이 책은 @Async를 붙이고 CompletableFuture를 체이닝하면서도 “그래서 이게 왜 빨라지는 거지?”에 자신 있게 답하지 못하는 백엔드 개발자를 위해 썼다. 정답을 외우는 대신, 연결 1만 개에 서버가 무너지던 시절에서 출발해 — 커널이 스레드를 재우는 방식, 준비된 fd만 알려주는 epoll, 콜백으로 결과를 조립하는 CompletableFuture, 그리고 다시 블로킹처럼 보이게 만든 가상 스레드까지 — 오늘의 비동기 API가 왜 그 모양으로 태어났는지를 따라간다.

관통하는 한 문장은 이것이다. 비동기는 더 빨리 하는 기술이 아니라, 기다리는 동안 스레드를 놓아주는 기술이다.

이 문장은 측정으로 확인된다. 100ms짜리 API 3곳을 부를 때 스레드 풀 방식과 CompletableFuture 방식은 소요 시간이 똑같다(108ms vs 107ms). 다른 건 하나뿐이다 — 그동안 묶여 있는 스레드가 4개냐 0개냐. 처리량은 거기서 갈린다. (✅ 실측 출처: CompletableFuture는 어떻게 논블로킹인가. 이 책은 여는 글에서 이 표를 다시 놓고, 3부에서 더 깊이 잰다.)

각 장은 배경 → 스토리 → 핵심 세 걸음으로 나아가고, 개념마다 실행 가능한 Java·C 코드와 그 장을 한 컷으로 요약한 삽화가 함께한다.

목차

제1부 · 기다림의 역사

  1. 1만 개의 연결 — C10K가 드러낸 것
  2. select에서 epoll까지 — 커널이 답을 바꾼 20년
  3. 블로킹이 문제다 — Node.js가 던진 질문
  4. 리액티브의 부상과 피로 — 그리고 Loom이 온 이유

제2부 · 커널이 스레드를 재우는 법 5. read() 한 번의 전 여정 — 잠드는 순간과 깨는 순간 6. 논블로킹은 플래그 하나다 — O_NONBLOCK과 EAGAIN 7. 이벤트 루프는 OS에 없다 — epoll_wait를 도는 건 당신의 스레드다 8. 스레드에는 천장이 있다 — 22.5배의 범인을 가른다

제3부 · Java는 어떻게 따라왔나 9. 연결마다 스레드를 주던 시절 — Thread와 스레드 풀 10. Future의 한계 — 결과를 꺼내려면 누군가는 기다려야 한다 11. CompletableFuture의 내부 — 콜백 스택과 완료 경합 12. 이벤트 루프를 프레임워크로 — Netty와 WebFlux

제4부 · 비동기가 무너지는 자리 13. 사라진 로그 — ThreadLocal이 스레드를 갈아탈 때 14. 전역 풀 오염 — commonPool에 블로킹을 던지면 15. 루프를 막는 한 줄 — WebFlux에 JDBC를 섞은 날 16. 아무도 기다리지 않으면 — 타임아웃과 백프레셔의 부재

제5부 · 다시 블로킹처럼 17. 가상 스레드의 기계 — 마운트, 언마운트, 그리고 pinning 18. 무엇을 언제 쓰나 — 결정표와 마이그레이션 판단

닫는 글 — 다시, 기다림 앞에서


여는 글부터 읽기 — CPU는 3%인데 서버가 죽었다

Last updated on