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

Support load balancing HTTP requests across worker threads #53074

Open
mcollina opened this issue May 20, 2024 · 10 comments
Open

Support load balancing HTTP requests across worker threads #53074

mcollina opened this issue May 20, 2024 · 10 comments
Labels
feature request Issues that request new features to be added to Node.js. worker Issues and PRs related to Worker support.

Comments

@mcollina
Copy link
Member

I think we can support load balancing HTTP requests across worker threads by leveraging the same mechanisms that allows Cluster to do it. If a libuv handle can cross processes, it definitely can cross threads.

I've tried to do it outside of core in https://github.com/mcollina/tbal, but unfortunately it does not work because it requires some mangling of libuv handles that is only possible from within core.

The idea is to allow worker threads to have an libuv IPC channel similar to child processes, so that they can exchange libuv handles through it.

This should be opt-in.

@mcollina mcollina added the feature request Issues that request new features to be added to Node.js. label May 20, 2024
@juanarbol
Copy link
Member

Interesting. Could you please be a bit more specific with the implementation? Currently, moving sockets between workers is not possible.

@theanarkh
Copy link
Contributor

theanarkh commented May 20, 2024

Maybe we can achieve this by sharing fd 🤔(Or listen on the port in the main thread and distribute fd to child threads?).

server.js

const { Worker, isMainThread, parentPort, threadId } = require('worker_threads');
const os = require('os');
const net = require('net');
const http = require('http');

if (isMainThread) {
    const handle = net._createServerHandle('127.0.0.1', '9999')
    if (typeof handle === 'number') {
        console.error('exit', handle);
        process.exit();
    }
    const workers = [];
    for (let i = 0; i < os.cpus().length; i++) {
        new Worker(__filename).postMessage(handle.fd)
    }
} else {
    parentPort.on('message', (fd) => {
        console.log(`worker ${threadId}`)
        http.createServer((_, res) => {
            res.end(`${threadId}`)
        }).listen({fd});
    });
}

client.js

const http = require('http')
for (let i = 0; i < 10; i++) {
    http.get("http://127.0.0.1:9999", res => {
        let chunk
        res.on('data', (data) => {
            chunk = chunk ? Buffer.concat([chunk, data]) : data;
        });
        res.on('end', () => {
            console.log(chunk.toString())
        })
    })
}

@juanarbol
Copy link
Member

The thing is. That handle you create on the main thread, is a socket (under the hood), and that socket/handle will be managed on your main event loop; you can't transfer that handle to another event loop.

@theanarkh
Copy link
Contributor

All threads share the fd among process, so main thread just need to send the fd to other worker threads, then the worker threads create a socket based on fd.

@VoltrexKeyva VoltrexKeyva added the worker Issues and PRs related to Worker support. label May 20, 2024
@mcollina
Copy link
Member Author

Sending the fd is possible, but we must tell the server on the main process that it should not respond to that fd, or doing anything with it.

Plus the tricky part is managing the "shutdown" of the server.

@flakey5
Copy link
Member

flakey5 commented May 21, 2024

If this is something y'all would be interested in pursuing I'd be willing to take a swing at it 👀

@mcollina
Copy link
Member Author

cc @nodejs/workers

I think this would be great.

@gireeshpunathil
Copy link
Member

IIRC, events that libuv manages have extended / unused attributes. one idea can be to use one of those attribute to attach the owner thread info. loops of all the threads will poll for the event, but only the one whose thread id matches with the thread info field will process the callback, the rest (including main thread) ignore the event.

@juanarbol
Copy link
Member

juanarbol commented May 22, 2024

I think I'm not considering something in the upfront.

https://github.com/libuv/libuv/blob/d2d92b74a8327daf9652a9732454937f3529bd30/src/unix/stream.c#L539

Every time uv_accept is called (at least in unix) the first operation made is check that the client's loop is associated with the server's loop.

Is this idea more like a reversed proxy with spawned workers in a IP range? And those workers will write into the reverse proxy socket? I'm very confused with this one.

Are the workers just responsable of creating the buffers and writing into the "main" process client sockets? If that is the case, I would consider this as a passable to achieve. IMHO.

@juanarbol
Copy link
Member

it may be possible to pass like an option for "http handlers" to the workers, they will compute your request, but the streams will remain on front. Not quite sure if that is what you would like to have

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature request Issues that request new features to be added to Node.js. worker Issues and PRs related to Worker support.
Projects
Status: Pending Triage
Development

No branches or pull requests

6 participants