はじめに

    • AWS Lambdaをnode.js(javascript/typescript)でよく使っている。

 

    • コスト、またはレスポンス改善のためにLambdaをECS+fargateなどDocker環境に移植したい。

 

    もちろんRustに移植すれば速くなると思ってやっている。

Lambdaの問題

    • リクエスト課金のため、大規模利用では課金がヤバいことになる。

 

    • レスポンスタイムの揺らぎが大きい、コールドスタートが遅い。

 

    このどっちの問題にも当てはまらないならLambdaはオススメです。

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

なぜRust?

    • 速いから。速さがそのままインフラコスト改善になるから。

 

    • C++を長くやってきたけど、最近Rustがいい気がしてきたから。

 

    • でも、現状のLambdaではnode.jsのほうが速いらしい。(node.js、Go、Pythonは同じぐらい)

 

    https://medium.com/the-theam-journey/benchmarking-aws-lambda-runtimes-in-2019-part-i-b1ee459a293d

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

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

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

node.js(javascript) + fastify

const fastify = require('fastify');
const server = fastify({});

server.post('/', (request, reply) => {
    reply.send({answer: request.body.a + request.body.b});
});

server.listen(3000, (err, address) => {
    if (err) throw err;
    console.log(`server listening on ${address}`);
});

Rust + actix_web

use actix_web::{web, App, HttpServer, Responder, post, HttpResponse};
use serde::{Deserialize, Serialize};

#[derive(Serialize)]
struct AddResult {
    answer: i32,
}

#[derive(Deserialize)]
struct AddQuery{
    a: i32,
    b: i32,
}

#[post("/")]
fn post(query: web::Json<AddQuery>) -> impl Responder {
    HttpResponse::Ok().json(AddResult{answer: query.a  +  query.b})
}

fn main() {
    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 + actix2372190643

Rustが速い。

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

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

const cluster = require('cluster');
const os = require('os');
const fastify = require('fastify');

if(cluster.isMaster) {

    for(let i = 0; i < os.cpus().length; i++) {
        cluster.fork();
    }

}
else {

    const server = fastify({});

    server.post('/', (request, reply) => {
        reply.send({answer: request.body.a + request.body.b});
    });

    server.listen(3000, (err, address) => {
        if (err) throw err;
        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 + actix237216227590643

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

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

nickelでやってみる。

#[macro_use] extern crate nickel;

use nickel::{Nickel, HttpRouter, JsonBody};
use serde::{Deserialize, Serialize};
use serde_json;

#[derive(Serialize)]
struct AddResult {
    answer: i32,
}

#[derive(Deserialize)]
struct AddQuery {
    a: i32,
    b: i32,
}

fn main() {

    let mut server = Nickel::new();

    server.post("/", middleware! { |request, response|

        let query = request.json_as::<AddQuery>().unwrap();
        let response = 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 + nickel6227567503

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

まとめ

    • Rustはnode.jsの1.5倍速かった。

 

    • もうちょっとRustは速いと思ってた。

 

    • ここから処理を追加するから、差はついてくると思うが、REST APIのガワだけであれば大差なかった。

 

    マジか。

補足

    ローカル実行の雑なベンチマークなので参考程度でお願いします。
广告
将在 10 秒后关闭
bannerAds