RHGitaly: Efficient and scalable front server layer in Rust
With the recent increase in concurrency of CI jobs on foss.heptapod.net, we started getting errors due to saturation of HGitaly workers, ending with GitCommandError: Deadline exceeded
errors on the Rails side.
Some reminders:
- HGitaly uses a prefork model, because Mercurial is not generally designed for multithreading (more specifically, different threads opening a given repository).
- we can increase the number of workers only up to a point because of total memory footprint
- some gRPC methods are very slow by nature, e.g. those that produce archives, and bundles. Some even depend on network conditions and servers (pull). It wouldn't be a problem if they didn't block the way for fast, routine methods, such as finding commit details by changeset node id. This is of course a classical limitation of prefork operation.
To solve this, we need a way to handle fast requests with low memory consumption, no matter how many concurrent heavy requests are being processed.
The current plans are to provide a front server layer for a limited subset of requests, with the following features:
- act as a proxy to the regular HGitaly for requests not implemented in the front layer
- fully implemented in Rust, depending on the
hg-core
crate - either spawning threads dynamically or based on asynchronous scheduling
- designed to be insensitive to data contention (ideally, lock-free)
Because 1. and 2. above would make it the HGitaly equivalent of rhg
compared to the regular hg
executable, we're nicknaming this effort RHGitaly.
The crucial feature 4. is made possible only for a small subset of Mercurial operations, which matches what we want. For example, to return author and message from a changeset node id, we need a read only access to the persistent nodemap and the changelog, both already having Rust implementations. Moreover, the append-only nature of the underlying data[1] makes it operate naturally on a snapshot if there turns out to be a concurrent write. File caching is provided for free by the kernel, and of course we'll be using mmap(2)
as much as possible.
A more extreme example: it's sad to see RepositoryExists
fail because all workers are busy: it does not even need Mercurial implementation, checking for a directory presence in the file system is all that is required. This is, in fact, a prime candidate to implement to focus on gRPC setup and proxying to the regular HGitaly.
[1] let's keep hg strip
out of the equation, as we're already doing