improve settings cascade performance
Created by: camdencheek
This PR improves the performance of generating merged settings through the settings cascade on sourcegraph.com from ~20ms to ~9ms.
Generating the settings from a cascade of user, org, and site configs is on the critical path of every search request. Currently, it adds ~20ms to the latency of every search request on sourcegraph.com, which is roughly 30% of our backend latency for fast-responding searches. This PR reduces that latency to ~9ms.
Merging settings is surprisingly slow because:
- We fetch settings string for each subject, unmarshal them into
map[string]interface{}
, merge them together, marshal back to a JSON string, then finally unmarshal again back into*schema.Settings
- We wait until the settings for every subject is fetched before starting to merge
- (last but not least) The JSON representation of the sourcegraph.com site settings is a whooping 244 kilobytes, composed mostly of repogroup definitions.
json.Unmarshal()
takes about 7 milliseconds for this.
What does this PR do to improve things?
- It pulls
(*settingsCascade).finalTyped()
, out of(*settingsCascade).Final()
so our search backend can fetch a*schema.Settings
without paying the JSON serialization roundtrip cost.Final()
still behaves the same by marshalling the result offinalTyped()
to a string. - It unmarshals the settings for the subject inside the goroutine pool instead of after the pool finishes so unmarshalling is done in parallel
- Instead of unmarshalling to
map[string]interface{}
, merging a bunch of interfaces, then marshalling back to a string before ultimately unmarshalling into*schema.Settings
it merges*schema.Settings
structs directly using reflection. This avoids a full JSON roundtrip, and (IMO), is easier to understand. - Now that we use reflection, tests for merging can be more accurate, comprehensive, and descriptive because we can directly test merging
*schema.Settings
structs more easily. - Instead of duplicating the deeply-merged field names as strings which are separated from the types they refer to, merge depth is added as a struct tag.
How can we reduce this even further? Well, currently, ~7ms of the remaining ~9ms is spent on the single unmarshal of the sourcegraph.com global site settings. It takes a long time because of repogroups. With repogroups being deprecated, I expect them to be removed from the site settings and for this to just resolve itself, dropping down to the ~2ms that it now takes to fetch and cascade most normal settings.
P.S. I apologize for the bad diff. git
chose some weird spots to hunk.