Closed red010182 closed 2 years ago
@red010182 may you try with github.com/go-playground/validator/v10 for fiber.
@red010182 versions are low. I think you should try with lastest versions.
@balcieren Still slow(20k req/s) and memory almost doubled. But, if I use a shared validator, it speeds up to 29k req/s and memory is reduced to 35MB. What I mean is all requests share the same validator. Like this:
import (
"github.com/go-playground/validator/v10"
...
)
...
func main() {
var key []byte = []byte("some-secret")
app := fiber.New()
validate := validator.New() // <--- init here
app.Get("/hello/:name", func(c *fiber.Ctx) error {
...
if err := validate.Struct(query); err != nil {
return fiber.ErrBadRequest
}
...
It turns out to be that the alloc/dealloc of validator seems very expensive. However, I'm not sure if it's a good idea to use a single validator for all requests and all different struct types.
@balcieren Still slow(20k req/s) and memory almost doubled. But, if I use a shared validator, it speeds up to 29k req/s and memory is reduced to 35MB. What I mean is all requests share the same validator. Like this:
import ( "github.com/go-playground/validator/v10" ... ) ... func main() { var key []byte = []byte("some-secret") app := fiber.New() validate := validator.New() // <--- init here app.Get("/hello/:name", func(c *fiber.Ctx) error { ... if err := validate.Struct(query); err != nil { return fiber.ErrBadRequest } ...
It turns out to be that the alloc/dealloc of validator seems very expensive. However, I'm not sure if it's a good idea to use a single validator for all requests and all different struct types.
Validator should be created once.
Validator should be created once.
Correct, why would you want different instances of validator for each request? Of course if you need to make an instance of a heavy object each request it's going to be slow and resource heavy.
Gin also instantiate it only once, hell, take any web framework of any language, like spring boot, and they will instantiate it only once
Validator should be created once.
Correct, why would you want different instances of validator for each request? Of course if you need to make an instance of a heavy object each request it's going to be slow and resource heavy.
Gin also instantiate it only once, hell, take any web framework of any language, like spring boot, and they will instantiate it only once
Yes that's right, I agree you.
Maybe can be developed custom validator for fiber like Gin's custom validator. It can be accessed from fiber.Ctx like c.Validate().
Maybe can be developed custom validator for fiber like Gin's custom validator. It can be accessed from fiber.Ctx like c.Validate().
If it's plug and play like how gin's works so i can easily bring my own implementations then I'm 100% on board
Validator should be created once.
Correct, why would you want different instances of validator for each request?
Because I was guided by the official tutorial https://docs.gofiber.io/guide/validation, in which a new validator is created in each request. So I thought it was a "light-weight" validator before.
Gin also instantiate it only once, hell, take any web framework of any language, like spring boot, and they will instantiate it only once
Thanks a lot. I didn't know Gin instantiate it only once.
Hmmm, maybe should have a disclaimer that those code examples are for demonstration purposes only.
If you want better examples of real usages, maybe go through the boilerplates listed here
my code:
package main
import (
"log"
"github.com/go-playground/validator/v10"
"github.com/gofiber/fiber/v2"
)
type Job struct{
Type string `validate:"required,min=3,max=32"`
Salary int `validate:"required,number"`
}
type User struct{
Name string `validate:"required,min=3,max=32"`
// use `*bool` here otherwise the validation will fail for `false` values
// Ref: https://github.com/go-playground/validator/issues/319#issuecomment-339222389
IsActive *bool `validate:"required"`
Email string `validate:"required,email,min=6,max=32"`
Job Job `validate:"dive"`
}
type ErrorResponse struct {
FailedField string
Tag string
Value string
}
var validate = validator.New() // <-- validator initialized once
func ValidateStruct(user User) []*ErrorResponse {
var errors []*ErrorResponse
//validate := validator.New() // <-- validator initialized every request
err := validate.Struct(user)
if err != nil {
for _, err := range err.(validator.ValidationErrors) {
var element ErrorResponse
element.FailedField = err.StructNamespace()
element.Tag = err.Tag()
element.Value = err.Param()
errors = append(errors, &element)
}
}
return errors
}
func AddUser(c *fiber.Ctx) error {
//Connect to database
user := new(User)
if err := c.BodyParser(user); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": err.Error(),
})
}
errors := ValidateStruct(*user)
if errors != nil {
return c.Status(fiber.StatusBadRequest).JSON(errors)
}
//Do something else here
//Return user
return c.JSON(user)
}
func main() {
app := fiber.New()
//app.Post("/", func(ctx *fiber.Ctx) error { // <-- request without any additional process
// return ctx.SendString("OK")
//})
app.Post("/", AddUser)
log.Fatalln(app.Listen(":8080"))
}
tool + request code:
bombardier -c 10 -d 10s "http://localhost:8080/" -m POST --body='{"name": "test","isActive": true,"email": "test@test.com","job": { "type": "tester", "salary": 30000 }}' --header="Content-Type: application/json"
case 1 - "request without any additional process"
case 2 - "validator initialized every request"
case 3 - "validator initialized once"
my change in the example: https://github.com/gofiber/docs/commit/98f9d00d2e2e6dbe8dd61af2c2e03d44e5fccf37 https://docs.gofiber.io/guide/validation
@red010182 thank you for the report and to all involved for the hints
@ReneWerner87 It's my honor to contribute a little bit to the community.
Maybe can be developed custom validator for fiber like Gin's custom validator. It can be accessed from fiber.Ctx like c.Validate().
If it's plug and play like how gin's works so i can easily bring my own implementations then I'm 100% on board
I am making a simple benchmark for some performance-oriented web frameworks like gin, fiber, actix, axum. fiber is undoubtedly an awesome framework which is probably the fatest in golang and is super fast before I add query parse and validation code.
However, after I add query parse and validation code, fiber is both slower and consumes larger memory than gin. Here's the numbers:
Before: fiber: 30k req/s, 25 MB (mem) gin: 24k req/s, 20 MB (mem)
After: fiber: 21k req/s, 60 MB (mem) gin: 24k req/s, 20 MB (mem)
OSX MBP, 2.3 GHz Dual-Core Intel Core i5, 16 GB Memory
ps.
token
is sent as query param for test convenience.Maybe the question should be what is the right way to validate params in fiber without dropping lots of performance?
test command
test command