frontend: Speed up repository listing by increasing batch size
Created by: mrnugget
This fixes #4093 by increasing the number of repositories the GraphQL backend loads into memory before it filters them based on their clone status.
The previous code was slow when a user applied a filter that doesn't yield any repositories, e.g.: filtering for "not cloned" when every repository has been cloned.
Why? Because in order to display 20 repositories on the site-admin page, what the code did was to...
- load 20 repositories from the database
- ask gitserver for their clone-status
- throw away the ones that away that didn't match the filter
- if number of repositories left was under 20, goto 1
In other words: we scanned the database in batches of 20 until we found 20 repositories that matched the criteria or we reached the end of the database table.
What this PR does is to change the code to always load at least 500 repositories from the database. That decreases the number of required roundtrips to the database and gitserver to scan the whole table for the clone status.
The trade-off we're making here is to trade in memory footprint and CPU cost for decreased latency.
Before this change, with ~4200 entries in the repo
table, it took ~20
seconds to fetch the first 20 repositories.
$ time curl 'http://localhost:3080/.api/graphql?Repositories' \
-H 'Authorization: token-sudo user="mrnugget",token="secret-token"' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
--data-binary '{"query":"query Repositories( $first: Int $query: String $cloned: Boolean $cloneInProgress: Boolean $notCloned: Boolean $indexed: Boolean $notIndexed: Boolean ) { repositories( first: $first query: $query cloned: $cloned cloneInProgress: $cloneInProgress notCloned: $notCloned indexed: $indexed notIndexed: $notIndexed ) { nodes { id name createdAt viewerCanAdminister url mirrorInfo { cloned cloneInProgress updatedAt } } totalCount(precise: true) pageInfo { hasNextPage } } }","variables":{"cloned":true,"cloneInProgress":false,"notCloned":false,"indexed":true,"notIndexed":true,"first":20,"query":""}}'
[...]
0.01s user 0.01s system 0% cpu 20.357 total
With the changes in this PR, the request is ~13 times faster:
0.01s user 0.01s system 1% cpu 1.569 total
The big caveat of this solution is that we need to do an in-memory sort after
filtering through the whole list of repositories and currently do not take into
account sorting by a column other than name
.
But that was already broken, since in the middle of the loop we reconstruct the slice of repos (which came sorted from the database) by looping over the map of results returned by gitserver. Iterating over maps is unstable in Go, so the resulting slice has a different order than the one we got from the database.
With the previous code, though, the problem was nuanced, but now the results can vary a lot, since you pick a different 20-item-window out of 500 with every reload on the repositories page. To fix that, I put in the sort-by-name at the end.
Test plan: go test & manual testing of repository page in site-admin area
Note to reviewers: I'll try to tackle the preexisting-sorting-issue mentioned in the commit message in a separate commit/branch. Wanted to get your input on this change first.
@slimsag I added you because I think you were the last one to optimize that code and probably have a good mental model of how it works :)