serversim
학습시뮬레이터템플릿가격
…
…
메뉴 열기
학습↗시뮬레이터↗템플릿↗가격↗
내 Documents문서
기초 트랙 목차/8 / 86분

이벤트루프는 언제 유리하고 언제 불리할까?

함정 질문적은 자리로 많은 일을 처리하는 방식이 항상 유리하다면, 왜 아직도 자리를 많이 두는 서버가 있을까요?

움직여 보기

① 기다림이 대부분일 때 — 자리 1개(이벤트루프) vs 자리 4개(스레드풀)

0/49 스텝속도자리(스레드)
0.0초완료01DB금고줄 0
응대 중통화 중금고에 감비어 있음

0.0초 · 줄 0명 · 응대 중 0곳 · 완료 0명 — 아직 아무도 오지 않았습니다

② 무거운 계산 하나가 섞이면 — 루프는 그 하나가 끝날 때까지 아무도 못 부른다

0/42 스텝속도자리(스레드)
0.0초완료01DB금고줄 0
응대 중통화 중금고에 감비어 있음

0.0초 · 줄 0명 · 응대 중 0곳 · 완료 0명 — 아직 아무도 오지 않았습니다

③ 답이 올 때까지 그 자리에서 손 놓고 기다리면(= 블로킹 호출) — 자리 하나짜리 동기 서버로 되돌아간다

0/49 스텝속도
0.0초완료01DB금고줄 0
응대 중통화 중금고에 감비어 있음

0.0초 · 줄 0명 · 응대 중 0곳 · 완료 0명 — 아직 아무도 오지 않았습니다

문장 따라가기

1/49

이벤트루프는 언제 유리하고 언제 불리할까요?

텍스트 모드에서는 3초마다 다음 문장으로 넘어갑니다.

이렇게 생각해 보세요

앞 장에서 "창구 200개"라고 부른 것은 이 장에서는 "자리"입니다 — 이 장의 창구는 실제로 일을 하는 CPU 하나입니다. 자리(스레드)가 하나뿐인 은행을 생각해 봅시다. 손님들은 저마다 금고에 서류를 가지러 가야 하고, 은행원은 지금 돌아온 사람만 골라 부릅니다. 기다림은 창구를 비워 주니까 자리 하나로도 여러 건이 겹쳐 진행됩니다 — 첫 그림의 "자리 1개"가 이 방식이고, 이것을 이벤트루프라고 부릅니다.

같은 일을 자리 네 개짜리 은행에서도 해봅니다. 창구는 여전히 하나이고, 자리에 앉은 손님 네 명이 같은 건수를 나눠 봅니다. 두 그림 다 일은 끝납니다 — 갈리는 것은 "몇 자리를 붙들고 있었나"입니다.

두 번째 그림에서는 그 사이에 무거운 계산 하나가 섞입니다. 자리 하나짜리 은행에서는 계산하는 손님이 창구를 놓지 않아서, 금고에서 돌아온 사람들이 전부 서서 기다립니다. 자리가 여럿이면 시간을 잘라 번갈아 앉히니 나머지가 멈추지 않습니다.

세 번째 그림은 자리 하나짜리 은행에서, 손님이 금고에서 돌아올 때까지 은행원이 창구를 비워 둔 채 기다리는 경우입니다 — 겹칠 것이 하나도 남지 않습니다. 이렇게 답이 올 때까지 그 자리에서 손을 놓고 기다리는 것을 블로킹(동기)이라 하고, 자리를 비켜 주고 벨이 울리면 받는 쪽을 논블로킹(비동기)이라고 부릅니다 — 자바에서는 이 둘을 IO와 NIO라고 부릅니다.

숫자로 뒤집기

실측 · 지금 계산됨

기다림이 대부분인 일 12건을 자리 1개와 자리 4개에 각각 태웠습니다. 자리 1개는 24.0칸, 자리 4개는 34.6칸 — 자리를 네 배 준 쪽이 오히려 1.4배 느립니다.

