Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add keep-alive connection tracking and reaping (resolve #3617) #3619

Merged
merged 1 commit into from Jan 18, 2022

Conversation

jsumners
Copy link
Member

2022-01-14:

This is a proof of concept. The code is bare minimum to add the feature set. It needs to be cleaned up to follow proper internals code if the feature set will be accepted. Also, automated tests will be needed. The PR is created to get feedback on the implementation and discussion on whether to move forward.

Checklist

@jsumners jsumners added the benchmark Label to run benchmark against PR and main branch label Jan 14, 2022
@github-actions

This comment has been minimized.

@github-actions github-actions bot removed the benchmark Label to run benchmark against PR and main branch label Jan 14, 2022
fastify.js Outdated Show resolved Hide resolved
lib/route.js Outdated Show resolved Hide resolved
@jsumners jsumners added the benchmark Label to run benchmark against PR and main branch label Jan 15, 2022
@github-actions

This comment has been minimized.

@github-actions github-actions bot removed the benchmark Label to run benchmark against PR and main branch label Jan 15, 2022
lib/route.js Outdated Show resolved Hide resolved
lib/route.js Show resolved Hide resolved
@RafaelGSS

This comment has been minimized.

@jsumners

This comment has been minimized.

@mcollina

This comment has been minimized.

@jsumners

This comment has been minimized.

@mcollina
Copy link
Member

The predominant thing you are testing with load testing new connection for every request is Node.js ability to accept them, the TCP settings of your machine and similar things. They are not useful for evaluating the performance of a framework.

Note that for this specific case I expect the actual performance to be worse for short lived connections that do 2-3 requests and then are closed (we can benchmark this with autocannon) as they will enter/exit the Set often.

A solution for this is to put it behind a flag.

@jsumners
Copy link
Member Author

Yes. The full implementation would be behind a flag. Doing this automatically would be a breaking change and would not be something everyone wants without explicitly saying so.

@mcollina
Copy link
Member

Then I'm good with this approach.

@jsumners jsumners added the benchmark Label to run benchmark against PR and main branch label Jan 17, 2022
@github-actions

This comment has been minimized.

@github-actions github-actions bot removed the benchmark Label to run benchmark against PR and main branch label Jan 17, 2022
@jsumners jsumners added the benchmark Label to run benchmark against PR and main branch label Jan 17, 2022
@github-actions

This comment has been minimized.

@github-actions github-actions bot removed the benchmark Label to run benchmark against PR and main branch label Jan 17, 2022
@jsumners
Copy link
Member Author

I have tested two methods of skipping connection tracking by default. First, by providing a "Set" that does nothing. Second by passing through a boolean that skips the whole "add to set" path in the route handler. I am seeing no difference between the two methods. Therefore, I am going to opt for the easier to maintain "noop Set" implementation.

Benchmark for noop Set

Node: 12
PR: [1] 1756k requests in 30.12s, 328 MB read
MAIN: [1] 1724k requests in 30.13s, 322 MB read

Node: 14
PR: [1] 1490k requests in 30.06s, 278 MB read
MAIN: [1] 1443k requests in 30.07s, 270 MB read

Node: 16
PR: [1] 1032k requests in 30.17s, 193 MB read
MAIN: [1] 1006k requests in 30.08s, 188 MB read

Benchmark for boolean toggle

Node: 12
PR: [1] 1677k requests in 30.14s, 313 MB read
MAIN: [1] 1683k requests in 30.16s, 315 MB read

Node: 14
PR: [1] 1832k requests in 30.11s, 342 MB read
MAIN: [1] 1917k requests in 30.05s, 358 MB read

Node: 16
PR: [1] 1634k requests in 30.11s, 305 MB read
MAIN: [1] 1642k requests in 30.08s, 307 MB read

@mcollina
Copy link
Member

The noop set would be ok for me.

@jsumners jsumners force-pushed the issue-3617 branch 2 times, most recently from a1c2b23 to d6bf012 Compare January 17, 2022 18:36
@jsumners
Copy link
Member Author

I'm getting the exact same missing coverage on the main branch.

// for the client to have seen the socket get destroyed. The mere fact
// that we have reached this callback is enough indication that the
// feature being tested works as designed.
t.equal(client.closed, false)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the client receive an error event due to the socket being closed?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe?

@Eomm

This comment has been minimized.

@jsumners

This comment has been minimized.

@jsumners

This comment has been minimized.

@Eomm
Copy link
Member

Eomm commented Jan 17, 2022

@jsumners I remembered this pr
#3456

The issue is the localhost string

@jsumners jsumners marked this pull request as ready for review January 18, 2022 13:51
@jsumners jsumners added the benchmark Label to run benchmark against PR and main branch label Jan 18, 2022
@github-actions
Copy link

Node: 12
PR: [1] 1990k requests in 30.16s, 372 MB read
MAIN: [1] 1905k requests in 30.1s, 356 MB read


Node: 14
PR: [1] 1464k requests in 30.07s, 274 MB read
MAIN: [1] 1455k requests in 30.06s, 272 MB read


Node: 16
PR: [1] 1547k requests in 30.19s, 289 MB read
MAIN: [1] 1784k requests in 30.11s, 333 MB read

@github-actions github-actions bot removed the benchmark Label to run benchmark against PR and main branch label Jan 18, 2022
@@ -16,7 +16,7 @@
"lint:typescript": "eslint -c types/.eslintrc.json types/**/*.d.ts test/types/**/*.test-d.ts",
"prepublishOnly": "tap --no-check-coverage test/internals/version.test.js",
"test": "npm run lint && npm run unit && npm run test:typescript",
"test:ci": "npm run unit -- --cov --coverage-report=lcovonly && npm run test:typescript",
"test:ci": "npm run unit -- -R terse --cov --coverage-report=lcovonly && npm run test:typescript",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll call this out before anyone else:

I added this because one of the macOS runs flaked. We have so many tests that scrolling through them to figure out why that run flaked was effectively impossible. By using -R terse, only the failures will be published to the test run logs. This will make it far easier to find the problems.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

THANKS!!! I didn't know this was possiblez

[Symbol.iterator]: function * () {},
add () {},
delete () {},
has () { return true }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't the noop set be empty, and return false for all .has() calls?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returning true prevents new connections from being added to the set and thus avoids a branch and extra function invocation (.add()).

Comment on lines +136 to +137
persistent connections to timeout first. Important: connections are not
inspected to determine if requests have been completed.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think it could be possible to order the shutdown cycle in this way:

  1. Disable acceptance of new requests
  2. In-progress request handlers are allowed to complete
  3. Once all handlers have completed, close the sockets with keep-alive connections?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I know, it is impossible to determine if a request is pending via the socket. We only retain the sockets so that we do not keep the full request(s) in memory for the duration of the persistent connection.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Node.js knows btw, however that info is private

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I know, it is impossible to determine if a request is pending via the socket.

Wouldn't we know via fastify tracking handler()s that are pending, rather than the socket?

We only retain the sockets so that we do not keep the full request(s) in memory for the duration of the persistent connection.

Yeah, I'm talking about while the request is still active and the connection is active.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@scmorse I believe this PR as written does the best we can do in Fastify without significant impact to performance. Any other changes will rely on nodejs/node#41578 going through.

Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

We might want to add something similar for HTTP/2, but I would push that to a separate issue.

@jsumners jsumners merged commit 2c1505a into fastify:main Jan 18, 2022
@jsumners jsumners deleted the issue-3617 branch January 18, 2022 15:45
@mattbishop
Copy link
Contributor

Folks, fastify.d.ts was not updated to include this new server option. I can file a ticket if necessary.

@jsumners
Copy link
Member Author

That is not a requirement for Fastify PRs. Not everyone knows TypeScript. A subsequent PR has already been created #3646.

@fastify fastify locked as resolved and limited conversation to collaborators Jan 27, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants