Hi, On Wed, Sep 16, 2020 at 10:24 AM Martin Grigorov <mgrigo...@apache.org> wrote:
> Hi Remy, > > On Tue, Sep 15, 2020 at 6:16 PM Rémy Maucherat <r...@apache.org> wrote: > >> On Tue, Sep 15, 2020 at 5:08 PM Martin Grigorov <mgrigo...@apache.org> >> wrote: >> >> > Hi Mark, >> > >> > On Tue, Sep 15, 2020 at 3:34 PM Mark Thomas <ma...@apache.org> wrote: >> > >> > > On 15/09/2020 12:46, Martin Grigorov wrote: >> > > > On Tue, Sep 15, 2020 at 2:37 PM Martin Grigorov < >> mgrigo...@apache.org> >> > > > wrote: >> > > > >> > > >> Hi, >> > > >> >> > > >> I am running some load tests on Tomcat and I've noticed that when >> > HTTP2 >> > > is >> > > >> enabled the throughput drops considerably. >> > > >> >> > > >> Here are the steps to reproduce: >> > > >> >> > > >> 1) Enable HTTP2, e.g. by commenting out this connector: >> > > >> >> > > >> >> > > >> > >> https://github.com/apache/tomcat/blob/d381d87005fa89d1f19d9091c0954f317c135d9d/conf/server.xml#L103-L112 >> > > >> >> > > >> 2) Download Vegeta load tool from: >> > > >> https://github.com/tsenart/vegeta/releases/ >> > > >> >> > > >> 3) Run the load tests: >> > > >> >> > > >> 3.1) HTTP/1.1 >> > > >> echo -e '{"method": "GET", "url": "http://localhost:8080/examples/ >> "}' >> > | >> > > >> vegeta attack -format=json -rate=0 -max-workers=1000 >> -duration=10s | >> > > >> vegeta encode > /tmp/http1.json; and vegeta report -type=json >> > > >> /tmp/http1.json | jq . >> > > >> >> > > >> 3.2) HTTP2 >> > > >> echo -e '{"method": "GET", "url": " >> https://localhost:8443/examples/ >> > "}' >> > > | >> > > >> vegeta attack -format=json -http2 -rate=0 -max-workers=1000 >> -insecure >> > > >> -duration=10s | vegeta encode > /tmp/http2.json; and vegeta report >> > > >> -type=json /tmp/http2.json | jq . >> > > >> >> > > >> As explained at https://github.com/tsenart/vegeta#-rate -rate=0 >> means >> > > >> that Vegeta will try to send as many requests as possible with the >> > > >> configured number of workers. >> > > >> I use '-insecure' because I use self-signed certificate. >> > > >> >> > > >> On my machine I get around 14-15K reqs/sec for HTTP1.1 with only >> > > responses >> > > >> with code=200 . >> > > >> But for HTTP2 Tomcat starts returning such kind of errors: >> > > >> >> > > >> "errors": [ >> > > >> "Get \"https://localhost:8443/examples/\": http2: server sent >> > > GOAWAY >> > > >> and closed the connection; LastStreamID=9259, >> ErrCode=PROTOCOL_ERROR, >> > > >> debug=\"Stream [9,151] has been closed for some time\"", >> > > >> "http2: server sent GOAWAY and closed the connection; >> > > >> LastStreamID=9259, ErrCode=PROTOCOL_ERROR, debug=\"Stream [9,151] >> has >> > > been >> > > >> closed for some time\"", >> > > >> "Get \"https://localhost:8443/examples/\": http2: server sent >> > > GOAWAY >> > > >> and closed the connection; LastStreamID=239, >> ErrCode=PROTOCOL_ERROR, >> > > >> debug=\"Stream [49] has been closed for some time\"" >> > > >> ] >> > > >> >> > > >> when I ask for more than 2000 reqs/sec, i.e. -rate=2000/1s >> > > >> > > That indicates that the client has sent a frame associated with a >> stream >> > > that the server closed previously and that that stream has been >> removed >> > > from the Map of known streams to make room for new ones. See >> > > Http2UpgardeHandler.pruneClosedStreams() >> > > >> > > It looks like the client is making assumptions about server behaviour >> > > that go beyond the requirements of RFC 7540, section 5.3.4. >> > > >> > >> > This is possible! >> > I've just tested with two more HTTP2 impls: >> > >> > 1) Node.js >> > >> > http2-server.js >> > =================================================== >> > const http2 = require('http2'); >> > const fs = require('fs'); >> > >> > const server = http2.createSecureServer({ >> > key: fs.readFileSync('/path/to/server.key'), >> > cert: fs.readFileSync('/path/to/server.crt') >> > }); >> > server.on('error', (err) => console.error(err)); >> > >> > server.on('stream', (stream, headers) => { >> > // stream is a Duplex >> > stream.respond({ >> > 'content-type': 'text/plain; charset=utf-8', >> > ':status': 200 >> > }); >> > stream.end('Hello world!'); >> > }); >> > >> > server.listen(18080); >> > =================================================== >> > >> > run with: node http2-server.js >> > >> > Runs fine with -rate=0 and gives around 8K reqs/sec >> > >> > 2) Rust >> > >> > Cargo.toml >> > =================================================== >> > [package] >> > name = "my-http2-server" >> > version = "0.0.1" >> > publish = false >> > authors = ["Martin Grigorov <mgrigo...@apache.org>"] >> > license = "MIT/Apache-2.0" >> > description = "Load test HTTP/2 " >> > repository = "https://github.com/martin-g/http2-server-rust" >> > keywords = ["http2"] >> > edition = "2018" >> > >> > [dependencies] >> > actix-web = { version = "3", features = ["openssl"] } >> > openssl = { version = "0.10", features = ["v110"] } >> > =================================================== >> > >> > src/main.rs >> > =================================================== >> > use actix_web::{web, App, HttpRequest, HttpServer, Responder}; >> > use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; >> > >> > async fn index(_req: HttpRequest) -> impl Responder { >> > "Hello world!" >> > } >> > >> > #[actix_web::main] >> > async fn main() -> std::io::Result<()> { >> > let mut builder = >> > SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); >> > builder >> > .set_private_key_file("/path/to/server.key", SslFiletype::PEM) >> > .unwrap(); >> > builder.set_certificate_chain_file("/path/to/server.crt").unwrap(); >> > >> > HttpServer::new(|| App::new().route("/", web::get().to(index))) >> > .bind_openssl("127.0.0.1:18080", builder)? >> > .run() >> > .await >> > } >> > =================================================== >> > >> > run with: cargo run >> > Again no errors, throughput: 3K reqs/sec >> > >> > I will test with Netty tomorrow too, but so far it looks like only >> Tomcat >> > fails under load. >> > >> > >> > >> > > >> All the access logs look like: >> > > >> >> > > >> 127.0.0.1 - - [15/Sep/2020:13:59:24 +0300] "GET /examples/ >> HTTP/2.0" >> > 200 >> > > >> 1126 >> > > >> 127.0.0.1 - - [15/Sep/2020:13:59:24 +0300] "GET /examples/ >> HTTP/2.0" >> > 200 >> > > >> 1126 >> > > >> 127.0.0.1 - - [15/Sep/2020:13:59:24 +0300] "GET /examples/ >> HTTP/2.0" >> > 200 >> > > >> 1126 >> > > >> 127.0.0.1 - - [15/Sep/2020:13:59:24 +0300] "GET /examples/ >> HTTP/2.0" >> > 200 >> > > >> 1126 >> > > >> 127.0.0.1 - - [15/Sep/2020:13:59:24 +0300] "GET /examples/ >> HTTP/2.0" >> > 200 >> > > >> 1126 >> > > >> 127.0.0.1 - - [15/Sep/2020:13:59:24 +0300] "GET /examples/ >> HTTP/2.0" >> > 200 >> > > >> 1126 >> > > >> >> > > >> i.e. there are no error codes, just 200. >> > > >> Vegeta reports the error with status code = 0. I think this just >> means >> > > >> that it didn't get a proper HTTP response but just TCP error. >> > > >> There are no errors in catalina.out. >> > > >> >> > > >> Are there any settings I can tune to get better throughput with >> HTTP2 >> > ? >> > > >> >> > > >> Tomcat 10.0.0-M8. >> > > >> > > If you really want to maximise throughput then you need to reduce the >> > > number of concurrent requests to (or a little above) the number of >> cores >> > > available on the server. Go higher and you'll start to see throughput >> > > tail off due to context switching. >> > > >> > > If you want to demonstrate throughput with a large number of clients >> > > you'll probably need to experiment with both maxThreads, >> > > >> > >> > I've forgot to say that I use maxThreads=8. >> > >> > >> > > maxConcurrentStreams and maxConcurrentStreamExecution. >> > > >> > > If I had to guess, I'd expect maxConcurrentStreams == >> > > maxConcurrentStreamExecution and low numbers for all of them to give >> the >> > > best results. >> > These didn't help I've created a GitHub repo with all HTTP2 server I've tested with so far: https://github.com/martin-g/http2-server-perf-tests Throughput with default settings: - Netty: 29K reqs/s - Golang: 19K - Node.js: 17K - Rust: 3K - Tomcat: 400, with many errors So far only Tomcat returns errors :-/ I'll keep digging! Martin > > > >> > >> > I will check those tomorrow! >> > >> >> I use h2load, and IMO I have very good performance with h2c. No errors. >> However, same as your tool, it's concurrency flag only controls how many >> streams it will use *inside a single connection* [I respect that, but I >> > > I think this is not the case here. > As far as I can see Vegeta does not use custom ClientConnPool and the > default pool in net::http2 shares many connections per host:port key: > https://github.com/golang/net/blob/62affa334b73ec65ed44a326519ac12c421905e3/http2/client_conn_pool.go > > > >> would have expected instead the tool would also use multiple connections >> in >> an attempt to simulate multiple users, but nope]. This can only go so far >> and the performance numbers caused me to add the flags to limit stream >> concurrency. >> >> Rémy >> >