공정하고 격리된 방식으로 웹 프레임워크를 벤치마크하는 방법 | Mahdi Shamlou
Source: Dev.to
여러분, 안녕하세요! 마흐디 샤몰루입니다 👋
온라인에서 웹 프레임워크를 비교하는 많은 글을 봤지만, 대부분 편향되었거나 오래됐거나 재현하기 어려웠습니다. 그래서 저는 모든 웹 프레임워크를 벤치마크할 수 있는 실용적인 방법을 공유하고자 합니다. 모든 것을 격리하고, 공정하며, 재현 가능하게 유지합니다.
우리는 격리를 위해 Docker를 사용하고, 부하 테스트를 위해 k6를 사용하며, 간단한 예시로 Python 프레임워크인 FastAPI와 Flask를 사용할 것입니다. 이 접근 방식은 Node.js, Go, Java, Rust 등 다른 어떤 언어에도 적용할 수 있습니다.
개요
웹 프레임워크 벤치마킹은 까다로울 수 있습니다. 결과에 영향을 주는 많은 요인이 있습니다:
- CPU 및 메모리 가용성
- 워커/스레드 수
- 백그라운드 프로세스
- 라우팅, 로깅, 데이터베이스, I/O
공정한 비교를 위해서는 다음이 필요합니다:
- 고정된 CPU 및 메모리 제한이 있는 Docker 컨테이너
- 각 프레임워크에서 동일한 라우트 또는 엔드포인트
- k6(또는 유사 도구)를 사용한 제어된 부하 테스트
- 이후 분석을 위한 결과 저장
프로젝트 구조
mkdir web_framework_benchmarks
cd web_framework_benchmarks
mkdir framework1 framework2 k6-tests results
framework1과 framework2을 비교하고 싶은 어떤 프레임워크로든 교체할 수 있습니다. 예시로 간단한 /hello 엔드포인트를 사용합니다.
FastAPI (Python)
# app.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/hello")
def hello():
return {"message": "hello world"}
Flask (Python)
# app.py
from flask import Flask, jsonify
app = Flask(__name__)
@app.route("/hello")
def hello():
return jsonify({"message": "hello world"})
Node.js, Go, Java 등에서도 동일한 엔드포인트를 구현할 수 있으며, 기능은 동일하게 유지합니다. 필요에 따라 I/O‑집중 작업을 시뮬레이션하기 위해 슬립 라우트를 추가할 수도 있습니다.
Dockerfiles (공정한 비교)
FastAPI Dockerfile
# Dockerfile (FastAPI)
FROM python:3.11-slim
WORKDIR /app
COPY . .
RUN pip install fastapi uvicorn gunicorn
CMD ["gunicorn", "-k", "uvicorn.workers.UvicornWorker", "-w", "1", "-b", "0.0.0.0:8000", "app:app"]
Flask Dockerfile
# Dockerfile (Flask)
FROM python:3.11-slim
WORKDIR /app
COPY . .
RUN pip install flask gunicorn
CMD ["gunicorn", "-w", "1", "-b", "0.0.0.0:8000", "app:app"]
✅ 이제 두 컨테이너 모두 동일한 워커 수와 동일한 CPU/메모리 제한을 가지고 있어 공정한 기준을 보장합니다.
k6를 이용한 부하 테스트
k6 스크립트 (JavaScript)
// k6-tests/framework1.js (or framework2.js)
import http from "k6/http";
import { sleep } from "k6";
export const options = {
stages: [
{ duration: "30s", target: 50 }, // ramp‑up
{ duration: "1m", target: 200 }, // hold load
{ duration: "30s", target: 0 }, // ramp‑down
],
thresholds: {
"http_req_duration": ["p(95)<200"], // 95% of requests should be <200 ms
},
};
export default function () {
http.get("http://localhost:8001/hello");
sleep(1);
}
테스트 실행
mkdir -p results
k6 run --out json=results/framework1.json k6-tests/framework1.js
k6 run --out json=results/framework2.json k6-tests/framework2.js
JSON 출력은 어떤 언어나 플로팅 도구로도 처리할 수 있습니다.
결과 분석
아래는 평균 지연 시간, 95번째 백분위 지연 시간, 요청 수, 실패율을 추출한 뒤 평균 응답 시간을 그래프로 표시하는 최소한의 Python 스크립트입니다.
# analyze.py
import json
import numpy as np
import matplotlib.pyplot as plt
files = {
"Framework1": "results/framework1.json",
"Framework2": "results/framework2.json"
}
summary = {}
for name, file in files.items():
durations, fails, total = [], 0, 0
with open(file) as f:
for line in f:
obj = json.loads(line)
if obj.get("type") == "Point":
metric = obj.get("metric")
if metric == "http_req_duration":
durations.append(obj["data"]["value"])
if metric == "http_req_failed":
fails += obj["data"]["value"]
total += 1
if durations:
summary[name] = {
"avg": np.mean(durations),
"p95": np.percentile(durations, 95),
"requests": len(durations),
"fail_rate": fails / total if total else 0,
}
print(summary)
plt.bar(summary.keys(), [summary[n]["avg"] for n in summary])
plt.title("Average Response Time (ms)")
plt.ylabel("Milliseconds")
plt.show()
주요 요점
- Docker 격리는 벤치마크를 재현 가능하게 합니다.
- 워커 수 및 CPU 제한은 컨테이너 간에 일치해야 합니다.
- 간단한 라우트는 Flask가 더 빠르게 보이게 할 수 있습니다; 속지 마세요.
- Async/I/O가 많은 라우트는 FastAPI(또는 다른 async 프레임워크)의 강점을 보여줍니다.
- 항상 실제 워크로드를 벤치마크하세요, 작은 예제만으로는 안 됩니다.
Repository
Dockerfile, k6 스크립트 및 분석 코드를 포함한 전체 예제는 다음에서 확인할 수 있습니다:
https://github.com/mahdi-shamlou/web_framework_benchmarks
레포지토리를 클론하고, Docker + k6 설정을 직접 실행해 보세요. FastAPI 및 Flask 예제를 탐색하거나, 비교를 위해 추가 프레임워크를 기여할 수 있습니다.