grantila / fetch-h2

HTTP/1+2 Fetch API client for Node.js
MIT License
336 stars 16 forks source link

parallel requests don't scale #85

Closed stefan-guggisberg closed 4 years ago

stefan-guggisberg commented 4 years ago

When running parallel requests to the same http2 origin the total time roughly increases linearly with the number of requests.

test.js

const { fetch } = require('fetch-h2');
//const fetch = require('node-fetch-h2');

var args = process.argv.slice(2);
const N = args.length && !isNaN(parseInt(args[0])) ? parseInt(args[0]) : 100;

console.log(`running ${N} paralled requests...`)

const TEST_URL = 'https://httpbin.org/bytes/'; // HTTP2

(async function () {
  // generete array of 'randomized' urls
  const urls = Array.from({ length: N }, () => Math.floor(Math.random() * N)).map((num) => `${TEST_URL}${num}`);

  // establish initial connection
  // (workround for https://github.com/grantila/fetch-h2/issues/39)
  await (await fetch(`${TEST_URL}${N}`)).arrayBuffer();

  const ts0 = Date.now();

  // send requests
  const responses = await Promise.all(urls.map((url) => fetch(url)));

  // read bodies
  await Promise.all(responses.map((resp) => resp.arrayBuffer()));

  const ok = responses.filter((res) => res.ok);
  if (ok.length !== N) {
    console.log(`failed requests: ${N - ok.length}`);
  }

  const ts1 = Date.now();

  console.log(`Elapsed time: ${ts1 - ts0} ms`);
}());

node test.js 10 takes ~1s node test.js 100 takes ~10s node test.js 200 takes ~20s

Note that the included workaround for #39 (send an initial blocking request) doesn't have a significant impact on the results.

OTOH, when running the same tests with node-fetch-h2 instead of fetch-h2:

node test.js 10 takes ~200ms node test.js 100 takes ~300ms node test.js 200 takes ~500ms

There seems to be a massive bottleneck in fetch-h2 responsible for the poor performance.

stefan-guggisberg commented 4 years ago

My guess: It's either an issue with already#funnel itself or how (and where) funnel is used in fetch-h2. It seems like parallel requests to the same HTTP/2 endpoint are effectively serialised, which IMO defeats the purpose.

stefan-guggisberg commented 4 years ago

@grantila I'd appreciate your comments.

grantila commented 4 years ago

Thanks, yes this is obviously not good and a serious bug. I'm looking into it.

github-actions[bot] commented 4 years ago

:tada: This issue has been resolved in version 2.4.3 :tada:

The release is available on:

Your semantic-release bot :package::rocket:

stefan-guggisberg commented 4 years ago

@grantila Thanks!