MariaDB 5.5 introduced an improved thread pool.
The task of scalable server software (and a DBMS like MariaDB is an example of such software) is to maintain top performance with an increasing number of clients. MySQL traditionally assigned a thread for every client connection, and as the number of concurrent users grows this model shows performance drops. Many active threads are a performance killer, because increasing the number of threads leads to extensive context switching, bad locality for CPU caches, and increased contention for hot locks. An ideal solution that would help to reduce context switching is to maintain a lower number of threads than the number of clients. But this number should not be too low either, since we also want to utilize CPUs to their fullest, so ideally, there should be a single active thread for each CPU on the machine.
The current MariaDB thread pool was implemented in MariaDB 5.5. It replaced the legacy thread pool that was introduced in MariaDB 5.1. The main drawback of the previous solution was that this pool was static–it had a fixed number of threads. Static thread pools can have their merits, for some limited use cases, such as cases where callbacks executed by the threads never block and do not depend on each other. For example, iimagine something like an echo server.
However, DBMS clients are more complicated. For example, a thread may depend on another thread's completion, and they may block each other via locks and/or I/O. Thus it is very hard, and sometimes impossible, to predict how many threads would be ideal or even sufficient to prevent deadlocks in every situation. MariaDB 5.5 implements a dynamic/adaptive pool that itself takes care of creating new threads in times of high demand and retiring threads if they have nothing to do. This is a complete reimplementation of the legacy pool-of-threads
scheduler, with the following goals:
There are currently two different low-level implementations – depending on OS. One implementation is designed specifically for Windows which utilizes a native CreateThreadpool
API. The second implementation is primarily intended to be used in Unix-like systems. Because the implementations are different, some system variables differ between Windows and Unix.
Thread pools are most efficient in situations where queries are relatively short and the load is CPU-bound, such as in OLTP workloads. If the workload is not CPU-bound, then you might still benefit from limiting the number of threads to save memory for the database memory buffers.
There are special, rare cases where the thread pool is likely to be less efficient.
thread_pool_idle_timeout
on Unix, or with thread_pool_min_threads
on Windows. extra-port
TCP/IP port. SELECT 1
, might take a bit longer when the thread pool is enabled than with one-thread-per-connection
if it gets queued. The thread_handling
system variable is the primary system variable that is used to configure the thread pool.
There are several other system variables as well, which are described in the sections below. Many of the system variables documented below are dynamic, meaning that they can be changed with SET GLOBAL
on a running server.
Generally, there is no need to tweak many of these system variables. The goal of the thread pool was to provide good performance out-of-the box. However, the system variable values can be changed, and we intended to expose as many knobs from the underlying implementation as we could. Feel free to tweak them as you see fit.
If you find any issues with any of the default behavior, then we encourage you to submit a bug report.
See Thread Pool System and Status Variables for the full list of the thread pool's system variables.
On Unix, if you would like to use the thread pool, then you can use the thread pool by setting the thread_handling
system variable to pool-of-threads
in a server option group in an option file prior to starting up the server. For example:
[mariadb] ... thread_handling=pool-of-threads
The following system variables can also be configured on Unix:
thread_pool_size
– The number of thread groups in the thread pool, which determines how many statements can execute simultaneously. The default value is the number of CPUs on the system. When setting this system variable's value at system startup, the max value is 100000. However, it is not a good idea to set it that high. When setting this system variable's value dynamically, the max value is either 128 or the value that was set at system startup--whichever value is higher. See Thread Groups in the Unix Implementation of the Thread Pool for more information. thread_pool_max_threads
– The maximum number of threads in the thread pool. Once this limit is reached, no new threads will be created in most cases. In rare cases, the actual number of threads can slightly exceed this, because each thread group needs at least two threads (i.e. at least one worker thread and at least one listener thread) to prevent deadlocks. The default value in MariaDB 5.5 and MariaDB 10.0 is 500
. The default value in MariaDB 10.1 is 1000
in MariaDB 10.1. The default value in MariaDB 10.2 and later is 65536
. thread_pool_stall_limit
– The number of milliseconds between each stall check performed by the timer thread. The default value is 500
. Stall detection is used to prevent a single client connection from monopolizing a thread group. When the timer thread detects that a thread group is stalled, it wakes up a sleeping worker thread in the thread group, if one is available. If there isn't one, then it creates a new worker thread in the thread group. This temporarily allows several client connections in the thread group to run in parallel. However, note that the timer thread will not create a new worker thread if the number of threads in the thread pool is already greater than or equal to the maximum defined by the thread_pool_max_threads
variable, unless the thread group does not already have a listener thread. See Thread Groups in the Unix Implementation of the Thread Pool: Thread Group Stalls for more information. thread_pool_oversubscribe
– Determines how many worker threads in a thread group can remain active at the same time once a thread group is oversubscribed due to stalls. The default value is 3
. Usually, a thread group only has one active worker thread at a time. However, the timer thread can add more active worker threads to a thread group if it detects a stall. There are trade-offs to consider when deciding whether to allow only one thread per CPU to run at a time, or whether to allow more than one thread per CPU to run at a time. Allowing only one thread per CPU means that the thread can have unrestricted access to the CPU while its running, but it also means that there is additional overhead from putting threads to sleep or waking them up more frequently. Allowing more than one thread per CPU means that the threads have to share the CPU, but it also means that there is less overhead from putting threads to sleep or waking them up. This is primarily for internal use, and it is not meant to be changed for most users. See Thread Groups in the Unix Implementation of the Thread Pool: Thread Group Oversubscription for more information. thread_pool_idle_timeout
– The number of seconds before an idle worker thread exits. The default value is 60
. If there is currently no work to do, how long should an idle thread wait before exiting? The Windows implementation of the thread pool uses a native thread pool created with the CreateThreadpool
API.
On Windows, if you would like to use the thread pool, then you do not need to do anything, because the default for the thread_handling
system variable is already preset to pool-of-threads
.
However, if you would like to use the old one thread per-connection behavior on Windows, then you can use use that by setting the thread_handling
system variable to one-thread-per-connection
in a server option group in an option file prior to starting up the server. For example:
[mariadb] ... thread_handling=one-thread-per-connection
On older versions of Windows, such as XP and 2003, pool-of-threads
is not implemented, and the server will silently switch to using the legacy one-thread-per-connection
method.
The native CreateThreadpool
API allows applications to set the minimum and maximum number of threads in the pool. The following system variables can be used to configure those values on Windows:
thread_pool_min_threads
– The minimum number of threads in the pool. Default is 1. This applicable in a special case of very “bursty” workloads. Imagine having longer periods of inactivity after periods of high activity. While the thread pool is idle, Windows would decide to retire pool threads (based on experimentation, this seems to happen after thread had been idle for 1 minute). Next time high load comes, it could take some milliseconds or seconds until the thread pool size stabilizes again to optimal value. To avoid thread retirement, one could set the parameter to a higher value. thread_pool_max_threads
– The maximum number of threads in the pool. Threads are not created when this value is reached. The default from MariaDB 5.5 to MariaDB 10.0 is 500 (this has been increased to 1000 in MariaDB 10.1). This parameter can be used to prevent the creation of new threads if the pool can have short periods where many or all clients are blocked (for example, with “FLUSH TABLES WITH READ LOCK”, high contention on row locks, or similar). New threads are created if a blocking situation occurs (such as after a throttling interval), but sometimes you want to cap the number of threads, if you’re familiar with the application and need to, for example, save memory. If your application constantly pegs at 500 threads, it might be a strong indicator for high contention in the application, and the thread pool does not help much. Starting with MariaDB 10.2.2, it is possible to configure connection prioritization. The priority behavior is configured by the thread_pool_priority
system variable.
By default, if thread_pool_priority
is set to auto
, then queries would be given a higher priority, in case the current connection is inside a transaction. This allows the running transaction to finish faster, and has the effect of lowering the number of transactions running in parallel. The default setting will generally improve throughput for transactional workloads. But it is also possible to explicitly set the priority for the current connection to either 'high' or 'low'.
There is also a mechanism in place to ensure that higher priority connections are not monopolizing the worker threads in the pool (which would cause indefinite delays for low priority connections). On Unix, low priority connections are put into the high priority queue after the timeout specified by the thread_pool_prio_kickup_timer
system variable.
MariaDB allows you to configure an extra port for administrative connections. This is primarily intended to be used in situations where all threads in the thread pool are blocked, and you still need a way to access the server. However, it can also be used to ensure that monitoring systems (including MaxScale's monitors) always have access to the system, even when all connections on the main port are used. This extra port uses the old one-thread-per-connection
thread handling.
You can enable this and configure a specific port by setting the extra_port
system variable.
You can configure a specific number of connections for this port by setting the extra_max_connections
system variable.
These system variables can be set in a server option group in an option file prior to starting up the server. For example:
[mariadb] ... extra_port = 8385 extra_max_connections = 10
Once you have the extra port configured, you can use the mysql
client with the -P
option to connect to the port.
$ mysql -u root -P 8385 -p
Currently there are two status variables exposed to monitor pool activity.
Variable | Description |
---|---|
Threadpool_threads |
Number of threads in the thread pool. In rare cases, this can be slightly higher than thread_pool_max_threads , because each thread group needs at least two threads (i.e. at least one worker thread and at least one listener thread) to prevent deadlocks. |
Threadpool_idle_threads |
Number of inactive threads in the thread pool. Threads become inactive for various reasons, such as by waiting for new work. However, an inactive thread is not necessarily one that has not been assigned work. Threads are also considered inactive if they are being blocked while waiting on disk I/O, or while waiting on a lock, etc. This status variable is only meaningful on Unix. |
On Unix, the thread pool implementation uses objects called thread groups to divide up client connections into many independent sets of threads. See Thread Groups in the Unix Implementation of the Thread Pool for more information.
When using global locks, even with a high value on the thread_pool_max_threads
system variable, it is still possible to block the entire pool.
Imagine the case where a client performs FLUSH TABLES WITH READ LOCK
then pauses. If then the number of other clients connecting to the server to start write operations exceeds the maximum number of threads allowed in the pool, it can block the Server. This makes it impossible to issue the UNLOCK TABLES
statement. It can also block MaxScale from monitoring the Server.
To mitigate the issue, MariaDB allows you to configure an extra port for administrative connections. See Configuring the Extra Port for information on how to configure this.
Once you have the extra port configured, you can use the mysql
client with the -P
option to connect to the port.
$ mysql -u root -P 8385 -p
This ensures that your administrators can access the server in cases where the number of threads is already equal to the configured value of the thread_pool_max_threads
system variable, and all threads are blocked. It also ensures that MaxScale can still access the server in such situations for monitoring information.
Once you are connected to the extra port, you can solve the issue by increasing the value on the thread_pool_max_threads
system variable, or by killing the offending connection, (that is, the connection that holds the global lock, which would be in the sleep
state).
Commercial editions of MySQL since 5.5 include an Oracle MySQL Enterprise thread pool implemented as a plugin, which delivers similar functionality. Official documentation of the feature can be found in the MySQL Reference Manual and a detailed discussion about the design of the feature is at Mikael Ronstrom's blog. Here is the summary of similarities and differences, based on the above materials.
Percona's implementation is a port of the MariaDB's threadpool with some added features. In particular, Percona added priority scheduling to its 5.5-5.7 releases. MariaDB 10.2 and Percona priority scheduling work in a similar fashion, but there are some differences in details.
Low-level implementation details are documented in the WL#246
When running sysbench and maybe other benchmarks, that create many threads on the same machine as the server, it is advisable to run benchmark driver and server on different CPUs to get the realistic results. Running lots of driver threads and only several server threads on the same CPUs will have the effect that OS scheduler will schedule benchmark driver threads to run with much higher probability than the server threads, that is driver will pre-empt the server. Use "taskset –c" on Linuxes, and "set /affinity" on Windows to separate benchmark driver and server CPUs, as the preferred method to fix this situation.
A possible alternative on Unix (if taskset or a separate machine running the benchmark is not desired for some reason) would be to increase thread_pool_size to make the server threads more "competitive" against the client threads.
When running sysbench, a good rule of thumb could be to give 1/4 of all CPUs to the sysbench, and 3/4 of CPUs to mysqld. It is also good idea to run sysbench and mysqld on different NUMA nodes, if possible.
The thread_cache_size system variable is not used when the thread pool is used and the Threads_cached status variable will have a value of 0.
© 2019 MariaDB
Licensed under the Creative Commons Attribution 3.0 Unported License and the GNU Free Documentation License.
https://mariadb.com/kb/en/thread-pool-in-mariadb/