<div dir="ltr"><div dir="ltr"><div dir="ltr">Hi all,<div><br></div><div>I&#39;ve just updated a patch [1] that implements a new thread pool based on a wait-free queue provided by userspace-rcu library. The patch also includes an auto scaling mechanism that only keeps running the needed amount of threads for the current workload.</div><div><br></div><div>This new approach has some advantages:</div></div><div><ul><li>It&#39;s provided globally inside libglusterfs instead of inside an xlator<br></li></ul></div><blockquote style="margin:0px 0px 0px 40px;border:none;padding:0px"><div dir="ltr"><div>This makes it possible that fuse thread and epoll threads transfer the received request to another thread sooner, wating less CPU and reacting sooner to other incoming requests.</div></div></blockquote><ul><li>Adding jobs to the queue used by the thread pool only requires an atomic operation</li></ul><div><blockquote style="margin:0px 0px 0px 40px;border:none;padding:0px"><div>This makes the producer side of the queue really fast, almost with no delay.</div></blockquote></div><ul><li>Contention is reduced</li></ul><div><blockquote style="margin:0px 0px 0px 40px;border:none;padding:0px"><div>The producer side has negligible contention thanks to the wait-free enqueue operation based on an atomic access. The consumer side requires a mutex, but the duration is very small and the scaling mechanism makes sure that there are no more threads than needed contending for the mutex.</div></blockquote></div><div><br></div>This change disables io-threads, since it replaces part of its functionality. However there are two things that could be needed from io-threads:<div><ul><li>Prioritization of fops</li></ul></div><blockquote style="margin:0px 0px 0px 40px;border:none;padding:0px"><div><div>Currently, io-threads assigns priorities to each fop, so that some fops are handled before than others.</div></div></blockquote><div><ul><li>Fair distribution of execution slots between clients</li></ul></div><blockquote style="margin:0px 0px 0px 40px;border:none;padding:0px"><div><div>Currently, io-threads processes requests from each client in round-robin.</div></div></blockquote><div><div dir="ltr"><div><br></div><div>These features are not implemented right now. If they are needed, probably the best thing to do would be to keep them inside io-threads, but change its implementation so that it uses the global threads from the thread pool instead of its own threads.</div><div><br></div><div>If this change proves it&#39;s performing better and is merged, I have some more ideas to improve other areas of gluster:</div><div><ul><li>Integrate synctask threads into the new thread pool</li></ul></div></div></div></div><blockquote style="margin:0 0 0 40px;border:none;padding:0px"><div dir="ltr"><div><div dir="ltr"><div><div>I think there is some contention in these threads because during some tests I&#39;ve seen they were consuming most of the CPU. Probably they suffer from the same problem than io-threads, so replacing them could improve things.</div></div></div></div></div></blockquote><div dir="ltr"><div><div dir="ltr"><div><ul><li>Integrate timers into the new thread pool</li></ul></div></div></div></div><blockquote style="margin:0 0 0 40px;border:none;padding:0px"><div dir="ltr"><div><div dir="ltr"><div><div>My idea is to create a per-thread timer where code executed in one thread will create timer events in the same thread. This makes it possible to use structures that don&#39;t require any mutex to be modified.</div></div><div><br></div></div></div></div></blockquote><blockquote style="margin:0 0 0 40px;border:none;padding:0px"><div dir="ltr"><div><div dir="ltr"><div>Since the thread pool is basically executing computing tasks, which are fast, I think it&#39;s feasible to implement a timer in the main loop of each worker thread with a resolution of few millisecond, which I think is good enough for gluster needs.</div></div></div></div></blockquote><div dir="ltr"><div><div dir="ltr"><div><ul><li>Integrate with userspace-rcu library in QSBR mode</li></ul></div></div></div></div><blockquote style="margin:0 0 0 40px;border:none;padding:0px"><div dir="ltr"><div><div dir="ltr"><div><div>This will make it possible to use some RCU-based structures for anything gluster uses (inodes, fd&#39;s, ...). These structures have very fast read operations, which should reduce contention and improve performance in many places.</div></div></div></div></div></blockquote><div dir="ltr"><div><div dir="ltr"><div><ul><li>Integrate I/O threads into the thread pool and reduce context switches</li></ul></div></div></div></div><blockquote style="margin:0 0 0 40px;border:none;padding:0px"><div dir="ltr"><div><div dir="ltr"><div><div>The idea here is a bit more complex. Basically I would like to have a function that does an I/O on some device (for example reading fuse requests or waiting for epoll events). We could send a request to the thread pool to execute that function, so it would be executed inside one of the working threads. When the I/O terminates (i.e. it has received a request), the idea is that a call to the same function is added to the thread pool, so that another thread could continue waiting for requests, but the current thread will start processing the received request without a context switch.</div></div><div><br></div></div></div></div></blockquote>Note that with all these changes, all dedicated threads that we currently have in gluster could be replaced by the features provided by this new thread pool, so these would be the only threads present in gluster. This is specially important when brick-multiplex is used.<div><br><div dir="ltr"><div><div dir="ltr"><div>I&#39;ve done some simple tests using a replica 3 volume and a diserse 4+2 volume. These tests are executed on a single machine using an HDD for each brick (not the best scenario, but it should be fine for comparison). The machine is quite powerful (dual Intel Xeon Silver 4114 @2.2 GHz, with 128 GiB RAM).</div><div><br></div><div>These tests have shown that the limiting factor has been the disk in most cases, so it&#39;s hard to tell if the change has really improved things. There is only one clear exception: self-heal on a dispersed volume completes 12.7% faster. The utilization of CPU has also dropped drastically:</div><div><br></div></div></div><blockquote style="margin:0px 0px 0px 40px;border:none;padding:0px"><div><div dir="ltr"><div>Old implementation: <span style="color:rgb(0,0,0);font-family:monospace">12.30 user, 41.78 sys, 43.16 idle,  0.73 wait</span></div></div></div></blockquote><blockquote style="margin:0px 0px 0px 40px;border:none;padding:0px">New implementation: <span style="color:rgb(0,0,0);font-family:monospace">4.91 user,  5.52 sys, 81.60 idle,  5.91 wait</span></blockquote><span style="color:rgb(0,0,0);font-family:monospace"><div><span style="color:rgb(0,0,0);font-family:monospace"><br></span></div></span><div><div dir="ltr"><div>Now I&#39;m running some more tests on NVMe to try to see the effects of the change when disk is not limiting performance. I&#39;ll update once I&#39;ve more data.</div><div><br></div><div>Xavi</div><div><br></div><div>[1] <a href="https://review.gluster.org/c/glusterfs/+/20636">https://review.gluster.org/c/glusterfs/+/20636</a></div></div></div></div></div></div>