Backend

비동기 처리를 지원하는 모델(스레드 기반/이벤트 루프 기반)

지미닝 2024. 6. 23. 23:03

비동기(Asynchronous) 처리


개발을 하다보면 비동기라는 용어를 많이 접하게 된다. 비동기라 함은, 특정 작업의 완료를 기다리지 않고 다른 작업을 동시에 수행하는 것이다. 곧, 서버에서 데이터를 요청하고 응답을 받아야 한다면, 응답이 오는 것과 관계없이 작업을 계속 이어나가 병렬로 작업을 동시에 처리하여 흐름이 멈추거나 지연되지 않는 것이다.

왼쪽이 동기, 오른쪽이 비동기

왜 웹은 비동기를 지원해야하는가?


 주로 성능과 사용자 경험을 향상시키기 위해서 필요하다. 웹 애플리케이션의 효율성을 높이고, 대규모의 동시 요청을 효과적으로 처리하며, 사용자의 대기 시간을 줄이는 데 중요한 역할을 한다.

  1. 성능 향상
    • 효율적인 자원 사용: I/O 작업을 수행하는 동안 다른 요청을 동시에 처리할 수 있다.
    • 대기시간 감소: 데이터베이스 쿼리나 파일 다운로드와 같이 시간이 많이 걸리는 작업을 비동기로 처리하면 클라이언트가 요청을 더 빠르게 받을 수 있다.
  2. 동시성 지원
    • 대규모 동시 요청 처리: 웹 애플리케이션은 종종 수백, 수천, 수백만 명의 사용자로부터 동시 요청을 받을 수 있다. 비동기 처리를 통해 이러한 대규모 동시 요청을 효율적으로 처리할 수 있으며, 서버의 병목 현상을 줄일 수 있다.
  3. 사용자 경험 개선
    • 빠른 응답 시간: 비동기 처리 덕분에 서버는 긴 작업을 백그라운드에서 처리하고, 사용자에게 즉시 응답을 반환할 수 있다.
  4. 리소스 사용 최적화
    • 서버 부하 감소: 비동기 처리는 블로킹되지 않는 방식으로 I/O 작업을 처리하기 때문에, 서버 자원을 보다 효율적으로 사용하여 부하를 줄일 수 있다.
  5. 비동기 모델의 현대적 요구
    • 실시간 데이터 처리: 실시간 데이터를 요구하는 현대적 웹 애플리케이션에서는 비동기 처리가 필수적이다.
    • API와의 통신: 클라이언트와 서버 간의 통신이 많은 현대적 웹 애플리케이션에서는 비동기 요청을 통해 데이터를 교환한다.

 

스레드 기반 비동기 처리


 스레드 기반 비동기 처리는 다중 스레드를 사용하여 비동기 작업을 수행한다.

  1. 멀티스레딩: 작업마다 새로운 스레드를 생성하거나 스레드 풀에서 스레드를 가져와 작업을 수행한다.
  2. 병렬 처리: 스레드들이 병렬로 실행되므로, 여러 작업을 동시에 처리할 수 있다.
  3. 복잡성: 스레드 간의 데이터 공유와 동기화를 관리해야 하므로, 복잡한 동시성 제어가 필요하다.
  4. 자원 소비: 각 스레드는 시스템 자원을 사용하므로 스레드가 많아지면 메모리와 CPU 자원의 사용량이 증가할 수 있다.

 

이벤트 루프 기반 비동기 처리


이벤트 루프 기반 비동기 처리는 단일 스레드에서 이벤트 루프를 사용하여 비동기 작업을 관리한다.

  1. 이벤트 루프: 단일 스레드에서 이벤트 루프가 이벤트(입력/출력 작업 등)를 관리하고, 작업이 완료될 때마다 콜백 함수를 호출한다.
  2. 논블로킹 IO: IO 작업이 완료될 때까지 기다리지 않고, 다른 작업을 계속 처리할 수 있다.
  3. 간단한 동시성: 단일 스레드에서 작업이 순차적으로 처리되므로, 복잡한 동기화가 필요 없다.
  4. 자원 절약: 스레드 기반 방식에 비해 자원 소모가 적다.

 

