Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 8838

Lambda+node.jsのREST APIをDocker+Rustに置き換えて高速化したい

$
0
0

はじめに

  • AWS Lambdaをnode.js(javascript/typescript)でよく使っている。
  • コスト、またはレスポンス改善のためにLambdaをECS+fargateなどDocker環境に移植したい。
  • もちろんRustに移植すれば速くなると思ってやっている。

Lambdaの問題

  • リクエスト課金のため、大規模利用では課金がヤバいことになる。
  • レスポンスタイムの揺らぎが大きい、コールドスタートが遅い。
  • このどっちの問題にも当てはまらないならLambdaはオススメです。

(最近、Provisioned Concurrencyとか追加されたけど、それでもコールドスタートは発生する)

なぜRust?

簡単なREST APIサーバーを書いてみる

数値を2つ含んだJSONをPOSTして、その和を返すREST APIを作る。

普段、fastifyを使っているので、node.jsはfastifyで比較する。

node.js(javascript) + fastify

main.js
constfastify=require('fastify');constserver=fastify({});server.post('/',(request,reply)=>{reply.send({answer:request.body.a+request.body.b});});server.listen(3000,(err,address)=>{if(err)throwerr;console.log(`server listening on ${address}`);});

Rust + actix_web

main.rs
useactix_web::{web,App,HttpServer,Responder,post,HttpResponse};useserde::{Deserialize,Serialize};#[derive(Serialize)]structAddResult{answer:i32,}#[derive(Deserialize)]structAddQuery{a:i32,b:i32,}#[post("/")]fnpost(query:web::Json<AddQuery>)->implResponder{HttpResponse::Ok().json(AddResult{answer:query.a+query.b})}fnmain(){HttpServer::new(||{App::new().service(post)}).bind("127.0.0.1:3000").expect("Can not bind to port 3000").run().unwrap();println!("server listening on 3000");}

測定

ローカルマシン(MacBook Pro 4コア)で、heyを使って測定する。

Rustはcargo run --releaseで実行する。

% hey -n 1000000 -c 100 -m POST -d'{"a":1,"b":2}'-T'application/json' http://localhost:3000

node+fastify heyの結果

Summary:
  Total:    42.1554 secs
  Slowest:  0.0426 secs
  Fastest:  0.0001 secs
  Average:  0.0042 secs
  Requests/sec: 23721.7658

  Total data:   12000000 bytes
  Size/request: 12 bytes

Response time histogram:
  0.000 [1] |
  0.004 [723692]    |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
  0.009 [271311]    |■■■■■■■■■■■■■■■
  0.013 [3823]  |
  0.017 [789]   |
  0.021 [286]   |
  0.026 [20]    |
  0.030 [3] |
  0.034 [22]    |
  0.038 [28]    |
  0.043 [25]    |


Latency distribution:
  10% in 0.0036 secs
  25% in 0.0036 secs
  50% in 0.0039 secs
  75% in 0.0044 secs
  90% in 0.0054 secs
  95% in 0.0058 secs
  99% in 0.0076 secs

Rust+actix heyの結果

Summary:
  Total:    11.0322 secs
  Slowest:  0.1170 secs
  Fastest:  0.0001 secs
  Average:  0.0011 secs
  Requests/sec: 90643.4012

  Total data:   12000000 bytes
  Size/request: 12 bytes

Response time histogram:
  0.000 [1] |
  0.012 [997956]    |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
  0.023 [1203]  |
  0.035 [280]   |
  0.047 [182]   |
  0.059 [212]   |
  0.070 [89]    |
  0.082 [61]    |
  0.094 [14]    |
  0.105 [0] |
  0.117 [2] |


Latency distribution:
  10% in 0.0006 secs
  25% in 0.0009 secs
  50% in 0.0010 secs
  75% in 0.0011 secs
  90% in 0.0013 secs
  95% in 0.0015 secs
  99% in 0.0032 secs

結果

Requests/sec

node + fastifyRust + actix
2372190643

Rustが速い。

node.js vs デフォルトでコア数だけスレッド立てるactixはフェアじゃないだろ

node側のコードをclusterを使って、マルチプロセス化する。

cluster.js
constcluster=require('cluster');constos=require('os');constfastify=require('fastify');if(cluster.isMaster){for(leti=0;i<os.cpus().length;i++){cluster.fork();}}else{constserver=fastify({});server.post('/',(request,reply)=>{reply.send({answer:request.body.a+request.body.b});});server.listen(3000,(err,address)=>{if(err)throwerr;console.log(`server listening on ${address}`);});}

それに対するheyの結果

Summary:
  Total:    16.0576 secs
  Slowest:  0.1326 secs
  Fastest:  0.0001 secs
  Average:  0.0016 secs
  Requests/sec: 62275.7432

  Total data:   12000000 bytes
  Size/request: 12 bytes

Response time histogram:
  0.000 [1] |
  0.013 [977295]    |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
  0.027 [17411] |■
  0.040 [4146]  |
  0.053 [812]   |
  0.066 [176]   |
  0.080 [67]    |
  0.093 [30]    |
  0.106 [32]    |
  0.119 [0] |
  0.133 [30]    |


Latency distribution:
  10% in 0.0003 secs
  25% in 0.0005 secs
  50% in 0.0007 secs
  75% in 0.0010 secs
  90% in 0.0020 secs
  95% in 0.0063 secs
  99% in 0.0208 secs

Requests/sec

node + fastifynode + fastify + clusterRust + actix
237216227590643

Rustが1.5倍ほど速いけど、思ったより差がなくなった。

他のhttp framework crateではどうなのか

nickelでやってみる。

main.rs
#[macro_use]externcratenickel;usenickel::{Nickel,HttpRouter,JsonBody};useserde::{Deserialize,Serialize};useserde_json;#[derive(Serialize)]structAddResult{answer:i32,}#[derive(Deserialize)]structAddQuery{a:i32,b:i32,}fnmain(){letmutserver=Nickel::new();server.post("/",middleware!{|request,response|letquery=request.json_as::<AddQuery>().unwrap();letresponse=AddResult{answer:query.a+query.b};serde_json::to_string(&response).unwrap()});server.listen("127.0.0.1:3000").unwrap();}

けど、heyの同じ負荷ではsocket: too many open filesが大量に出て耐えれなかった。
仕方なく、

% hey -n 100000 -c 10 -m POST -d'{"a":1,"b":2}'-T'application/json' http://localhost:3000

同時接続数を減らして比較した

Rust + actixRust + nickel
6227567503

nickelがやや速い。が、多同時接続が不安。

まとめ

  • Rustはnode.jsの1.5倍速かった。
  • もうちょっとRustは速いと思ってた。
  • ここから処理を追加するから、差はついてくると思うが、REST APIのガワだけであれば大差なかった。
  • マジか。

補足

  • ローカル実行の雑なベンチマークなので参考程度でお願いします。

Viewing all articles
Browse latest Browse all 8838

Trending Articles