aiohttp signedURL 테스트 및 미반영 결론 도출 - 100-hours-a-week/5-yeosa-wiki GitHub Wiki
1. 테스트 세팅 변경 사항
- aiohttp + signedURL
- signedURL을 k6 서버에서 사전 생성
- k6 서버에서 signedURL을 이용하여 FastAPI 서버에 요청 전송
- FastAPI 서버에서 signedURL을 aiohttp를 이용해 비동기 read
a. VM 인프라 구성
역할 | 인스턴스 스펙 | 설명 |
---|---|---|
FastAPI 서버 | n2d-standard-4 (vCPU 4개, 메모리 16GB) |
GCS에서 이미지 30~50개 다운로드 처리 |
K6 서버 | e2-medium (vCPU 2개, 메모리 4GB) |
테스트 트래픽 생성기 |
b. FastAPI 서버 코드 요약
from fastapi import FastAPI, Query
from typing import List
from fastapi.responses import JSONResponse
import aiohttp
import asyncio
app = FastAPI()
print("서버 준비 완료")
@app.get("/")
async def ping():
return {"message": "FastAPI is running"}
@app.post("/load-images")
async def load_images(signed_urls: List[str]):
"""
클라이언트가 전달한 signed URL들을 통해 이미지 다운로드 요청.
FastAPI는 다운로드만 수행하고 signed URL은 클라이언트가 미리 발급받음.
"""
async def fetch(session, url: str):
async with session.get(url) as response:
_ = await response.read()
return url
async with aiohttp.ClientSession() as session:
tasks = [fetch(session, url) for url in signed_urls]
result = await asyncio.gather(*tasks)
return JSONResponse(
status_code=200,
content={"message": f"{len(result)}장 이미지 로딩 완료"}
)
- 요청당 30~50장의 이미지 다운로드
- 실패 시 즉시 500 응답 반환
c. K6 부하 테스트 스크립트
import http from 'k6/http';
import { check, sleep } from 'k6';
import { SharedArray } from 'k6/data';
export const options = {
vus: 30,
duration: '10m',
};
// JSON 객체 리스트에서 url만 추출
const signedUrls = new SharedArray('signedUrls', function () {
const raw = JSON.parse(open('./signed_urls_100.json'));
return raw.map(item => item.url); // "url"만 추출해서 사용
});
function getRandomUrlList() {
const total = Math.floor(Math.random() * 21) + 30;
const copy = Array.from(signedUrls); // 복제
const shuffled = copy.sort(() => 0.5 - Math.random()); // 복제본 정렬
return shuffled.slice(0, total);
}
export default function () {
const urls = getRandomUrlList();
const payload = JSON.stringify(urls); // 서버가 요구하는 구조
const headers = { 'Content-Type': 'application/json' };
const res = http.post('http://10.178.0.2:8000/load-images', payload, { headers });
check(res, {
'status is 200': (r) => r.status === 200,
});
sleep(1);
}
2. 테스트 결과 요약
-
첨부 파일:
-
평균 응답 시간(P95): 약
2초 후반
-
요청 성공률:
요청 실패 발생
지표 수치 P95 응답 시간 2초 후반
3. 테스트 시도 이유
- 기존 방식:
download_byte
(S3 SDK) - 기존 방식의 단점
- download_byte는 동기 함수: i/o 차단 발생
- 이로 인해 네트워크 요청/응답 처리 시간이 길어질 수 있음
- 개선 시도:
signed URL + aiohttp
(비동기 방식) - 기대 효과
- S3 객체 접근용 signed URL을 서버에서 미리 발급
- 클라이언트는
aiohttp
를 이용해 비동기적으로 read 처리 read()
는 asyncio 기반 → I/O 블로킹 최소화- 요청 시 인증 헤더 없이 URL에 서명 포함 → 전송 요청이 더 가벼움
4. 병목 원인 분석
- 각 signed URL 요청마다 개별 HTTP 커넥션이 생성됨
- 따라서 매 요청마다 TCP 3-way handshake + TLS handshake
- → 커넥션 생성 비용이 누적되어 응답 시간 증가
5. 결론
항목 | 평가 |
---|---|
signed URL + aiohttp |
비동기 처리와 요청 경량화는 장점이지만, 커넥션 재사용 불가로 오히려 지연 발생 |
download_byte |
동기 방식이지만, persistent connection 유지 덕분에 안정적인 성능 확보 |
6. 부하테스트 회고
- 단순히 비동기 처리가 항상 빠르지 않다
- 커넥션 수립 비용, TLS 핸드셰이크, 연결 재사용 여부가 중요한 변수
- 단순히 "비동기"라는 개념 자체만을 근거로 기술을 도입하기보다는, P95, P99와 같은 지연 시간 지표를 기반으로 실제 성능을 분석하고, 그 결과를 바탕으로 기술 선택을 결정하는 것이 중요하다.