[Golang] Why my go-api was 10x slower than node-api
The secret of bcrypt(which only I didn’t know)
Problem
api to sign in user
mutation { signInUser(input: { email: “some_user_email” password: “some_password expoToken: “some_expo_token”}) {tokenString}}
The code to sign in user(Go-version)
var user model.Uservar token model.Tokenvar result pg.Resultvar wg = sync.WaitGroup{}_, err := pgdb.Con.Query(&user, `select id, password from users where email = ?`, input.Email)if user.ID == “” {return &token, errors.New(“No User with such email”)}if err != nil {return &token, err}noMatch := bcrypt.CompareHashAndPassword([]byte(*user.Password), []byte(input.Password))if noMatch != nil {return &token, errors.New(“Wrong password”)}// update last_visited_at when signed in_, err = pgdb.Con.Query(&result, `update users set last_visited_at = ? where id = ?`, time.Now(), user.ID)if err != nil {return &token, err}tokenString, err := generatetoken.GenerateToken(user.ID)if err != nil {return err}token.TokenString = &tokenStringreturn &token, nil
time to respond, calculated by Postman
Go-based api: avr. of 923ms
node-based api: avr. of 120ms
The gap was too large to be ignored; performance was the №1 reason I chose Go over NodeJS!
So, I decided to find WHY.
Candidates for the reason of low performance
- Compile problem: I ran local Go server using go run command, not using go install or go build command. Maybe the reason my server is slow is because I used Go as an interpreting language.
- But, this was not the case; I compiled Go and ran that file in my local server, and it made no difference.
2. Go Routine problem: I did not use go routine for that api, so that could be the reason. So I added go routine for token generating process.
- This was not the reason, either.
3. Token generating might take too much time: To test this hypothesis, I removed password comparing part and ran the server again.
- To no avail.
The Reason is…
Finally, I thought that password comparing might take much time, maybe because I hashed password too many times when creating user.
So, I tested the hypothesis, and the result was surprising(to me, at least).
bytes, err := bcrypt.GenerateFromPassword([]byte(input.Password), hashCount)
hashCount = 14(status quo): 927ms to respond
hashCount = 12: 247ms to respond
hashCount = 10: 81ms to respond
(* node hashCount = 10: 122ms to respond)
It took almost 3 times more when hashCount was added by 2.
Then, how many times should I hash?
One article I found said that ‘Recommended # of rounds for bcrypt : The number of iterations that gives at least 250 ms to compute’.
That perfectly fits my condition, so I lowered the round of hashes to 12, and my go-api works 1.5x faster than node-api(at least for this one api, but that’s far advanced than before).