cylc / cylc-uiserver

A Jupyter Server extension that serves the cylc-ui web application for monitoring and controlling Cylc workflows.
https://cylc.org
GNU General Public License v3.0
15 stars 18 forks source link

resolvers: run in separate processes #538

Open oliver-sanders opened 11 months ago

oliver-sanders commented 11 months ago

In multi-user setups we may have multiple users subscribing to multiple workflows simultaneously.

Large workflows can cause heavy server load in some cases (e.g. https://github.com/cylc/cylc-uiserver/issues/547). Because we handle each request synchronously on the same process, this means larger updates can hold up other updates. In the extreme, this can cause UIs to show as disconnected as a result of websocket heartbeat timeout.

Ideally we would find a way to run the resolvers for each workflow in separate processes to isolate them from each other. Though we would probably want to limit the number of processes and distribute subscriptions across a pool.

Original Post We run subscriptions in a ThreadPoolExecutor. In Python only one thread can actively run at a time (because of the GIL) so there is no compute parallelism advantage to this (but there may be IO concurrency advantages depending on the implementation details of the code being run). This means that one large workflow can hog 100% of the CPU of the server, causing issues with other workflows. We should be able to swap the ThreadPoolExecutor for a ProcessPoolExecutor. I had a quick try, but it didn't work first time, the first update came through but subsequent ones got stuck, so a little work required. This does raise the question of how many processes the UI Server should be allowed to spawn. I think we should be able to run more subscribers than processes in the pool, but would need to read the docs to find out.
oliver-sanders commented 10 months ago

https://github.com/cylc/cylc-uiserver/pull/548 may reduce the urgency on this by optimizing some resolver stuff.

dwsutherland commented 9 months ago

Yeah, reason I didn't go with separate processes is so the resolvers have access to the central data-store (if it works that way)

oliver-sanders commented 9 months ago

We could potentially put the data store into shared memory but I'm guessing that managing parallel access would be challenging.

dwsutherland commented 9 months ago

Silly idea, and wider scope.. But if cylc-flow DataStoreMgr is refactored into DeltaMgr and a DataStoreMgr (which is imported by the UIS).. This would mean UIS window size(s) are isolated from the Scheduler window size(s), so:

The DeltaMgr would receive/be-called-by events from the scheduler internals, and package them up for local (Schd data-store) and abroad (UI Servers). Effectively breaking up DataStoreMgr, and reforming it to something agnostic to where it is and how it get's it's deltas. Would be a bit of work, and might require the scheduler config being made available somehow.. (if DataStoreMgr is to be imported for independent window sizes)

Would we even consider building a data-store for each subscription? advantages being:

Disadvantages would include machine load, amongst others..

First part, yes (and I think we've discussed it).. Second part, maybe?

oliver-sanders commented 9 months ago

This would mean UIS window size(s) are isolated from the Scheduler window size(s)

That sounds like a good idea. I think this aligns with what I was thinking of in https://github.com/cylc/cylc-uiserver/issues/464.

I think resolving subscriptions off of the same data store makes still sense so long as filtering by the n-window doesn't become prohibitively expensive.

hjoliver commented 9 months ago

Yes we originally wanted n=0 (or perhaps 1) at the scheduler, since that's all it needs to schedule.

oliver-sanders commented 9 months ago

(the only reason we would want n=1 at the scheduler is Tui, we can drop back to n=0 if there isn't a client connected)

oliver-sanders commented 7 months ago

See also #194