v3: Improve performance of Adaptor Middleware (#3078)

* Improve performance of adaptor middleware by over 50%

* Update whats_new documentation

* Remove fasthttp.Request pool

* Update whats_new.md
pull/3075/head
Juan Calderon-Perez 2024-07-18 07:41:56 -04:00 committed by GitHub
parent 58d07f091d
commit 091a59472c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 52 additions and 7 deletions

View File

@ -254,6 +254,34 @@ DRAFT section
## 🧬 Middlewares ## 🧬 Middlewares
### Adaptor
The adaptor middleware has been significantly optimized for performance and efficiency. Key improvements include reduced response times, lower memory usage, and fewer memory allocations. These changes make the middleware more reliable and capable of handling higher loads effectively. Enhancements include the introduction of a `sync.Pool` for managing `fasthttp.RequestCtx` instances and better HTTP request and response handling between net/http and fasthttp contexts.
| Payload Size | Metric | V2 | V3 | Percent Change |
|--------------|------------------|-----------|----------|-------------------|
| 100KB | Execution Time | 1056 ns/op| 588.6 ns/op | -44.25% |
| | Memory Usage | 2644 B/op | 254 B/op | -90.39% |
| | Allocations | 16 allocs/op | 5 allocs/op | -68.75% |
| 500KB | Execution Time | 1061 ns/op| 562.9 ns/op | -46.94% |
| | Memory Usage | 2644 B/op | 248 B/op | -90.62% |
| | Allocations | 16 allocs/op | 5 allocs/op | -68.75% |
| 1MB | Execution Time | 1080 ns/op| 629.7 ns/op | -41.68% |
| | Memory Usage | 2646 B/op | 267 B/op | -89.91% |
| | Allocations | 16 allocs/op | 5 allocs/op | -68.75% |
| 5MB | Execution Time | 1093 ns/op| 540.3 ns/op | -50.58% |
| | Memory Usage | 2654 B/op | 254 B/op | -90.43% |
| | Allocations | 16 allocs/op | 5 allocs/op | -68.75% |
| 10MB | Execution Time | 1044 ns/op| 533.1 ns/op | -48.94% |
| | Memory Usage | 2665 B/op | 258 B/op | -90.32% |
| | Allocations | 16 allocs/op | 5 allocs/op | -68.75% |
| 25MB | Execution Time | 1069 ns/op| 540.7 ns/op | -49.42% |
| | Memory Usage | 2706 B/op | 289 B/op | -89.32% |
| | Allocations | 16 allocs/op | 5 allocs/op | -68.75% |
| 50MB | Execution Time | 1137 ns/op| 554.6 ns/op | -51.21% |
| | Memory Usage | 2734 B/op | 298 B/op | -89.10% |
| | Allocations | 16 allocs/op | 5 allocs/op | -68.75% |
### Cache ### Cache
We are excited to introduce a new option in our caching middleware: Cache Invalidator. This feature provides greater control over cache management, allowing you to define a custom conditions for invalidating cache entries. We are excited to introduce a new option in our caching middleware: Cache Invalidator. This feature provides greater control over cache management, allowing you to define a custom conditions for invalidating cache entries.

View File

@ -5,6 +5,7 @@ import (
"net" "net"
"net/http" "net/http"
"reflect" "reflect"
"sync"
"unsafe" "unsafe"
"github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3"
@ -13,6 +14,12 @@ import (
"github.com/valyala/fasthttp/fasthttpadaptor" "github.com/valyala/fasthttp/fasthttpadaptor"
) )
var ctxPool = sync.Pool{
New: func() any {
return new(fasthttp.RequestCtx)
},
}
// HTTPHandlerFunc wraps net/http handler func to fiber handler // HTTPHandlerFunc wraps net/http handler func to fiber handler
func HTTPHandlerFunc(h http.HandlerFunc) fiber.Handler { func HTTPHandlerFunc(h http.HandlerFunc) fiber.Handler {
return HTTPHandler(h) return HTTPHandler(h)
@ -82,12 +89,13 @@ func HTTPMiddleware(mw func(http.Handler) http.Handler) fiber.Handler {
return func(c fiber.Ctx) error { return func(c fiber.Ctx) error {
var next bool var next bool
nextHandler := http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { nextHandler := http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
next = true
// Convert again in case request may modify by middleware // Convert again in case request may modify by middleware
next = true
c.Request().Header.SetMethod(r.Method) c.Request().Header.SetMethod(r.Method)
c.Request().SetRequestURI(r.RequestURI) c.Request().SetRequestURI(r.RequestURI)
c.Request().SetHost(r.Host) c.Request().SetHost(r.Host)
c.Request().Header.SetHost(r.Host) c.Request().Header.SetHost(r.Host)
for key, val := range r.Header { for key, val := range r.Header {
for _, v := range val { for _, v := range val {
c.Request().Header.Set(key, v) c.Request().Header.Set(key, v)
@ -124,9 +132,9 @@ func FiberApp(app *fiber.App) http.HandlerFunc {
func handlerFunc(app *fiber.App, h ...fiber.Handler) http.HandlerFunc { func handlerFunc(app *fiber.App, h ...fiber.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
// New fasthttp request
req := fasthttp.AcquireRequest() req := fasthttp.AcquireRequest()
defer fasthttp.ReleaseRequest(req) defer fasthttp.ReleaseRequest(req)
// Convert net/http -> fasthttp request // Convert net/http -> fasthttp request
if r.Body != nil { if r.Body != nil {
n, err := io.Copy(req.BodyWriter(), r.Body) n, err := io.Copy(req.BodyWriter(), r.Body)
@ -141,26 +149,35 @@ func handlerFunc(app *fiber.App, h ...fiber.Handler) http.HandlerFunc {
req.SetRequestURI(r.RequestURI) req.SetRequestURI(r.RequestURI)
req.SetHost(r.Host) req.SetHost(r.Host)
req.Header.SetHost(r.Host) req.Header.SetHost(r.Host)
for key, val := range r.Header { for key, val := range r.Header {
for _, v := range val { for _, v := range val {
req.Header.Set(key, v) req.Header.Set(key, v)
} }
} }
if _, _, err := net.SplitHostPort(r.RemoteAddr); err != nil && err.(*net.AddrError).Err == "missing port in address" { //nolint:errorlint, forcetypeassert // overlinting if _, _, err := net.SplitHostPort(r.RemoteAddr); err != nil && err.(*net.AddrError).Err == "missing port in address" { //nolint:errorlint, forcetypeassert // overlinting
r.RemoteAddr = net.JoinHostPort(r.RemoteAddr, "80") r.RemoteAddr = net.JoinHostPort(r.RemoteAddr, "80")
} }
remoteAddr, err := net.ResolveTCPAddr("tcp", r.RemoteAddr) remoteAddr, err := net.ResolveTCPAddr("tcp", r.RemoteAddr)
if err != nil { if err != nil {
http.Error(w, utils.StatusMessage(fiber.StatusInternalServerError), fiber.StatusInternalServerError) http.Error(w, utils.StatusMessage(fiber.StatusInternalServerError), fiber.StatusInternalServerError)
return return
} }
// New fasthttp Ctx // New fasthttp Ctx from pool
var fctx fasthttp.RequestCtx fctx := ctxPool.Get().(*fasthttp.RequestCtx) //nolint:forcetypeassert,errcheck // overlinting
fctx.Response.Reset()
fctx.Request.Reset()
defer ctxPool.Put(fctx)
fctx.Init(req, remoteAddr, nil) fctx.Init(req, remoteAddr, nil)
if len(h) > 0 { if len(h) > 0 {
// New fiber Ctx // New fiber Ctx
ctx := app.AcquireCtx(&fctx) ctx := app.AcquireCtx(fctx)
defer app.ReleaseCtx(ctx)
// Execute fiber Ctx // Execute fiber Ctx
err := h[0](ctx) err := h[0](ctx)
if err != nil { if err != nil {
@ -168,10 +185,10 @@ func handlerFunc(app *fiber.App, h ...fiber.Handler) http.HandlerFunc {
} }
} else { } else {
// Execute fasthttp Ctx though app.Handler // Execute fasthttp Ctx though app.Handler
app.Handler()(&fctx) app.Handler()(fctx)
} }
// Convert fasthttp Ctx > net/http // Convert fasthttp Ctx -> net/http
fctx.Response.Header.VisitAll(func(k, v []byte) { fctx.Response.Header.VisitAll(func(k, v []byte) {
w.Header().Add(string(k), string(v)) w.Header().Add(string(k), string(v))
}) })