serversim
학습시뮬레이터템플릿가격
…
…
메뉴 열기
학습↗시뮬레이터↗템플릿↗가격↗
내 Documents문서
목차/7단원7 / 16

기다림의 발견 — IO를 만난 CPU는 논다

데이터베이스에 물어보는 코드 한 줄은, 답을 기다리는 동안 CPU를 얼마나 쓸까요? 금고에 서류를 가지러 간 손님은 창구 자리를 붙잡고 있을 뿐, 그 창구에서 일이 되고 있지는 않습니다.

기초에서 먼저 보기어떤 데이터는 왜 순식간에 오고, 어떤 건 한참 걸릴까?→

지금까지의 코드는 CPU 안에서만 놀았습니다. 하지만 실제 프로그램은 파일을 읽고, 네트워크로 데이터를 받고, 데이터베이스에 물어봅니다. 이런 작업을 통틀어 IO(입출력)라고 부릅니다. 이 단원의 핵심은 하나입니다 — IO는 CPU 기준으로 어마어마하게 느리고, 그동안 코드는 그냥 멈춰 있습니다.

① 얼마나 느린가 — 숫자로 보기

CPU가 명령 하나를 처리하는 데 대략 1나노초(10억분의 1초)가 걸립니다. 메모리에서 값을 가져오면 약 100나노초, SSD에서 읽으면 약 100마이크로초(10만 나노초), 네트워크 너머 서버에 물어보면 수 밀리초(수백만 나노초)입니다. CPU 시간을 사람 시간으로 확대해 보면, 명령 하나가 1초일 때 SSD 읽기는 하루가 넘고 네트워크 응답은 몇 달에 해당합니다. 이 격차가 이 시리즈 전체의 출발점입니다.

② 블로킹이란 무엇인가

코드가 db.query() 같은 함수를 부르면, 그 함수는 결과가 도착할 때까지 돌아오지 않습니다. 그래서 그다음 줄은 실행되지 못하고 멈춰 있습니다. 이렇게 결과를 받을 때까지 진행이 막히는 것을 블로킹(blocking)이라고 합니다. 애니메이션에서 PC가 한 줄에 붙박여 있고 CPU 표시가 "대기"로 바뀌는 구간이 그것입니다.

③ 기다리는 동안에도 메모리는 그대로

4단원을 떠올려 보세요. 참조를 쥐고 있으면 GC는 회수하지 못합니다. 기다리는 중인 함수는 스택 프레임을 그대로 유지하고 있고, 그 안의 변수들이 힙 데이터를 계속 가리키고 있습니다. 즉 기다리는 코드는 CPU는 안 쓰면서 메모리는 쓰는 상태입니다. 이 조합이 나중에 서버에서 어떤 사고를 만드는지 10단원에서 보게 됩니다.

④ 그렇다면 CPU는?

이 애니메이션은 프로그램 하나만 보고 있기 때문에 CPU가 "논다"고 표시됩니다. 하지만 실제 컴퓨터에서 CPU가 정말 놀고 있을까요? 아닙니다 — 운영체제가 그 순간 다른 일감을 CPU에 올립니다. 그 과정이 5·6단원의 주제입니다.

⑤ 얼마나 느린가 — 숫자로 잡는 감각

CPU 명령 하나는 나노초 단위입니다. 메모리 접근은 100나노초 안팎, SSD 읽기는 수십 마이크로초, 같은 데이터센터 안의 네트워크 왕복은 0.5밀리초 정도, 다른 지역이면 수십 밀리초입니다. CPU 시간으로 환산하면 디스크 한 번은 명령 수만 개, 원격 호출 한 번은 명령 수천만 개를 실행할 수 있는 시간입니다. 이 격차가 이 시리즈 전체의 출발점입니다.

⑥ 그래서 무엇을 최적화해야 하나

기다림이 지배적인 작업에서는 계산을 아무리 빠르게 만들어도 전체 시간이 거의 줄지 않습니다. 대신 기다림을 겹치게 만들면 극적으로 줄어듭니다. 요청 열 개를 순서대로 기다리면 열 배가 걸리지만, 동시에 기다리면 한 번에 가까워집니다. 앞으로 나올 스레드풀·이벤트루프·코루틴은 전부 이 겹치기를 어떻게 싸게 만드느냐에 대한 서로 다른 답입니다.