10.6칸의 차이가 어디서 오는지 갈라 보겠습니다. 자리가 4개면 금고에 동시에 가 있을 수 있는 손님도 4명뿐인데, 자리 하나로 도는 은행원은 7명을 금고에 보내 놓고 다음 사람을 받았습니다. 같은 풀에서 바꿔 앉는 비용만 0으로 두고 다시 돌리면 31.6칸이니, 창구에 앉은 사람이 21번 바뀐 비용(자리 1개 쪽은 0번)은 차이의 28.3%뿐입니다. 나머지는 "동시에 몇 명을 기다리게 해 두었나"에서 옵니다.

그래서 이벤트루프의 이득은 "빠름"이 아니라 "적은 자리로 많은 기다림을 동시에 걸어 두는 것"입니다. 이 모델에서 동시에 붙들고 있던 자리는 4개와 1.1개(3.5배)였고 — 자리 하나를 1, 금고에 보내 둔 일 하나를 0.02로 세는 단순화입니다 — 그동안 창구가 실제로 일한 시간의 비율은 69.4%와 100%였습니다. 실제 서버에서 메모리까지 줄어드는지는 아래 프리셋에서 직접 확인해 보세요. 늘 그렇지는 않습니다.

이제 무거운 계산 하나를 섞습니다. 전체가 끝나는 시각은 자리 1개 쪽이 40.5칸으로 자리 12개 쪽(46.8칸)보다 빠릅니다. 그런데 나머지 요청 12건이 끝난 평균 시각은 자리 1개에서 38.1칸, 자리 12개에서 17.6칸으로 2.2배 벌어집니다 — 전체 처리량과 손님 한 명이 겪는 시간은 다른 이야기입니다.

마지막으로 루프 안에서 블로킹 호출을 하면, 같은 12건이 96.0칸 걸립니다. 자리 하나짜리 동기 서버(96.0칸)와 칸 단위까지 같은 값입니다. 금고에 동시에 가 있던 사람이 7명에서 1명으로 줄었으니, 겹칠 것이 없어진 것입니다.

이 숫자는 문구에 박아둔 값이 아니라, 이 화면을 그릴 때 시뮬을 돌려 나온 값입니다.

그래서 무엇을 고르나

상황고를 것왜
기다림이 대부분이고 접속이 많다 (외부 API·DB·소켓)이벤트루프, 또는 코루틴(같은 방식을 위에서 아래로 읽히게 쓴 것)기다림은 창구를 비켜 주므로 자리 하나로도 여러 건이 동시에 기다립니다. 이득은 적은 자리로 많은 접속을 동시에 붙들 수 있다는 것이지, 메모리가 반드시 줄어든다는 뜻은 아닙니다 — 위 ① 그림.
요청 하나가 CPU를 오래 붙잡는다 (이미지 변환·암호화·큰 정렬)스레드풀계산은 창구를 비켜 주지 않습니다. 자리가 여럿이면 운영체제가 시간을 잘라 번갈아 앉혀서 나머지 요청이 멈추지 않습니다 — 위 ② 그림. 자리 수는 코어 수 근처에서 잡는 것이 흔한 경험칙입니다(위 실측이 정한 값은 아닙니다).
기다리는 동안 손을 못 놓는 라이브러리를 써야 한다 (오래된 DB 드라이버 같은)스레드풀, 또는 그 호출만 별도 자리로 넘기기루프 안에서 블로킹 호출을 하면 겹칠 것이 사라져 자리 하나짜리 동기 서버가 됩니다 — 위 ③ 그림.
접속은 아주 많은데 대부분 조용하다 (웹소켓·SSE·롱폴링)이벤트루프접속 수와 자리 수가 분리됩니다. 접속마다 자리를 하나씩 주면 일이 없어도 자리부터 바닥납니다.

