공정하고 격리된 방식으로 Web Frameworks 벤치마크하는 방법 | Mahdi Shamlou
Source: Dev.to
안녕하세요 여러분! Mahdi Shamlou입니다 👋
온라인에서 웹 프레임워크를 비교하는 글을 많이 보았지만, 대부분 편향되었거나 오래됐거나 재현하기 어렵습니다. 그래서 저는 웹 프레임워크를 공정하고 격리된 방식으로 벤치마킹하는 실용적인 방법을 공유하고자 합니다.
우리는 격리를 위해 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 출력은 어떤 언어나 시각화 도구로도 처리할 수 있습니다.
결과 분석
Below is a minimal Python script that extracts average latency, 95th‑percentile latency, request count, and failure rate, then plots the average response time.
# 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 프레임워크)의 강점을 보여줍니다.
- 실제 워크로드를 항상 벤치마크하세요, 작은 예제만이 아니라.
저장소
Dockerfile, k6 스크립트 및 분석 코드를 포함한 전체 예제는 다음에서 확인할 수 있습니다:
https://github.com/mahdi-shamlou/web_framework_benchmarks