DockerRunner performance improvement #142
Comments
Hi! Thanks for doing the investigation on this, it's nice to see some concrete numbers! We've had a quick discussion with the team and decided that the reason we didn't go for this pattern was that it would have it's own resource trade-offs (keeping containers alive in an idle-loop even while they're not necessarily active). At the moment we have logic to re-use an existing container if it's present on the system, so the initial cold-start cost should only be paid rarely, and most actions should be relatively fast (as you've discovered). Another thing that comes to mind, which would need to be investigated, is the kind of locking and synchronization logic that might need to be introduced to prevent overlapping commands from clobbering each other in the one container. I think we'll park this for the time being and keep it in mind for optimizing our production load in the future. In the meantime, do you have a sense of how this would change the performance characteristics on your own system under normal workloads? |
Hello @ShaneKilkelly, thank you for bringing this up with the team! There is already logic in place to prevent two compile requests from running in parallel on the same project/project+user directory 1. The other commands do not create new files and should be safe to run without any additional locking. In order to support the switching of the texlive image and more broader container option changes, I would suggest to keep the suffix in the container name. A hash of the container options, but without the command now. The command timeout and kill logic both need some tweaking to support the reuse of container. Now that I finished my studies - yay - my sharelatex setup is just under synthetic load and serving as a distributed system to experiment/work with. I will probably see no performance impact from this, but I will observe slightly better response times on paper 😉 and eventually notice it when working on the frontend/editor. |
Hi! Thank you for taking the time to write up this issue. We are in the process of migrating to a monorepo at https://github.com/overleaf/overleaf and will mark this repository read-only soon. We are going to close this issue now to avoid any confusion about the inability to comment further. If you believe this issue still needs addressing, please create a new issue at https://github.com/overleaf/overleaf. Thanks again! |
In the following substitute
project
withproject x user
for jailed compiles (disablePerUserCompiles
in the web config).The clsi app creates a new container for each unique command and project. A command could be a compile, word count, sync from pdf, sync from code request.
The first two commands scale OK, as there is only one container per project for the word count and one container per project and doc for the compile request. But the sync commands create a container for each unique lookup - unique per
line x column x project
(x doc
for sync from code).For the following examples I am using a VPS with dedicated threads of a fairly new server CPU (latest Xeon E5 clocked at 2.4Ghz). The VPS is running CentOS 7,
Docker version 19.03.5, build 633a0ea
andcontainerd 1.2.10 b34a5c8af56e510852c35414db4c1f4fa6172339
, overlay2 storage driver on xfs. The source of the docker image is hosted on Github.Creating and starting a new container has a time penalty of about 1000ms.
Starting an existing container has a time penalty of about 600ms.
Another method to run commands in containers is to split the creation of the container and the command execution into two stages.
The first stage creates an idle container [1]. And the second stage uses this idle container as a jailed environment to run commands.
Creating an idle container has a time penalty of about 700ms.
Running a command in the idle container has a time penalty of about 300ms.
The second method still has a delay of about 1000ms for the first command per project. But every following command runs at a must faster pace: recompile from 600ms down to 300ms, sync from pdf/code from 1000ms down to 300ms.
Resource usage of idle containers:
containerd
+ 1 PID forcat
/sleep
I am aware of your request distribution/scaling via a load agent, haproxy + a cookie, cookie storage in redis per project. For the proposed method the load agent may have to incorporate the memory (and PID [2]) usage as well. It really depends on the resource capacity of your worker nodes.
What do you think about this proposal?
cc @briangough @emcsween @henryoswald @mans0954 @mmazour @ShaneKilkelly
[1]: Using
sleep X
with a highX
- it is prone to race conditions with a low timeout: once the container is started, there is no way to extend the delay in order to preserve to container for another command.X=(DockerRunner.MAX_CONTAINER_AGE=60*60)*50=180000
should give the cleanup task enough tries to cycle the container before it times out. Another option here is to usecat
and a fake stdin/tty for the container, but this uses more resources - jailed stdin file handler and polling bycat
.[2]: The PID limit can be bumped up to 4 million via
echo X > /proc/sys/kernel/pid_max
.The text was updated successfully, but these errors were encountered: