It’s more like goroutines or other lightweight concurrency mechanisms. If threads are OS-level concurrency primitives, fibers are scheduled within Ruby itself, which makes them much more efficient than threads.
In fact, I got the following results in HTTP benchmark tests:
Go:
* Latency under stable load: p95 0.25–5.32 ms, p99 2.20–9.92 ms
* Memory: 23–31 MB RSS across HTTP scenarios
Ruby:
* Latency under stable load: p95 1.03–6.45 ms, p99 2.32–8.30 ms
* Memory: 84–295 MB RSS, depending on the scenario
Fibers can also handle WebSockets better because WebSocket workloads involve more I/O waiting.
A typical Falcon setup uses N workers, one thread per worker, and many fibers. Since the fibers are cooperatively scheduled within a single thread, this avoids much of the context-switching overhead associated with OS threads. Multiple workers can still run in parallel across CPU cores.
Some programmers find it easier to write concurrent programs that use "light-weight threads", "fibers", "goroutines", "coroutines" or other variants of this idea.
This feature is obviously intended for them and it might enhance their productivity.
Nonetheless, no program that uses a great number of any variant of the "light-weight threads" can ever be as efficient as a thread pool that is dimensioned to have the same number of threads as the number of hardware threads of a SMT CPU, or a slightly greater number of threads than the number of hardware threads of a non-SMT CPU.
For maximum performance, the use of a correctly-sized thread pool remains the best solution, but writing an efficient program that uses it can be significantly more difficult, because good methods of communication and synchronization must be implemented, while the run-time library of a language with "light-weight threads"/"fibers"/etc. already takes care of such problems so the programmer does not need to think about them.
My concern is number of database connections. In the example it's 100 fibers per worker, at that rate you are going to exhaust db connections sooner. Happy to be wrong.
I think you want to make sure you don't hold a database connection while waiting on other slow async work (like outgoing HTTP requests). Then you can more feasibly have more workers than pool size. It's just very tricky to do this in Rails...
I don't know Solid Queue or the rails environment, but I expect that not every worker will create it's own connection, there should be a connection pool in-between
I think by default they check out a new connection when obtained a job and then release it. In comparison with some async languages like JS where a connection is only checked when a query is about to be executed
In fact, I got the following results in HTTP benchmark tests:
Go:
* Latency under stable load: p95 0.25–5.32 ms, p99 2.20–9.92 ms * Memory: 23–31 MB RSS across HTTP scenarios
Ruby:
* Latency under stable load: p95 1.03–6.45 ms, p99 2.32–8.30 ms * Memory: 84–295 MB RSS, depending on the scenario
Fibers can also handle WebSockets better because WebSocket workloads involve more I/O waiting.
A typical Falcon setup uses N workers, one thread per worker, and many fibers. Since the fibers are cooperatively scheduled within a single thread, this avoids much of the context-switching overhead associated with OS threads. Multiple workers can still run in parallel across CPU cores.
This feature is obviously intended for them and it might enhance their productivity.
Nonetheless, no program that uses a great number of any variant of the "light-weight threads" can ever be as efficient as a thread pool that is dimensioned to have the same number of threads as the number of hardware threads of a SMT CPU, or a slightly greater number of threads than the number of hardware threads of a non-SMT CPU.
For maximum performance, the use of a correctly-sized thread pool remains the best solution, but writing an efficient program that uses it can be significantly more difficult, because good methods of communication and synchronization must be implemented, while the run-time library of a language with "light-weight threads"/"fibers"/etc. already takes care of such problems so the programmer does not need to think about them.
Is it possible to either have multiple ractors dispatching jobs with fibres or to set up multiple queues with different strategies?
E.g. one for IO bound and one for CPU bound?
With Sidekiq I’ve had luck having workers running on Truffleruby but generally don’t use it for my main rails apps.