hydro-dev / Hydro

Hydro - Next generation high performance online-judge platform - 新一代高效强大的信息学在线测评系统 (a.k.a. vj5)
https://hydro.js.org/
GNU Affero General Public License v3.0
3.2k stars 273 forks source link

core: async hash calculation #779

Closed criyle closed 2 months ago

criyle commented 2 months ago

The pbkdf2 function is a CPU consuming operation (~50ms on m1) so using sync version blocks the Nodejs event loop for a while during request. Changing it into async version allows it to be calculated in worker thread without blocking. Stress test shows ~75% latency reduction.

before:

$ wrk -c8 -t1 -d5s -s login.lua http://localhost:2333/login
Running 5s test @ http://localhost:2333/login
  1 threads and 8 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   398.81ms  113.01ms 711.82ms   72.45%
    Req/Sec    19.38      6.48    34.00     79.17%
  98 requests in 5.01s, 40.96KB read
Requests/sec:     19.55
Transfer/sec:      8.17KB

after:

$ wrk -c8 -t1 -d5s -s login.lua http://localhost:2333/login
Running 5s test @ http://localhost:2333/login
  1 threads and 8 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   101.32ms    4.36ms 141.85ms   94.43%
    Req/Sec    78.42      7.85    90.00     74.00%
  395 requests in 5.05s, 165.10KB read
Requests/sec:     78.24
Transfer/sec:     32.70KB
criyle commented 2 months ago

LGTM, but you may need edit @hydrooj/migrate too, then you can direct await in result = await h(password, this._salt, this);.

See https://github.com/hydro-dev/Hydro/blob/master/packages/migrate/index.ts#L62 .

I don't want to break the existing plugin system by this change since there might be other implementation of customized plugin which defines their own hash functions or there might be someone who forget to upgrade migrate plugins with hydrooj. At the same time, I believe this implementation just extend the current functionality to support async hash functions without a breaking change.

I found that await a value which is not a promise always returns itself, so changed to result = await h(password, this._salt, this); anyway.