k6 icon indicating copy to clipboard operation
k6 copied to clipboard

How to allow VU to keep working while keeping WebSocket open

Open AlexGustafsson opened this issue 6 years ago • 4 comments

Scenario

I would like to open up WebSockets to a server and keep them open. I'd like to open at least a couple of thousand connections that when open will stay open. I think that this could be done using one VU per socket but I find it painfully slow for thousands of clients. I also think that it may be a bad design choice. I'd therefore want to use VUs to open up a socket each iteration and then closing them in teardown.

Right now, however, VUs don't seem to want to work with another iteration when a socket is still opened. The only way for it to go on to the next iteration seem to be to close the socket as soon as it's opened. In some sense it's smart that the VU doesn't leave any loose ends, but in this scenario I find it troublesome.

My current code looks a bit like this:

export const sockets = [];

export default function(data) {
  const url = 'ws://localhost/channel';

  const response = ws.connect(url, socket => {
    socket.on('open', () => {
      // Unless I close the socket (socket.close()) this VU will never move on
      sockets.push(socket);
    });

    socket.on('error', error => {
      errorRate.add(1);
    });
  });

  check(response, {
    'WS status is 101': (r) => r.status === 101
  }) || errorRate.add(1);
};

export function teardown() {
  for (const socket of sockets)
    socket.close()
}

I'm pretty sure that the "global state" I'm trying here with sockets = [] won't work since it seems to go against documentation, but I think it best shows what I'm trying to do.

Questions

  1. Can a VU move on while still leaving a socket connected or do I need one VU per socket connection?
  2. If it's possible, Is there a global state that can be used like seen above or could I use the data object passed down from setup() to each VU instance to keep track of open sockets to be closed?

AlexGustafsson avatar Dec 10 '18 18:12 AlexGustafsson

Unfortunately for the moment, unlike other JS runtimes, k6 doesn't have a global event loop, Instead we rely on having multiple parallel runtimes (i.e. VUs) to have concurrency (more info). k6 websockets actually have their own local event loop, but currently, to answer your first question, the VU cannot move on while leaving a socket connected.

I also think that it may be a bad design choice.

I kind of agree... While the current k6 architecture works relatively well for HTTP requests, having a proper even loop will be very helpful if we want more useful websockets or things like HTTP/2 server push, gRPC, and other similar protocols. We plan to introduce a real event loop per VU at some point, albeit likely a bit more restricted than others, simply due to the nature of load testing and VUs... I'll add a separate issue for it later and link this one to it, since once we have that global event loop, we should definitely implement a more flexible way to work with websockets.

If it's possible, Is there a global state that can be used like seen above or could I use the data object passed down from setup() to each VU instance to keep track of open sockets to be closed?

No, and we currently don't plan to add that. We can theoretically have some thread-safe global data store for a single k6 instance, but sharing actual JS objects between different JS runtimes is either extremely hard or just plain impossible. Also, this won't scale well enough in a distributed execution context where there's more than one k6 instance. And in that case it'd likely be more feasible to use an external service like Redis for sharing data between all of the distributed VUs.

na-- avatar Dec 13 '18 16:12 na--

Thank you for such a thorough explanation. I understand the choices you’ve made and I can’t really see it work any differently. You’ve answered all my questions and more.

AlexGustafsson avatar Dec 13 '18 18:12 AlexGustafsson

There is currently an extension that lets you do that. It might at some point be moved to core, but you can use it for now if it's particularly needed by someone.

mstoykov avatar May 12 '22 07:05 mstoykov

The extension is now released as an experimental module in k6 v0.40.0, more info in the relevant section of the release notes

But I am going to keep this open until an API for this is considered stable in k6.

mstoykov avatar Sep 08 '22 11:09 mstoykov

As the previous comment mentions, it has been released a WebSocket experimental module that solves the main issue. We plan to merge to the core in one of the next releases, track https://github.com/grafana/k6/issues/3185 for getting the updates.

codebien avatar Oct 03 '23 16:10 codebien