Docker presents new levels of portability and ease of use when it comes to deploying systems. We have for some time now released Dockerfiles and scripts for MySQL products, and are not surprised by it steadily gaining traction in the development community.
The typical concern that users have with any level of abstraction, is if there is a measurable level of overhead in doing so. We have conducted our own performance tests to understand the characteristics more closely, and in this post I will go into some detail as to what we have observed.
We’ve primarily focused on I/O and network overhead and compared the results to a stock instance of MySQL. Specifically we wanted to compare the performance between Docker’s different storage options and also how much overhead Docker’s bridged network brought into the picture. The tests have been run on a Oracle Server X5-2, with 2x Xeon E5-2660 v3 (40 hardware threads) and 256GB RAM using Docker version 1.11.2 on Ubuntu 16.04.
Docker has three different ways of providing persistent storage.
- The default is by using data volumes. This writes data to a directory on the host system using Docker’s internal volume management.
- The second way is by specifying a directory on the host system that will be mounted into a specified location inside the container.
- A third way is by creating a data volume container. Basically, this lets you create a shared container whose data volumes can be used by other containers.
Docker images are made up of a set of layers stacked on top of each other, where each layer represent file system differences. Docker’s storage drivers are responsible for stacking the image layers and creating a single unified view of the image. However, data volumes and host directories bypass Docker’s storage driver and should therefore run at near-native speeds. For our tests, we’ve used the AUFS storage driver as it’s the most widely used driver.
For measuring network overhead, we’ve conducted tests using Docker’s host and bridge networks, specified by --net=host or --net=bridged respectively when creating your container. Bridged networking is the default when creating containers. Host networking on the other hand adds a container on the host’s network stack, and should thus avoid the overhead typically imposed by the bridged network.
For these tests, we used a custom configuration file. We first deliberately set the buffer pool size to around 10% of the total database size in order to increase I/O-bound load. The database size was 2358MB, so we set our buffer pool size to 256MB. We then increased the buffer size to 16384MB to see what happens when Docker isn’t bound by I/O load. Specifying a custom configuration file for MySQL with Docker is relatively straight-forward. The easiest way to go about this is by mounting your config file into /etc/my.cnf on your container. This is done by passing -v /path/to/host/my.cnf:/etc/my.cnf to Docker when creating the container. You could also manually modify the container and commit your changes back into a new image, but this is somewhat impractical as you’ll have to do this procedure each time you update MySQL.
We’ve conducted tests using sysbench. We ran the following command in order to set up our databases for testing.
./sysbench --test=oltp --oltp-table-size=10000000 \
--db-driver=mysql --mysql-db=dbtest --mysql-user=root \
--mysql-password=aTestPwd --mysql-host=127.0.0.1 prepare
Warm-up was performed by first running a test like the one outlined below for 320 seconds. The full test was run thereafter. Each test was run three times, and the results in the table below reflect the average of those three runs.
We changed the --mysql-host address as needed when running with the bridged network.
The tests produced the following results:
|Stock instance running on host||847|
|Docker Volume –net=host||857|
|Docker Hostdir –net=host||850|
|Docker Datacontainer –net=host||849|
|Docker Volume –net=bridged||868|
|Docker Hostdir –net=bridged||846|
|Docker Datacontainer –net=bridged||856|
As we can see, heavy I/O-bound load give rather even results. There’s no measurable I/O nor network overhead imposed by Docker. In this scenario, MySQL under Docker performs just as well as running MySQL directly on the host computer. Also note that Docker’s storage options all have almost equal performance with no option being significantly better than the others.
We then scaled up our buffer pool size to 16384MB to see what happens when Docker isn’t bound by I/O load. We then ran the tests once again.
|Stock instance running on host||12686|
|Docker Volume –net=host||12201|
|Docker Hostdir –net=host||12180|
|Docker Datacontainer –net=host||12233|
|Docker Volume –net=bridged||11698|
|Docker Hostdir –net=bridged||11644|
|Docker Datacontainer –net=bridged||11662|
These results show that Docker imposes some overhead compared to a stock instance running on the host. There also is a minor network overhead, although not as substantial as with previous versions of Docker. We also note that there’s practically no difference between Docker’s various storage options.
The key takeaway here is that heavy I/O-bound load evens out the differences which might otherwise be present, resulting in Docker performing just as well as a stock instance. When not bound by I/O, Docker imposes a minor overhead, especially when running through the bridged network. Given the results, there’s no major difference between the various storage options and it therefore comes down to what’s most convenient when choosing your storage option. It’s also important to tune your configuration file for optimum performance, even when MySQL runs inside a Docker container.
MySQL with Docker has this far mainly seen use in testing and development environments, but with these results we clearly see that there is no performance-wise reason for not considering the use of MySQL with Docker in production as well.