I remembered that Automattic had a plugin that cached those parts of WP_Query that aren't cached by core, so I decided to give it a try. https://github.com/Automattic/advanced-post-cache https://github.com/cuny-academic-commons/cac/commit/7b27805cae0164e649eaf801626415699c1ae40e
It does not work out of the box, because of an incompatibility between the function signature of wp_cache_get_multi() as it's defined in our cache drop-in, and the way that advanced-post-cache expects the function to work. I've fixed this in https://github.com/cuny-academic-commons/cac/commit/387d92f3625a6a2f18d6100a4930e8343cd8bba2
The plugin is imperfect. Notably, it can't properly cache queries that return empty arrays. This mostly appears to be the fault of WordPress, which doesn't run its found_posts logic (hooked into by advanced-post-cache) when no posts are found. This problem arose specifically in the case of the home page, where WP tries to load the latest non-public posts; since there aren't any, the query was returning an empty result, which was remaining uncached, and thus was being fired on every pageload. As a workaround, I've added a single blank 'post' to the Commons, which allows the plugin to cache the query.
Elsewhere on the main site, the plugin seems to be working as expected. It might be interesting to explore network-activating it, but let's hold off on that for the time being.
It looks at a glance like this plugin ought to eliminate the nav_menu_item queries throughout the main site, but it doesn't. See #10728. This needs investigation.
Down to 3 queries when loading the main page. Would like to get to zero :-D