요즘 개발하다 보면 꼭 한번쯤 듣게 되는 말이 있다.
“이거 비동기로 짜야 돼.” 혹은 “Nginx는 비동기니까 더 빠르다.”
처음에는 “동기? 비동기? 그게 뭐야… 그냥 실행되면 되는 거 아냐?” 싶었는데, 이게 생각보다 꽤 중요했다.
특히 나는 Python 백엔드 개발을 할 때도 그렇고, nginx를 공부할 때도 자주 맞닥뜨리게 됐고, 개념이 헷갈려서 고생도 많이 했다.
그래서 이 글에서는 동기 vs 비동기, 블로킹 vs 논블로킹을 먼저 정리하고
마지막에는 Nginx에서 비동기 처리가 실제로 어떻게 작동하는지, 컴퓨터 내부에서 어떤 일이 벌어지는지를 시스템 수준에서 설명하려 한다.
처음에는 단순히 “동기는 기다리는 거고, 비동기는 안 기다리는 거다” 라고 들었다.
근데 막상 코드를 짜보면, 비동기로 짠 건데도 뭔가 안 기다리는 것 같지가 않고, 도대체 뭐가 뭔지 모르겠었다.
그래서 이렇게 정리했다.
구분 | 동기 (Synchronous) | 비동기 (Asynchronous) |
---|---|---|
처리 흐름 | A 끝나고 나서 B 실행 | A 실행 중에 B도 같이 시작 |
특징 | 요청한 코드가 끝날 때까지 계속 붙잡고 있음 | 요청하고 나서 나중에 결과 오면 처리 |
내 느낌 | 줄 서서 처리하는 느낌 | 번호표 뽑고 있다가 불리면 처리하는 느낌 |
실제로 개발할 땐, “요청을 보내놓고 기다리냐, 아니면 바로 다음 작업하냐” 가 핵심이다.
나는 예전에 Selenium으로 브라우저 자동화할 때, 페이지가 로딩될 때까지 기다리느라 비동기 처리에 대해 관심이 생겼고, 그때부터 본격적으로 개념을 파기 시작했다.
여기서 끝나면 좋은데, “blocking”, “non-blocking”이라는 말도 같이 튀어나온다.
동기/비동기와 헷갈리지 않게 표로 정리하면 다음과 같다:
개념 | 동기 / 비동기 | 블로킹 / 논블로킹 |
---|---|---|
관점 | 프로그램 흐름 (순차 vs 이벤트) | 함수 호출 방식 (기다림 유무) |
질문 | “이 작업 끝날 때까지 기다릴래?” | “CPU는 이 함수 기다리면서 놀고 있을래?” |
조금 더 감 잡기 쉽게 예시를 정리해봤다
조합 | 예시 | 설명 |
---|---|---|
동기 + 블로킹 | 일반적인 Python 함수 | result = slow_function() 호출하면 결과 나올 때까지 가만히 기다림 |
비동기 + 블로킹 | async 함수지만 내부에 sleep() 이나 I/O 가 있을 때 | 이벤트 루프는 안 돌고, 그냥 결과 기다리면서 멈춤 |
비동기 + 논블로킹 | Node.js, Nginx | 요청하고 다른 작업도 같이 돌림. 응답 오면 콜백으로 처리 |
이 조합은 진짜 중요하다.
특히 Nginx를 이해할 때 “비동기 + 논블로킹”이라는 구조가 왜 고성능인지를 설명해주는 핵심이기 때문이다
손님이 주문하면 종업원은 주문을 주방에 넘기고 다른 손님을 받는다.
주방에서 요리가 끝나면 종업원은 다시 돌아와 서빙한다.
핵심: CPU는 기다리지 않고 다른 일을 한다.
동기 + 블로킹
import time
def task(name):
time.sleep(2)
print(f"{name} 작업 완료")
task("A")
task("B") # A가 끝나야 B 시작
비동기 + 논블로킹
import asyncio
async def task(name):
await asyncio.sleep(2)
print(f"{name} 작업 완료")
async def main():
await asyncio.gather(task("A"), task("B"))
asyncio.run(main())
간단한 코드에서는 이해는 되지만 코드가 길어지다보면 어느 단위로 어떻게 비동기 처리를 해야할지 막막했던 기억이 난다. 중요한 점은 내부에서 진짜 비동기 I/O가 되느냐가 핵심이었다.
Nginx는 비동기 + 논블로킹 + 이벤트 기반 구조의 대표적인 예다.
그렇다면 컴퓨터 안에서는 도대체 무슨 일이 일어나는 걸까?
GET /index.html
요청을 보낸다.epoll
, kqueue
같은 이벤트 감시 메커니즘을 통해 비동기 + 논블로킹으로 이벤트를 대기한다.epoll_wait()
호출을 통해 Nginx는 "이 소켓에 읽을 데이터 생기면 내게 알려줘"라고 커널에게 등록한다.이 전 과정에서 Nginx는 단 한 번도 기다리지 않는다.
이벤트가 오면 처리하고, 없으면 다른 요청을 처리하거나 대기만 할 뿐이다.
구성 요소 | 설명 |
---|---|
마스터 프로세스 | 설정 로딩, 워커 관리 역할 |
워커 프로세스 | 실제 요청 처리. 각각 단일 스레드로 동작 |
이벤트 루프 | epoll/kqueue로 수천 개의 요청을 감시 |
비동기 + 논블로킹 | CPU 낭비 없이 효율적인 I/O 처리 |
Apache는 요청마다 스레드를 생성하고, 컨텍스트 스위칭 오버헤드가 발생한다.
반면 Nginx는 단일 워커 스레드에서 수천 개의 소켓을 이벤트 기반으로 처리한다.
이 덕분에 훨씬 더 많은 동시 요청을 처리할 수 있다.
비동기 처리란 단순히 "기다리지 않는" 게 아니다.
커널, 소켓, 이벤트 루프, epoll 같은 시스템 레벨의 구조와 함께 이해해야 실체가 보인다.
이제는 "Nginx는 왜 빠른가?"라는 질문에,
“단일 스레드지만, 논블로킹 I/O 기반의 이벤트 루프 구조 덕분에 CPU 낭비 없이 수천 개의 요청을 동시에 처리할 수 있기 때문”
이라고 대답할 수 있다.