만약 비동기 처리 환경에서 동기 함수를 사용한다면..?

 특히나 이번에 FastAPI를 하면서 겪은 일인데, 비동기 처리 환경에서 실수로 동기(Sync)함수를 사용하면 심각한 문제를 발생시킬 수 있다.

 

 이벤트 루프를 차단시킬 수 있다. 즉, 이렇게 되면 이 스레드는 작업이 완료될 때까지 다른 작업을 처리할 수 없게 한다. 동기 함수는 작업이 완료될 때까지 현재 스레드를 block하게 되므로, 이벤트 루프가 차단된다. 이로 인해서 다른 비동기 작업들이 지연되거나 실행되지 않을 수 있다. 이로 인해서 시스템 응답 시간이 증가하고, 동기 작업이 중첩된다면 응답 시간이 급격하게 늘어난다. 또한, CPU와 같은 시스템 자원을 효율적으로 못 쓰게 되고 자원 활용도가 매우 떨어진다. 또한, 동기 함수는 일반적으로 동시성 문제를 해결하는 메커니즘이 부족하다. 이벤트 루프는 순차적으로 처리해서 동시성 문제를 최소화하지만, 동기 함수는 여러 작업을 동시에 처리할 수 없고, 이러한 작업들이 이벤트 루프에서 실행된다면 동시성 문제를 발생시킬 수 있다.

 

 라이브러리 또한 조심해야한다. 비동기 라이브러리를 선택해서 사용하여야 한다. 항상 타사 모듈을 사용할 때, 그 함수가 동기인지 비동기인지 확인하고, 동기 함수가 이벤트 루프에서 직접 호출되도록 하면 안된다.

 

 

비교


특성 스레드 기반 비동기 처리 이벤트 루프 기반 비동기 처리
처리 방식 다중 스레드 단일 스레드 + 이벤트 루프
동시성 관리 복잡한 동기화 필요 단일 스레드로 간단한 동기화
자원 소비 높은 자원 소비 낮은 자원 소비
성능 높은 병렬 처리 성능, 그러나 스레드 수에 제한 낮은 병렬 처리 성능, 그러나 IO 처리에 유리
복잡성 복잡한 스레드 관리와 동기화 필요 비교적 간단한 코드 구조

 

 


 스레드 기반 비동기 처리는 고성능 병렬 처리가 필요할 때 유용하지만, 자원 소비가 많고 복잡한 동기화 문제가 있다. 이벤트 루프 기반 비동기 처리는 자원 소비가 적고 코드가 간단하지만, 병렬 처리 능력은 상대적으로 낮다. 두 방식 모두 비동기 처리 모델을 지원하지만, 특정 요구 사항에 맞는 방식 선택이 중요하다.

 

Spring과 FastAPI 비교


 Spring의 경우 스레드 기반 비동기 처리를 한다. Spring WebFlux와 같은 리액티브 르포그래밍 모델을 통해서 비동기 작업을 처리할 수 있다.

 

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
public class AsyncService {

    @Async
    public void performAsyncTask() {
        // 비동기 작업 수행
        System.out.println("Asynchronous task running");
    }
}

 

 반면 FastAPI의 경우 이벤트 루프 기반 비동기 처리를 한다. 따라서 단일 스레드에서 이벤트 루프를 사용하여 비동기 작업을 관리한다.

from fastapi import FastAPI
import asyncio

app = FastAPI()

@app.get("/async-task")
async def async_task():
    await asyncio.sleep(1)
    return {"message": "Asynchronous task completed"}

 

 

참고 게시물

https://livvjh.com/posts/develop/fastapi-beginner/

https://inpa.tistory.com/entry/%F0%9F%8C%90-js-

async#%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98_%EB%8F%99%EA%B8%B0%EC%99%80_%EB%B9%84%EB%8F%99%EA%B8%B0