## 🗓 이번 주 개요
- 주차: Week 01 (5/18~5/22)
- 키워드: #Python #AsyncIO #EventLoop
# <주제 1> — time.sleep(1) 하나가 비동기 서버를 멈출 수 있다.
## 핵심 개념
1. 비동기는 여러 작업이 await 지점에서 이벤트 루프에게 제어권을 양보하는 구조다.
2. ‘await asyncio.sleep(1)’은 “난 기다릴 테니까 다른 작업을 처리해도 돼” 라는 의미로 이해하면 된다.
3. ‘time.sleep(1)’은 현재 스레드를 실제로 멈춰버린다.
4. 이벤트 루프도 같은 스레드 위에서 돌고 있다면, 이벤트 루프 자체가 멈춘다.
## 내가 이해한 방식
비동기 서버를 “여러 요청을 번갈아 처리하는 직원”이라고 생각해보자.
이 직원은 A 손님이 서류를 기다리는 동안 B 손님을 처리하고, B 손님이 결제를 기다리는 동안 C 손님을 처리한다.
이게 가능한 이유는 각 손님이 기다리는 시점에 직원에게 자리를 양보하기 때문이다!
그런데 ‘time.sleep(1)’은 “직원이 1초 동안 눈을 감고 아무 창구도 보지 않는 것”에 가깝다.
A 손님만 기다리는 게 아니라 B 와 C 손님도 모두 멈춘다.
그래서 서버 프로세스는 죽지 않았지만, 요청 처리는 진행되지 않는 이상한 상태가 생길 수 있다.
> 예를 들어, 카카오페이지 서버에서 작품 상세 화면을 열 때 작품 정보, 댓글 수, 추천 작품 등을 비동기로 가져온다고 가정해보자.
추천 작품을 가져오는 함수 안에 실수로 'time.sleep(1)' 이 들어가 있다면,
1초 동안 추천 요청 하나만 느려지는 것이 아니라 같은 이벤트 루프에서 처리 중이던 다른 사용자 요청들도 함께 밀릴 수 있다.
> 구체적인 예를 들어보면
1. 다른 사용자의 작품 상세 페이지 로딩이 갑자기 느려지거나
2. 댓글 수 API 응답이 늦게 오거나
3. 홈 화면 추천 목록이 한동안 로딩 상태로 남아 있을 수 있다.
> 사용자 입장에서는
1. 서버가 죽었나?
2. 왜 이렇게 느리지?
이렇게 느낄 수 있다.
**하지만 실제로는 서버가 종료된 것이 아니라 이벤트 루프가 'time.sleep()' 같은 코드에 묶여 있었던 상황인 것이다.**
## 코드로 보면
```jsx
import time
async def fetch_recommendations():
time.sleep(1) # 이벤트 루프를 막는 동기 블로킹
return ["작품A", "작품B"]
```
> 이 코드는 좋지 않은 코드인 것이고
```jsx
import asyncio
async def fetch_recommendations():
await asyncio.sleep(1) # 기다리는 동안 제어권 양보
return ["작품A", "작품B"]
```
> 이렇게 변경해야 이벤트 루프가 다른 요청도 계속 처리할 수 있다고 이해했다.
**즉, 비동기 서버에서 중요한 것은 단순히 'async def'를 사용하는 것이 아니라
이벤트 루프가 다른 요청을 계속 처리할 수 있도록 흐름을 막지 않는 코드를 작성하는 것으로 이해했다.**
## 최종 결론
처음에는 ‘sleep(1)’ 이나 ‘await asyncio.sleep(1)’이나 둘 다 1초 기다리는 코드라고 생각 했지만, 비동기에서는 “무엇을 기다리느냐” 보다 “기다리는 동안 제어권을 양보하느냐”가 중요했다.
결국 비동기 코드를 작성한다는 것은 ‘async def’를 붙이는 것이 아니라, 코드 흐름 전체가 이벤트 루프를 막지 않도록 설계하는 일이라고 이해했다.
## 참고
강의 내용이 실제 서비스에서는 어떤 식으로 사용되는지 궁금해서,
카카오페이지 상황을 예시로 직접 연결해보며 정리했다.
# <주제 2> — 비동기 동시 처리는 강력하지만, 무제한 동시성은 위험하다.
## 핵심 개념
1. 'asyncio.gather()' 는 여러 비동기 작업을 동시에 실행하고, 모든 결과를 한 번에 모아주는 함수이다.
2. 비동기는 I/O 대기 시간을 줄이는 데 강점이 있지만,
동시에 너무 많은 요청을 실행하면 외부 API나 DB 같은 시스템 자원에 큰 부담을 줄 수도 있다.
## 내가 이해한 방식
‘gather’는 여러 명의 배달 기사를 한 번에 출발시키는 것으로 생각해보자!
3명 정도를 보내면 빠르고 효율적이다.
하지만 10,000 명을 한 번에 출발시키면 길이 막히고, 가게도 한 번에 처리를 못하고, 주문 시스템도 버티기 어려울 것이다.
**즉, 비동기의 장점은 무작정 많이 보내는 것이 아니라 놀고 있는 대기 시간을 줄이고 시스템이 감당할 수 있는 범위 안에서 조율하는 것이라고 이해했다.**
> 카카오택시 상황으로 이해를 해보자 한 번
카카오택시에서 사용자의 위치 근처에 배차 후보가 10,000개 있다고 생각해 보자.
각 후보에 대해 외부 지도 API, 거리 계산 API, 기사 상태 조회 API를 호출해야 한다고 할 때, 비동기라고 해서 한 번에 10,000개 요청을 보내면 위험하다.
순간적으로 너무 많은 네트워크 요청이 발생하면서 외부 API의 요청 제한을 초과하거나 서버의 메모리, CPU, DB 연결 자원이 부족해질 수 있기 때문이다.
( 짧은 순간에 너무 많은 요청이 몰리면 외부 API 서버나 우리 서버가 동시에 감당하지 못한다는 뜻 )
```jsx
results = await asyncio.gather(
*[fetch_driver_status(driver) for driver in drivers]
)
```
> 이 코드는 'drivers' 라는 기사 후보 목록을 돌면서, 각 기사마다 'fetch_driver_status(driver)' 라는 상태 조회 작업을 만든다.
> 그리고 'asyncio.gather()' 가 작업들을 한 번에 실행한다.
> 즉, 후보가 3명이면 상태 조회 요청 3개가 동시에 나가고,
> 후보가 10,000명이면 상태 조회 요청 10,000개가 거의 동시에 나갈 수 있다.
"기사 후보들의 상태를 한 번에 조회한다"는 간단한 코드처럼 볼 수 있지만,
서버만 비동기로 빨라지는 것이 아니라, 외부 API도 10,000개의 요청을 한 번에 받아야 한다는 것이다.
짧은 순간에 너무 많은 요청이 몰리면 외부 API가 요청 제한을 걸 수 있고,
서버도 동시에 많은 요청을 관리해야 하므로 메모리와 CPU 사용량이 급격히 늘어날 수 있다.
또 DB 조회까지 연결되어 있다면, DB와 연결할 수 있는 통로가 부족해져서 다른 정상 요청들까지 기다리게 만든다.
**따라서, 비동기는 잘 쓰면 처리량을 높일 수 있는 장점이 있지만 제한 없이 막 쓰면 장애를 더 빠르게 만드는 단점이 생길 수 있다는 것을 알았다.**
> 또한 사용자가 화면을 나갔는데 추천하는 API 가 계속 돌게 되는 경우를 생각해보자.
카카오페이지 같은 어플리케이션은 사용자가 작품 상세 화면에 들어오면 추천 작품 API를 호출한다고 가정한다.
그런데 사용자가 바로 뒤로 가기를 눌러 화면을 이탈했다면, 그 추천 결과는 더 이상 UI에 표시될 필요가 없다.
하지만 이미 시작한 비동기 작업을 취소하지 않는다면 서버는 계속 추천 API 응답을 기다릴 것이고, 결과를 처리하면서 메모리를 사용할 것이다.
( 사용자는 이미 화면을 떠났는데 서버는 계속 일하고 있는 셈이다. )
**이런 상황을 생각하면 비동기 작업은 “시작하는 법”만 중요한 것이 아닌 “언제 취소할 것인지”, “얼마나 기다릴 것인지”, “동시에 몇 개 까지 허용할 것인지” 까지 설계하는게 중요하다고 느끼고 있다.**
그래서 이러한 상황을 해결하기 위해 강의에서 배운 내용인 **Semaphore, timeout, cancel**을 사용한다!
```jsx
sem = asyncio.Semaphore(30)
async def fetch_driver_status(driver):
async with sem:
return await call_external_api(driver)
```
‘Semaphore(30)’ 을 사용하면 전체 후보가 10,000개 여도 실제로 외부 API를 동시에 호출하는 작업은 최대 30개로 제한된다.
( 비동기의 장점은 살리되, 외부 시스템이 감당할 수 있는 수준으로 조절하는 방식. )
```jsx
try:
result = await asyncio.wait_for(
fetch_recommendations(user_id),
timeout=2.0,
)
except asyncio.TimeoutError:
result = []
```
외부 API가 느려졌을 때 무한정 기다리면 사용자 경험도 나빠지고 서버 리소스도 오래 묶이게 된다.
그래서 timeout = 2.0 을 넣어 2초까지만 기다리고, 실패하면 빈 추천 목록이나 기본값을 내려주는 정책이 필요하다.
```jsx
task = asyncio.create_task(fetch_recommendations(user_id))
task.cancel() # 사용자가 화면을 떠났거나 더 최신 요청이 들어온 경우
```
더 이상 결과가 필요 없는 작업은 취소해야 한다.
비동기 작업을 끝까지 실행시키는 것이 항상 좋은 것이 아니라, 필요 없는 작업을 빨리 중단하는 것도 시스템 자원을 지키는 설계이다!
## 최종 결론
비동기는 “여러 일을 동시에 해서 빠르게 만드는 것” 이라고 이해했지만 비동기가 단순히 빠르게 만드는 코드가 아니라 대기 시간, 외부 시스템의 한계, 실패 가능성까지 고려해서 작업의 흐름을 조율하는 설계 방식이라는 생각을 하게 되었다.
‘gather()’ 하나로 여러 작업을 동시에 실행할 수 있다는 점보다 그 동시성을 어디까지 허용할 것인지 정하는 ‘Semaphore’, 얼마나 기다릴 것인지 정하는 ‘timeout’, 더 이상 필요 없는 작업을 멈추는 ‘cancel’이 현업에서 엄청 중요하다는 것을 느꼈다!
## 참고
강의 내용이 실제 서비스에서는 어떤 식으로 사용되는지 궁금해서,
카카오택시 같은 상황을 예시로 직접 연결해보며 정리했다.
## 🔁 이번 주 회고 (KPT)
- Keep 유지하고 싶은 습관:
> 강의를 나름 꼬박꼬박 들은 것 당일 강의는 반드시 당일에 봐야함!
> 강의록 작성하는게 지금은 즐겁다. 예시를 들면서 공부하는 걸 좋아하는데 내가 배운 CS 지식이 왜 필요한지 이해가 잘 들어서 더 즐겁게 느껴지는 거 같다.
- Problem 아쉬웠던 점:
> 당일 날 강의 들은 내용을 복습하고 예시를 찾아보면서 "아 이게 이래서 쓰는거구나~" 를 하지 않았다. 그래서 강의를 들으면서 배운 개념들이 기억이 많이 지워진 거 같다..
- Try 다음 주에 시도할 것:
> 지금 배우는 코드 자체가 많이 길고 어려운 내용이 아니니, 코드를 직접 쳐보면서 코드를 확실하게 이해하는게 좋을 거 같음.
## 🎯 다음 주 목표
- 강의를 들은 당일 날 혹은 다음날 회고 노트를 작성할 것
> 강의 들으면서 재밌는 내용과 신기한 내용, 새로 배운 내용들을 일단 노트에 따로 작성해 뒀는데
결국 당일 혹은 다음날 스스로 공부하지 않는다면 기억에 덜 남게 된다고 생각했음
'카카오테크캠퍼스' 카테고리의 다른 글
| 카카오테크 캠퍼스 4기 CONNECT DAY (0) | 2026.07.09 |
|---|---|
| 나나를 만들면서 발견한 품질 엔지니어링 (0) | 2026.07.01 |