[CS] 동기와 비동기의 차이, Nginx 구조로 쉽게 이해하기

Hyunjun Kim·2025년 5월 17일
0

Computer_Science

목록 보기
5/19

서론

요즘 개발하다 보면 꼭 한번쯤 듣게 되는 말이 있다.
“이거 비동기로 짜야 돼.” 혹은 “Nginx는 비동기니까 더 빠르다.”

처음에는 “동기? 비동기? 그게 뭐야… 그냥 실행되면 되는 거 아냐?” 싶었는데, 이게 생각보다 꽤 중요했다.
특히 나는 Python 백엔드 개발을 할 때도 그렇고, nginx를 공부할 때도 자주 맞닥뜨리게 됐고, 개념이 헷갈려서 고생도 많이 했다.

그래서 이 글에서는 동기 vs 비동기, 블로킹 vs 논블로킹을 먼저 정리하고

마지막에는 Nginx에서 비동기 처리가 실제로 어떻게 작동하는지, 컴퓨터 내부에서 어떤 일이 벌어지는지를 시스템 수준에서 설명하려 한다.


목차

  1. 개념 정리: 동기(Synchronous) vs 비동기(Asynchronous)
  2. 관련 개념: Blocking vs Non-blocking
  3. 예시로 이해하기 (비유 + 코드 + 시스템 관점)
  4. Nginx에서 비동기의 의미
  5. Nginx 내부에서 컴퓨터는 어떻게 처리하는가
  6. 마스터-워커 구조와 단일 스레드의 효율

본문

1. 동기 vs 비동기 – 정의와 핵심 차이점

처음에는 단순히 “동기는 기다리는 거고, 비동기는 안 기다리는 거다” 라고 들었다.
근데 막상 코드를 짜보면, 비동기로 짠 건데도 뭔가 안 기다리는 것 같지가 않고, 도대체 뭐가 뭔지 모르겠었다.

그래서 이렇게 정리했다.

구분동기 (Synchronous)비동기 (Asynchronous)
처리 흐름A 끝나고 나서 B 실행A 실행 중에 B도 같이 시작
특징요청한 코드가 끝날 때까지 계속 붙잡고 있음요청하고 나서 나중에 결과 오면 처리
내 느낌줄 서서 처리하는 느낌번호표 뽑고 있다가 불리면 처리하는 느낌

실제로 개발할 땐, “요청을 보내놓고 기다리냐, 아니면 바로 다음 작업하냐” 가 핵심이다.
나는 예전에 Selenium으로 브라우저 자동화할 때, 페이지가 로딩될 때까지 기다리느라 비동기 처리에 대해 관심이 생겼고, 그때부터 본격적으로 개념을 파기 시작했다.


2. Blocking vs Non-blocking

여기서 끝나면 좋은데, “blocking”, “non-blocking”이라는 말도 같이 튀어나온다.
동기/비동기와 헷갈리지 않게 표로 정리하면 다음과 같다:

개념동기 / 비동기블로킹 / 논블로킹
관점프로그램 흐름 (순차 vs 이벤트)함수 호출 방식 (기다림 유무)
질문“이 작업 끝날 때까지 기다릴래?”“CPU는 이 함수 기다리면서 놀고 있을래?”

조금 더 감 잡기 쉽게 예시를 정리해봤다

조합예시설명
동기 + 블로킹일반적인 Python 함수result = slow_function() 호출하면 결과 나올 때까지 가만히 기다림
비동기 + 블로킹async 함수지만 내부에 sleep()이나 I/O가 있을 때이벤트 루프는 안 돌고, 그냥 결과 기다리면서 멈춤
비동기 + 논블로킹Node.js, Nginx요청하고 다른 작업도 같이 돌림. 응답 오면 콜백으로 처리

이 조합은 진짜 중요하다.
특히 Nginx를 이해할 때 “비동기 + 논블로킹”이라는 구조가 왜 고성능인지를 설명해주는 핵심이기 때문이다


3. 예시로 감 잡기