단계 1/6속도
program
1handle(req) {
2 id = parse(req)PC ◀
3 user = db.query(id) // DB에 물어보고 기다림
4 return toJson(user)
5}

서버가 요청을 받으면 handle이 호출됩니다(1단원의 [주소 등록 + 점프] 그대로). 요청에서 id를 꺼냅니다 — CPU가 일하는 중 (0.3ms). PC = 2.

CPU0
상태실행 중PC · 실행 위치→ 2번 줄
스택↑ 위가 최근 프레임
handle()복귀 → 호출한 곳
reqid = 42
힙
∅

기다림의 자릿수 — 1나노초를 1초로 늘려 보면

CPU 명령 하나가 1초로 느껴지도록 모든 시간을 10억 배 확대했습니다.

  • L1 캐시에서 읽기0.5 ns0.5초 — 심장 한 번 뛰는 사이
  • 메인 메모리에서 읽기100 ns1분 40초 — 양치질하는 시간
  • SSD에서 한 번 읽기150 µs1.7일 — 주말 한 번
  • 같은 데이터센터 안 왕복0.5 ms5.8일 — 짧은 휴가
  • 하드디스크 헤드 이동10 ms16.5주 — 한 학기
  • 대륙 간 왕복 (미국↔유럽)150 ms4.8년 — 대학 학사 과정

외부 호출 한 번을 기다리는 동안, CPU는 명령 수억 개를 실행할 수 있었습니다.

출처: Jeff Dean · Peter Norvig, “Latency Numbers Every Programmer Should Know” (사람 시간 환산: hellerbarde). 막대는 로그 스케일.

시뮬레이터에서 해보기

  1. 1.'캐시 추가' 프리셋에서 Redis 노드의 cache hit rate (%)가 95입니다 — 10초 재생하면 Redis가 13,915건을 즉답하고 MySQL은 6,685건만 받습니다.
  2. 2.인스펙터에서 cache hit rate (%)를 50으로 내려보세요 — miss가 10배로 늘면서 MySQL이 받는 요청도 그만큼 따라 올라갑니다. 이 단원에서 본 '기다림'을 얼마나 자주 겪는지가 바로 이 노브입니다.
시뮬레이터에서 실험해보기 →

이 단원의 용어

IO (입출력)
디스크·네트워크·키보드 등 CPU 바깥 장치와 데이터를 주고받는 일.
블로킹(blocking)
결과가 올 때까지 다음 줄로 넘어가지 못하고 멈춰 있는 방식.
지연(latency)
요청 하나가 끝날 때까지 걸리는 시간.
처리량(throughput)
단위 시간당 끝낸 일의 개수. 지연이 길어도 겹쳐 처리하면 처리량은 올릴 수 있다.
지연 시간 계층
메모리 · 디스크 · 네트워크가 자릿수 단위로 다른 속도를 갖는 것.
겹친 기다림
여러 기다림을 동시에 진행시켜 총시간을 줄이는 것. 이후 모든 모델의 목표.

왜 이렇게 발전했나

CPU는 반도체 속도로 동작하지만 디스크와 네트워크는 물리적 거리와 기계적 한계에 묶여 있습니다. 이 격차는 앞으로도 사라지지 않습니다. 그래서 컴퓨터 역사에 등장한 실행 모델 대부분은 "기다리는 시간을 어떻게 쓸모 있게 만들 것인가"에 대한 서로 다른 대답입니다.

병목은 계산이 아니라 기다림. 기다리는 코드는 CPU는 안 쓰고 메모리만 쓴다.

남은 문제

기다리는 동안 CPU에게 다른 일을 시키면 되지 않을까요? 그러려면 "다른 일"이 먼저 있어야 합니다 →

← 이전 단원

6단원. CPU는 언제 쉬나 — 인터럽트와 컨텍스트 스위치

다음 단원 →

8단원. 스레드 — 프로세스가 무거워서 태어났다