시뮬레이터에서 해보기

  1. 1.'리액티브 스택' 프리셋에서 WebFlux 카드를 선택하고 ioWorkerCount를 찾아보세요. 이 프리셋에는 비교용 Tomcat 카드가 없습니다. 위 그림의 자리 비교와 실제 프리셋을 구분해서 봅니다.
  2. 2.부하·seed·실행 시간을 고정하고 기본 결과를 기록한 뒤 ioWorkerCount만 줄여 보세요. 초기화하고 같은 시간 동안 실행해 CPU·처리량·지연을 비교합니다.
  3. 3.기다림은 여러 요청이 겹칠 수 있어도 계산할 자리는 필요합니다. 자리를 늘리는 효과는 작업에 따라 다르며, 다른 프레임워크와의 차이에는 요청 비용과 설정도 함께 영향을 줍니다.
  • 두 스택의 성공률이 똑같이 100%인 것과 함께 메모리도 보세요 — 이 프리셋 실측은 352MB(WebFlux) vs 324MB(Tomcat)로 오히려 WebFlux가 조금 더 씁니다. 이벤트루프의 이득은 스레드당 스택이 아니라 동시 접속 수용력(maxConcurrent 10,000)입니다. 위 단계에서 본 CPU 18% vs 46%도 같은 방향이지만, 그것은 이 프리셋에서 시뮬레이터가 낸 값이라 스택과 워크로드가 바뀌면 달라집니다.
  • 프리셋 목록의 "WebSocket Chat — 10k 동시 user"도 열어 보세요. 10,000명이 붙어 있는데 WebFlux CPU는 6%입니다. 요청마다 스레드를 붙드는 방식이면 접속 10,000개에 스레드도 10,000개가 필요하고, 그건 OOM입니다 — 결정 표의 마지막 줄이 이 장면입니다.
시뮬레이터에서 열기 →

한 문장으로

적은 자리로 많은 기다림을 감당하는 방식은 기다림이 대부분일 때 유리하고, 오래 걸리는 계산이나 블로킹 호출이 섞이면 불리해집니다.

진도는 이 브라우저에만 저장됩니다 (계정·기기 간 동기화 없음).

확인 문제

0 / 3 맞음
  1. 1.자리 하나로 여러 일을 번갈아 처리하는 서버에서 가장 위험한 요청은?

  2. 2.자리 1개(이벤트루프)가 자리 4개(스레드풀)보다 같은 일을 빨리 끝낸 가장 큰 이유는?

  3. 3.이벤트루프 안에서 동기(블로킹) DB 드라이버를 쓰면?

새로 나온 말

이벤트 루프
자리 하나가 "지금 준비된 일 있나?"를 계속 돌아보며 짧게짧게 처리하는 방식입니다. 기다리는 일은 자리를 비켜 주므로 자리 하나로 여러 건을 겹칠 수 있습니다.
스레드풀
요청을 받을 자리를 미리 여러 개 만들어 두고 돌려 쓰는 묶음입니다. 자리마다 메모리가 들고, 자리를 바꿔 앉을 때마다 비용이 듭니다.
코루틴
스케줄링은 이벤트루프와 같지만, 코드는 "기다렸다가 이어서"처럼 위에서 아래로 읽히게 쓰는 방식입니다(Python asyncio, 스레드 하나에서 도는 Kotlin 코루틴). 방식이 아니라 문법이 다른 것입니다. 다만 Go의 고루틴은 여러 코어에 나눠 돌리고 긴 계산은 강제로 바꿔 앉히므로 ②의 함정이 그대로 적용되지는 않습니다.

이전 장

창구가 200개인데 왜 10개만 일할까?

serversim

서버가 느려지고 터지는 이유를 배우고, 같은 현상을 직접 시뮬레이션하는 학습 도구.

© 2025-2026 serversim · 아키텍처 시뮬레이션 도구

제품

  • 학습
  • 시뮬레이터
  • 템플릿
  • 가격

자료

  • 문서
  • 벤치마크
  • 블로그

정책

  • 개인정보
  • 약관
  • 환불 정책