3.1. 비유 - 음식점 주문

  • 종업원 = CPU
  • 주방 = 외부 시스템 (DB, 파일 I/O, 네트워크 등)

손님이 주문하면 종업원은 주문을 주방에 넘기고 다른 손님을 받는다.
주방에서 요리가 끝나면 종업원은 다시 돌아와 서빙한다.

핵심: CPU는 기다리지 않고 다른 일을 한다.

3.2. Python 코드 예시

동기 + 블로킹

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가 되느냐가 핵심이었다.


4. Nginx에서 비동기의 의미

Nginx는 비동기 + 논블로킹 + 이벤트 기반 구조의 대표적인 예다.

  • 요청 하나 들어올 때마다 스레드를 새로 만드는 구조가 아니라, 하나의 워커 프로세스가 수천 개의 요청을 동시에 다룰 수 있다.

그렇다면 컴퓨터 안에서는 도대체 무슨 일이 일어나는 걸까?

5. 컴퓨터 내부에서 Nginx가 요청을 처리하는 과정

1단계: 요청(signal)이 들어온다

  • 사용자가 브라우저에서 GET /index.html 요청을 보낸다.
  • 이 요청은 인터넷을 통해 TCP 패킷으로 변환되어 서버의 네트워크 카드(NIC)로 들어온다.

2단계: 커널이 소켓을 통해 이 요청을 수신한다

  • 네트워크 카드(NIC)는 인터럽트를 발생시켜 CPU에게 "데이터 들어왔어!" 라고 알린다.
  • 운영체제 커널의 소켓 버퍼에 데이터가 저장된다.
  • 이 소켓은 파일 디스크립터(file descriptor) 형태로 Nginx 프로세스가 접근 가능하다.

3단계: Nginx는 커널에게 "이 fd에 이벤트 생기면 알려줘"라고 등록한다

  • Nginx는 epoll, kqueue 같은 이벤트 감시 메커니즘을 통해 비동기 + 논블로킹으로 이벤트를 대기한다.
  • epoll_wait() 호출을 통해 Nginx는 "이 소켓에 읽을 데이터 생기면 내게 알려줘"라고 커널에게 등록한다.

4단계: 데이터가 준비되면, 커널이 이벤트 루프에 알려준다

  • 커널은 I/O가 준비되면 epoll에게 알려주고,
  • Nginx 이벤트 루프는 해당 콜백 함수를 실행한다 (예: 읽기, 파싱, 응답 생성 등)

5단계: 응답은 다시 소켓을 통해 커널 → NIC → 사용자에게 전송된다

이 전 과정에서 Nginx는 단 한 번도 기다리지 않는다.
이벤트가 오면 처리하고, 없으면 다른 요청을 처리하거나 대기만 할 뿐이다.


6. Nginx 구조 요약 – 왜 단일 스레드인데도 빠를까?

구성 요소설명
마스터 프로세스설정 로딩, 워커 관리 역할
워커 프로세스실제 요청 처리. 각각 단일 스레드로 동작
이벤트 루프epoll/kqueue로 수천 개의 요청을 감시
비동기 + 논블로킹CPU 낭비 없이 효율적인 I/O 처리

Apache는 요청마다 스레드를 생성하고, 컨텍스트 스위칭 오버헤드가 발생한다.
반면 Nginx는 단일 워커 스레드에서 수천 개의 소켓을 이벤트 기반으로 처리한다.
이 덕분에 훨씬 더 많은 동시 요청을 처리할 수 있다.

결론

비동기 처리란 단순히 "기다리지 않는" 게 아니다.
커널, 소켓, 이벤트 루프, epoll 같은 시스템 레벨의 구조와 함께 이해해야 실체가 보인다.

이제는 "Nginx는 왜 빠른가?"라는 질문에,

“단일 스레드지만, 논블로킹 I/O 기반의 이벤트 루프 구조 덕분에 CPU 낭비 없이 수천 개의 요청을 동시에 처리할 수 있기 때문”
이라고 대답할 수 있다.

profile
Data Analytics Engineer 가 되

0개의 댓글