왜 이 글을 쓰는가
이전 글에서 Spring MVC의 문제를 다뤘다.
I/O 대기 중에 스레드가 점유 상태로 묶여 있다는 것.
그러면 "I/O 대기"라는 게 정확히 뭐고, 대안은 뭔가.
Blocking I/O, Non-Blocking I/O, Synchronous, Asynchronous.
이 네 가지 개념은 종종 혼동되어 쓰인다.
이 글에서 정확히 구분하고, Spring WebFlux와 Kafka가 왜 이 개념들에 기반하는지 연결한다.
1. I/O란 무엇인가
I/O(Input/Output)는 CPU 외부와의 데이터 접근을 말한다.
•
파일 읽기/쓰기
•
네트워크 송수신
•
DB 쿼리
•
터미널 입력
CPU는 매우 빠르다. I/O는 매우 느리다.
디스크 접근은 CPU보다 10만 배, 네트워크는 더 느리다.
I/O가 일어나는 동안 스레드를 어떻게 다루느냐의 차이가 모델 전체의 성능을 가른다.
2. Blocking I/O
동작 방식
•
스레드가 I/O 완료될 때까지 순수하게 대기
•
I/O 동안 CPU는 다른 작업을 할 수 있지만, 스레드 자체는 어디도 못 감
문제점
[Spring MVC 시나리오]
Thread 1: [---요청 처리---][DB 쿼리 대기.............][---응답---]
^ 이 시간 동안 Thread 1은 묶여 있음
Thread 2: [---요청 처리---][API 호출 대기.............][---응답---]
^ Thread 2도 묶여 있음
Thread N: [---요청---][...................................................]
^ Thread N개가 모두 소진되면 대기 발생
JavaScript
복사
스레드가 200개라면, 200개의 요청이 동시에 DB를 기다리는 순간 풀이 곧 차단된다.
3. Non-Blocking I/O
동작 방식
•
I/O 호출이 즉시 리턴. 데이터가 없으면 없다고 바로 알려준다.
•
스레드는 블록되지 않고 다른 일을 할 수 있다.
•
단, 언제 데이터가 준비되었는지 알아야 하므로 I/O 다중화(Multiplexing) 와 주로 함께 쓴다.
I/O 다중화: select / poll / epoll
•
select/poll: 등록된 디스크립터 전체를 순회하며 확인. O(n)
•
epoll (Linux): 준비된 것만 이벤트로 알려줌. O(1). Nginx, Node.js가 사용
4. Synchronous vs Asynchronous
Blocking/Non-Blocking은 I/O 호출 시 스레드가 대기하느냐의 문제다.
Synchronous/Asynchronous는 결과를 누가 처리하느냐의 문제다.
구분 | 의미 |
Synchronous | 호출한 스레드가 결과를 직접 확인하고 이어서 처리 |
Asynchronous | 결과를 콜백/이벤트로 알려주면 다른 스레드가 이어서 처리 |
네 가지 조합
Synchronous Blocking (= 일반 Blocking I/O)
// 프로그래머가 주로 접하는 일반적인 형태
byte[] data = file.read(); // 완료될 때까지 대기
process(data); // 이어서 실행
Java
복사
호출한 스레드가 I/O가 끝날 때까지 대기하고, 직접 결과를 처리한다.
Synchronous Non-Blocking
// 폴링 방식 — 실무에서 거의 안 쓰임
while (true) {
Result r = socket.readNonBlocking();
if (r.isReady()) {
process(r.data());
break;
}
// 데이터 없으면 계속 폴링 ← CPU 낭비
}
Java
복사
I/O 호출은 즉시 리턴되지만, 완료 확인은 직접 한다. CPU를 낭비하여 실무에서 드물다.
Asynchronous Blocking
// select/poll 유사 케이스 — 이론적 조합
// epoll이 없는 상황에서만 해당 (Linux 2.5 이전)
fd = select(fds); // 변화 발생할 때까지 대기 (blocking)
process(fd.data()); // 큐에서 데이터 가져옴 (async fetch)
Java
복사
호출 자체는 블록되지만, 데이터를 가져오는 경로가 비동기적인 코너케이스다.
실무에서 사용하는 경우는 없다.
Asynchronous Non-Blocking (= 진정한 비동기 I/O)
// Java NIO2 (AsynchronousFileChannel) 예시
AsynchronousFileChannel channel = AsynchronousFileChannel.open(path);
ByteBuffer buffer = ByteBuffer.allocate(1024);
channel.read(buffer, 0, null, new CompletionHandler<>() {
@Override
public void completed(Integer result, Object attachment) {
// I/O 완료 시 다른 스레드가 콜백 실행
process(buffer);
}
});
// 여기선 즉시 리턴 — 대기 없음
// 호출한 스레드는 다른 일을 함
Java
복사
I/O 호출이 즉시 리턴되고, 완료되면 OS가 콜백으로 알려준다.
호출한 스레드가 결과를 처리하지 않아도 된다.
5. 정리하면
모델 | 대기 | 결과 처리 | 스레드 점유 |
Sync Blocking | O | 직접 | O |
Sync Non-Blocking | X | 직접 | X (but 폴링) |
Async Blocking | O | 콜백 | O |
Async Non-Blocking | X | 콜백 | X |
성능에서 중요한 것은 주로 두 가지다.
1.
I/O 동안 스레드가 블록되는가 (Blocking vs Non-Blocking)
2.
완료 콜백으로 처리하는가 (Async)
6. 실무 연결
Spring WebFlux와 Reactor
Spring WebFlux는 Async Non-Blocking 모델로 동작한다.
// WebFlux 코드 예시
@GetMapping("/users/{id}")
public Mono<User> getUser(@PathVariable Long id) {
return userRepository.findById(id) // Non-Blocking DB 호출
.map(user -> enrichUser(user)); // 콜백으로 이어 처리
}
Java
복사
•
DB 쿼리가 나가는 동안 스레드가 다른 요청을 처리한다.
•
쿼리가 완료되면 OS가 이벤트로 알려주고, 콜백이 실행된다.
•
소수의 스레드로 많은 요청을 병렬 처리할 수 있다.
주의할 점: WebFlux를 쓰면서 JDBC(블로킹 드라이버)를 그대로 쓰면 이벤트 루프 스레드가 블록되어
스레드풀 전체가 멈출 수 있다. R2DBC를 쓰는 이유다.
Node.js의 이벤트 루프
Node.js는 단일 스레드에 epoll 기반 이벤트 루프로 I/O 집약적 서비스에 적합하다.
단, CPU 연산이 많은 작업에는 절대적으로 부적합하다.
Kafka 컨슈머 비동기 처리
@KafkaListener(topics = "orders")
public void handleOrder(String message) {
// 메시지가 도착하면 콜백 실행
// 컨슈머는 메시지가 올 때까지 블록되지 않음
processOrder(message);
}
Java
복사
Kafka 컨슈머는 Async Non-Blocking에 가깝다.
메시지가 도착하면 이벤트로 알려주고, 콜백이 처리한다.
7. 언제 어떤 모델을 선택하는가
상황 | 권장 모델 |
I/O가 적고 CPU 작업이 많은 서비스 | Sync Blocking (Spring MVC) |
I/O가 많고 동시 요청이 많은 서비스 | Async Non-Blocking (WebFlux) |
메시지 기반 마이크로서비스 간 통신 | Async (Kafka, RabbitMQ) |
실시간 스트리밍 | Async Non-Blocking (WebSocket + WebFlux) |
정리
개념 | 핵심 |
Blocking I/O | I/O 동안 스레드 대기. 단순하지만 스레드 자원 낭비 |
Non-Blocking I/O | I/O 호출 즉시 리턴. epoll로 완료 시점 감지 |
Synchronous | 호출한 스레드가 직접 결과 확인/처리 |
Asynchronous | 콜백/이벤트로 결과 통보. 처리는 다른 흐름에서 |
Spring MVC | Sync Blocking. 스레드풀 크기가 동시 처리량 상한 |
Spring WebFlux | Async Non-Blocking. 소수 스레드로 많은 I/O 병렬 처리 |
Kafka Consumer | Async 이벤트 기반. 메시지 도착 시 콜백 |
1단계 네트워크/OS 기초를 여기서 마친다.
2단계 DB 심화에서는 이 I/O 모델 위에서 DB가 데이터를 어떻게 읽는지를 다룬다.

