Best API 프레임워크와 Benchmark (2022년)
포스트
취소

Best API 프레임워크와 Benchmark (2022년)

언어에 상관 없이 API 용 웹 프레임워크에 대해 조사하고 성능 벤치마킹 자료를 살펴봅니다.

최고의 웹 프레임워크 (API 서버)

원글에 더해 별 개수 조사와 프레임워크를 추가했습니다.

출처: Best API Frameworks For Fast Performance (2022.7)

1위. Gin Web Framework - 별 62.6k 개

마이크로서비스 바람이 불더니, Express.js 를 넘어섰네요. 백엔드 개발의 대세가 된 Go 언어는 도커와 쿠버네티스와도 잘 어울립니다.

예제 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// example.go

package main

import (
  "net/http"

  "github.com/gin-gonic/gin"
)

func main() {
  r := gin.Default()
  r.GET("/ping", func(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{
      "message": "pong",
    })
  })
  r.Run()  // listen and serve on 0.0.0.0:8080
}

설치 및 실행

1
2
3
4
5
# 라이브러리 다운로드
$ go get -u github.com/gin-gonic/gin

# 예제 실행 => 0.0.0.0:8080/ping
$ go run example.go

Go-fastapi: a library to quickly build APIs

go-fastapi is a library to quickly build APIs. It is inspired by Python’s popular FastAPI library.

예제 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package main

import (
    "github.com/gin-gonic/gin"
    "github.com/sashabaranov/fastapi"

    "encoding/json"
    "fmt"
)

type EchoInput struct {
    Phrase string `json:"phrase"`
}

type EchoOutput struct {
    OriginalInput EchoInput `json:"original_input"`
}

func EchoHandler(ctx *gin.Context, in EchoInput) (out EchoOutput, err error) {
    out.OriginalInput = in
    return
}

func main() {
    r := gin.Default()

    myRouter := fastapi.NewRouter()
    myRouter.AddCall("/echo", EchoHandler)

    // for OpenAPI/Swagger
    swagger := myRouter.EmitOpenAPIDefinition()
    swagger.Info.Title = "My awesome API"
    jsonBytes, _ := json.MarshalIndent(swagger, "", "    ")
    fmt.Println(string(jsonBytes))

    // 라우터 url 맵핑
    r.POST("/api/*path", myRouter.GinHandler) // must have *path parameter
    r.Run()
}

설치 및 실행

1
2
3
4
5
6
7
$ go get github.com/sashabaranov/go-fastapi

$ go run example.go

# Try it:
$ curl -H "Content-Type: application/json" -X POST --data '{"phrase": "hello"}' localhost:8080/api/echo
# ==> {"response":{"original_input":{"phrase":"hello"}}}

2위. Express.js + Node.js - 별 58.2k

가장 대중적인 javascript 언어에 오랜 시간 사용되어 온 웹 프레임워크

예제 코드

1
2
3
4
5
6
7
8
9
10
11
const express = require('express')
const app = express()
const port = 3000

