1. 동기(Synchronous) 처리의 개념
동기 방식은 요청이 들어오면 한 스레드가 하나의 요청만 담당하는 구조다.
이 요청이 끝날 때까지 스레드는 다른 작업을 하지 못한다. (블로킹)
예를 들어 /register 요청이 DB를 기다리는 동안, 그 스레드는 단순히 멈춰 있는 상태다.
스레드풀이 모두 사용 중이면 새 요청은 대기 상태로 들어간다.
2. 비동기(Asynchronous) 처리의 개념
비동기는 I/O가 발생할 때 스레드를 점유하지 않고 다른 요청을 처리한다.
즉, 기다리긴 하지만 스레드를 붙잡아두지 않는다.
스레드는 요청을 잠시 멈춘(suspend) 코루틴 대신 다른 요청을 처리할 수 있다.
결과적으로 적은 스레드로 수천 개의 요청을 동시에 처리할 수 있다.
코루틴의 기본 원리
- suspend fun이 호출되면 일시중단(suspend)되고 스레드는 반환된다.
- I/O가 완료되면 코루틴이 다시 재개(resume)된다.
- 논리적으로는 “순차 코드”, 실제로는 “비동기 처리” 구조다.
3. 동기 vs 비동기의 실제 동작 비교
구분 동기(Spring MVC) 비동기(WebFlux / Coroutine)
| 요청당 스레드 | 1개 고정 | suspend/resume 구조 |
| I/O 대기 | 스레드 블로킹 | 스레드 반납 |
| 동시에 처리 가능한 요청 수 | 스레드 수에 제한 (200~400) | 수천~수만 가능 |
| 메모리 사용 | 많음 (스레드당 1~2MB) | 매우 적음 (코루틴당 몇 KB) |
| 코드 형태 | 전통적, 직관적 | 순차형이지만 논블로킹 |
| 효율 | CPU·메모리 낭비 많음 | 매우 효율적 |
4. 스레드 1개에 코루틴 여러 개가 실행된다는 의미
코루틴은 스레드 위에서 동작하는 가벼운 실행 단위다.
하나의 스레드가 여러 코루틴을 번갈아 실행하고,
각 코루틴이 suspend되면 즉시 다른 코루틴으로 전환된다.
예를 들어 스레드 4개로 100명의 요청을 처리할 때,
스레드 4개가 100개의 코루틴을 라운드 로빈 방식으로 빠르게 순환 실행한다.
1번 요청과 100번 요청 사이에는 미세한 시간차만 존재하며,
사용자는 거의 동시에 처리되는 것으로 느낀다.
5. 동기 방식이 여전히 유효한 이유
리소스가 넉넉하거나 서비스 구조가 단순하다면, 동기 방식도 충분히 효율적이다.
특히 순서가 중요하거나 트랜잭션 일관성이 필요한 서비스에서는 동기식이 오히려 필수다.
상황 동기 방식이 적합한 이유
| CPU 연산 중심 | I/O 대기 거의 없음, 비동기 이점 적음 |
| 내부 서비스 / 관리자 페이지 | 트래픽 낮음, 구조 단순 |
| 블로킹 API만 제공하는 라이브러리 | 비동기 변환이 오히려 복잡 |
| 유지보수 / 디버깅 중요 | 호출 흐름이 명확함 |
6. 코루틴이 인기 있는 이유
- 언어 레벨 지원 (Kotlin)
suspend fun, withContext, Flow 등을 통해 언어 차원에서 자연스럽게 통합된다. - 동기처럼 보이는 비동기 코드
콜백 없이 순차 코드 형태로 작성이 가능하다. - Reactor보다 단순한 구조
Mono/Flux 체인보다 훨씬 읽기 쉽고 유지보수하기 쉽다. - 가벼운 실행 단위
코루틴은 스레드보다 훨씬 가볍기 때문에 수천~수만 개 생성이 가능하다.
7. 코루틴의 한계
| JVM 종속 | Kotlin 전용, 다른 언어와의 호환성 낮음 |
| CPU 바운드 작업 | suspend 지점이 적어 효율이 떨어짐 |
| 디버깅 어려움 | 스택 추적이 중간에 끊기는 경우 있음 |
| 블로킹 라이브러리 혼용 | JDBC 등은 논블로킹이 아니므로 주의 필요 |
코루틴은 JVM 환경에서 가장 현실적이고 강력한 비동기 모델이지만,
모든 상황에서의 정답은 아니다.
8. 동기 방식을 반드시 사용해야 하는 서비스
모든 서비스를 비동기로 만들 필요는 없다.
일부 서비스는 순차 실행과 트랜잭션 일관성이 핵심이기 때문에
동기 구조가 필수적이다.
대표 사례
서비스 예시 이유
| 티켓팅, 예매, 결제 | 순서 보장, 동시 요청 시 데이터 충돌 방지 필요 |
| 재고 관리, 포인트 적립 | 순차적 트랜잭션 처리 필요 |
| 송금, 결제, 회계 시스템 | 원자성(Atomicity), 일관성(Consistency) 필수 |
| 하드웨어 제어 시스템 | 처리 순서와 타이밍 보장이 중요 |
| 내부 관리, 배치 프로세스 | 복잡도보다 안정성·단순성이 중요 |
이런 서비스는 한 요청이 끝나야 다음 요청을 처리해야 하므로,
순차적 동기 실행이 올바른 선택이다.
9. 현실적인 접근: 동기와 비동기의 혼합
현대 시스템은 대부분 “하이브리드 구조”를 취한다.
핵심 트랜잭션은 동기로 처리하고,
부가적인 부분은 비동기로 분리한다.
| API 수신부 | 비동기 | 많은 요청을 빠르게 수용 |
| 핵심 비즈니스 로직 (DB, 결제, 재고 등) | 동기 | 순서, 일관성 보장 |
| 부가 처리 (이메일, 로그, 알림 등) | 비동기 | 사용자 응답 지연 방지 |
이 방식이 실무에서 가장 널리 사용되는 구조다.
10. 핵심 요약
| 동기 서버 | 단순하고 명확하지만 비효율적 |
| 비동기 서버 | 효율적이지만 복잡하며 관리가 필요 |
| 코루틴 | JVM 환경에서 가장 실용적인 비동기 모델 |
| 동기 방식 필요 서비스 | 순서, 트랜잭션, 일관성이 중요한 서비스 |
| 권장 접근법 | 핵심 로직은 동기, 주변 I/O는 비동기로 분리 |
결론
- 동기 서버는 단순하지만 리소스 효율이 낮다.
- 비동기 서버는 효율적이지만 설계와 디버깅이 복잡하다.
- 순서와 데이터 무결성이 중요한 서비스는 동기가 필수이고,
트래픽과 병렬성이 중요한 서비스는 비동기가 유리하다. - 코루틴은 JVM 환경에서 가장 현실적이고 강력한 비동기 도구지만,
절대적인 정답은 아니다. - 진짜 중요한 것은 서비스의 성격에 따라 동기와 비동기를 적절히 혼합하는 설계이다.
'Backend' 카테고리의 다른 글
| 동시성 문제 해결 (Kotlin, Spring Boot) (3) | 2025.08.07 |
|---|---|
| Preflight란? (CORS) (0) | 2025.04.28 |
| 구글 유튜브 API (YouTube Data API) 활용 (0) | 2025.03.18 |
| JetBrains IDE에서 GitHub Copilot 사용하기 (0) | 2025.03.17 |
| Kotlin 백엔드 개발자를 위한 Java 개념 정리 (2) | 2025.03.17 |