app.get('/', (req, res) => {
  res.send('Hello World!')
})

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`)
})

설치 및 실행

1
2
3
$ npm install express --save

$ node app.js

3위. FastAPI - 별 49k

Python 생태계가 커지면서 빠르게 사용자층이 늘어난 FastAPI. Nodejs 의 비동기 방식을 채택하여 성능을 높였다.

예제 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from typing import Union

from fastapi import FastAPI

app = FastAPI()


@app.get("/")
def read_root():
    return {"Hello": "World"}


@app.get("/items/{item_id}")
def read_item(item_id: int, q: Union[str, None] = None):
    return {"item_id": item_id, "q": q}


#######################################
##  또는 async 호출로
##

@app.get("/")
async def read_root():
    return {"Hello": "World"}


@app.get("/items/{item_id}")
async def read_item(item_id: int, q: Union[str, None] = None):
    return {"item_id": item_id, "q": q}

설치 및 실행

1
2
3
4
$ pip install fastapi
$ pip install "uvicorn[standard]"

$ uvicorn main:app --reload

4위. Fastify - 별 24.8k

Express.js 만 알고 있었는데, 훨씬 뛰어난 성능을 가진 Fastify 가 있었다. Nestjs 의 http 엔진을 Fastify 로 바꿔서 사용할 수 있다.

  • Nestjs - Performance (Fastify)
    • Express 엔진보다 2배 정도 빠르다고
    • 그럼에도 공식적으로 Express 를 채택하는 이유는 호환성과 서드파티가 많아서

예제 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// ESM
import Fastify from 'fastify'

const fastify = Fastify({
  logger: true
})

// sync 호출
fastify.get('/sync', (request, reply) => {
  reply.send({ hello: 'world' })
})

// async 호출
fastify.get('/async', async (request, reply) => {
  reply.type('application/json').code(200)
  return { hello: 'world' }
})

// Run the server!
fastify.listen({ port: 3000 }, (err, address) => {
  if (err) throw err
  // Server is now listening on ${address}
})

설치 및 실행

1
2
3
4
5
$ yarn add fastify

$ npm init fastify

$ npm start

Fastify 에서 제시하는 Benchmarks

Machine: EX41S-SSD, Intel Core i7, 4Ghz, 64GB RAM, 4C/8T, SSD.

Method:: autocannon -c 100 -d 40 -p 10 localhost:3000 * 2, taking the second average

FrameworkVersionRouter?Requests/sec
Express4.17.314,200
hapi20.2.142,284
Restify8.6.150,363
Koa2.13.054,272
Fastify4.0.077,193
http.Server16.14.274,513

5위. Fiber - 별 22.2k

Fiber 는 Go 언어에서 가장 빠른 Fasthttp 엔진 기반으로 만들어진 웹 프레임워크. 빠른 개발과 최적의 메모리 활용을 제공한다.

Golang WebFrameworks Performance Benchmark

예제 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import "github.com/gofiber/fiber/v2"

func main() {
    app := fiber.New()

    app.Get("/", func(c *fiber.Ctx) error {
        return c.SendString("Hello, World 👋!")
    })

    app.Listen(":3000")
}

6위. Phoenix + Elixir - 별 18.5k

몰랐던 웹 프레임워크인데, 성능상 최고의 웹 프레임워크이다. 분산시스템을 기반으로 동작하기 때문에 무한히 수평확장을 할 수 있다. 별도의 소켓 기반 클라이언트가 필요하다.

Elixir (엘릭서) 언어란?

Elixir는 José Valim이 개발한 함수형 프로그래밍 언어로, 고성능 분산 Erlang VM (BEAM)에서 돌아갑니다. 따라서 엘릭서 컴파일러는 소스코드를 Erlang VM 바이트 코드로 컴파일합니다.

  • Elixir 창시자인 José Valim은 이러한 Erlang에 특징에서 메타프로그래밍, 다형성, 프로덕션 환경에서 사용할 수 있는 툴과 같이 일상적으로 사용하는 기능에 대한 지원을 추가하여 Elixir를 만들었습니다.
  • 동시성과 메시지 전달은 언어의 기본입니다. Erlang으로 작성된 애플리케이션은 종종 수백 또는 수천 개의 경량 프로세스로 구성됩니다. Erlang 프로세스 간의 컨텍스트 전환은 일반적으로 C 프로그램의 스레드 간 전환보다 1-2배 저렴합니다.
  • Erlang으로 작성된 애플리케이션은 통신,메시징과 관련된 서버에 강점이 있고 서로 메시지를 주고 받는 수백,수천개의 경량 프로세스로 돌아간다고 할 수 있습니다.

Phoenix Channels JavaScript client v1.6.11

예제 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let socket = new Socket("/socket", {params: {userToken: "123"}})
socket.connect()

let channel = socket.channel("room:123", {token: roomToken})

channel.on("new_msg", msg => console.log("Got message", msg) )

$input.onEnter( e => {
  channel.push("new_msg", {body: e.target.val}, 10000)
    .receive("ok", (msg) => console.log("created message", msg) )
    .receive("error", (reasons) => console.log("create failed", reasons) )
    .receive("timeout", () => console.log("Networking issue...") )
})

channel.join()
  .receive("ok", ({messages}) => console.log("catching up", messages) )
  .receive("error", ({reason}) => console.log("failed join", reason) )
  .receive("timeout", () => console.log("Networking issue. Still waiting..."))


벤치마크 : FastAPI, Fastify, Spring, Gin

출처: FastAPI vs. Fastify vs. Spring Boot vs. Gin Benchmark (01.10.2022)

저자는 Node.js와 동등하다는 FastAPI의 주장을 확인하기 위해 이 벤치마크를 수행했다고 합니다.

1) 저자의 결론

  • FastAPI는 기본적으로 빠르지 않습니다. FastAPI의 속도를 최대한 활용하려면 asyncpg와 같은 올바른 데이터베이스 드라이버를 사용해야 합니다.
  • asyncpg를 사용하더라도 FastAPI와 함께 더 빠른 json 라이브러리를 사용하여 성능을 Node.js 수준까지 끌어올려야 합니다.
  • 원시 sql 쿼리에서 json으로 이동하는 것은 ORM을 사용하는 것보다 훨씬 빠르며, 이는 개체 매핑 프로세스를 건너뛰므로 의미가 있습니다.
  • 직접 확인하지는 않았지만 컴파일된 언어가 인터프리터 언어보다 빠르다는 말을 항상 들었습니다. Java/Go는 해석된 언어의 유사한 설정에 비해 실제로 더 빠릅니다.
  • Node.js에는 Node.js 프로세스 클러스터를 시작하여 멀티 코어 시스템을 활용할 수 있는 클러스터 모듈이 있습니다.
  • 로깅은 성능에 영향을 줍니다.

최적의 조합 구성시 Node 와 Python 프레임워크의 성능이 비등

04) FastAPI + asyncpg + ujson + gunicorn 8w (4831 req/sec)
05) Fastify + pg + cluster mode 8w (without logging) (4622 req/sec)

2) The Rankings

  • 01) Spring Boot + jdbc (7886 req/sec)
    • 20) SpringBoot + JPA (844 req/sec)
  • 02) Go + pgx (7517 req/sec)
    • 03) Go + pg + SetMaxOpenConns + SetMaxIdleConns (7388 req/sec)
  • 04) FastAPI + asyncpg + ujson + gunicorn 8w (4831 req/sec)
    • 06) FastAPI + asyncpg + ujson + gunicorn 4w (4401 req/sec)
    • 07) FastAPI + asyncpg + gunicorn 4w + orjson (4193 req/sec)
    • 14) FastAPI + asyncpg + uvicorn + orjson (1885 req/sec)
    • 15) FastAPI + asyncpg + uvicorn + ujson (1711 req/sec)
    • 18) FastAPI + psycopg2 + gunicorn 4w (989 req/sec)
    • 19) FastAPI + asyncpg + gunicorn 4w (952 req/sec)
    • 21) FastAPI + psycopg2 + uvicorn + orjson (827 req/sec)
    • 23) FastAPI + SQLModel + gunicorn 4w (569 req/sec)
    • 25) FastAPI + asyncpg + uvicorn (314 req/sec)
    • 26) FastAPI + psycopg2 + uvicorn (308 req/sec)
    • 27) FastAPI + databases + uvicorn (267 req/sec)
    • 28) FastAPI + SQLModel + uvicorn (182 req/sec)
  • 05) Fastify + pg + cluster mode 8w (without logging) (4622 req/sec)
    • 09) Fastify + pg + cluster mode 8w (3417 req/sec)
    • 11) Fastify + pg (without logging) (2750 req/sec)
    • 12) Fastify + pg (2184 req/sec)
  • 08) Express.js + pg + cluster mode 8w (4145 req/sec)
    • 13) Express.js + pg (1931 req/sec)
    • 17) Nest.js + Prisma (1184 req/sec)
  • 10) Gin + database/sql + lib/pq (2966 req/sec)

  • 16) Flask + psycopg2 + gunicorn 4w (1478 req/sec)
    • 22) Flask + psycopg2 + flask run (705 req/sec)
    • 24) Flask + psycopg2 + gunicorn 1w (536 req/sec)

3) 제 나름의 정리

복잡한 비즈니스 요구가 아니면 JDBC + json 조합 고려

  • 어떤 프레임워크이든 ORM 이 붙게되면 1/4 로 성능 저하 (심지어 10배까지)
  • 성능이 우선되어야 한다면 JDBC 와 SQL + Json Mapper 조합이 좋겠다는 것
    • 코드의 유지보수, 모델부터 일체화된 디자인 등의 정석적 이득을 다소 희생
      • 사실 정석은 어디까지나 정석일뿐이고
      • ORM 은 JSON 변환 뿐만 아니라 sql 관련 기능이 많아 무겁다
    • 비교적 단순한 스키마와 참조 테이블이 얼마 안되면 ORM 안써도 된다
  • ORM 을 써야 한다면 JPA 말고 대체제를 찾아보자
    • iBatis 가 좋겠다

웹 프레임워크, ORM 등 차세대 버전들은 async 이전 중

07) FastAPI + asyncpg + gunicorn 4w + orjson (4193 req/sec)
18) FastAPI + psycopg2 + gunicorn 4w (989 req/sec)

  • SQLAlchemy 도 2.0 으로 가면서 asynio 기반으로 변환중
  • DB 어댑터들도 asyncpg, aiomysql 등으로 교체중
  • 이러한 이슈는 한 5~7년 전에도 나왔었는데, 지금이 그 타이밍인듯
    • 많은 부분이 함께 async 를 지원해야 제성능이 나오니깐

Java/Spring-boot, Go/Gin (Go/Fiber)

01) Spring Boot + jdbc (7886 req/sec)
20) SpringBoot + JPA (844 req/sec)

Node/Fastify

12) Fastify + pg (2184 req/sec)
13) Express.js + pg (1931 req/sec)
17) Nest.js + Prisma (1184 req/sec)

  • Node 계열에서는 확실히 Fastify 를 선택하는게 좋겠다는 것
  • Fastify 의 ORM 으로 prisma 를 써봐야겠다는 것

Python/FastAPI

Node 구성

12) Fastify + pg (2184 req/sec)
13) Express.js + pg (1931 req/sec)

Python 구성

21) FastAPI + psycopg2 + uvicorn + orjson (827 req/sec)
26) FastAPI + psycopg2 + uvicorn (308 req/sec)

ORJSON 예제 코드

1
2
3
4
5
6
7
8
9
from fastapi import FastAPI
from fastapi.responses import ORJSONResponse

app = FastAPI()


@app.get("/items/", response_class=ORJSONResponse)
async def read_items():
    return ORJSONResponse([{"item_id": "Foo"}])

UJSON 예제 코드

1
2
3
4
5
6
7
8
9
from fastapi import FastAPI
from fastapi.responses import UJSONResponse

app = FastAPI()


@app.get("/items/", response_class=UJSONResponse)
async def read_items():
    return [{"item_id": "Foo"}]

9. Review

  • 성능이 뛰어나다는 것과 생산성이 뛰어난 것은 의미가 다르다.
    • python 버전은 성능은 느릴지라도, 빨리 개발할 수 있어 인기가 높다.
    • 투자 대비 효과는 단연 nodejs 계열이다.
  • 풀스택 개발 언어로는 nodejs 가 적절하다. 수많은 사용 사례가 있다.
    • 반면에 go 언어는 백엔드 전용, 동시성 처리, 성능 중심이다.
  • Spring 은 과거에도, 현재도 가장 사랑받는 백엔드 프레임워크이다.

 
 

끝!   읽어주셔서 감사합니다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.