-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Addons: make the API endpoint as cacheable as possible #10536
Comments
We also talked about moving these to query string paramenters because it's easier to cache them than HTTP headers: https://developers.cloudflare.com/cache/how-to/cache-keys/#create-custom-cache-keys |
I took a look at the Algolia integration we have setup on https://rtd-with-docsearch.readthedocs.io/en/latest/ and it seems they send all the versions in the query string. Something to consider 😉 |
Move `X-RTD-Hosting-Integration-Version` header to `api-version` querystring parameter. This has some benefits: - make links to this URL clickable (there is no need to use a browser extension to send a specific header in the request) - easier to cache using the default strategy supported by CF Related #10536
Move `X-RTD-Hosting-Integration-Version` header to `api-version` querystring parameter. This has some benefits: - make links to this URL clickable (there is no need to use a browser extension to send a specific header in the request) - easier to cache using the default strategy supported by CF Related #10536
I implemented this part in #10753 and readthedocs/addons#138 |
Currently, we are using However, it would be interesting to figure it out how to do this because in that case, it will allow us to cache the request only "per project & version & build 1" instead of "per project & version & build & exact URL" as we are currently doing now. There are some ideas here:
I think 1) is probably the easiest and simplest option since 2) seems pretty hacky 😅 . Following that pattern, I think this could be as follows:
This sounds pretty simple to understand and follow in my head, but I've been thinking on this pattern for some time already. I'd like to get some feedback on this and decide how we want to move forward. Note that this will have an impact in our web instances and also in our database. Besides, it will make our addons rendering a lot faster since most of the requests to the endpoint will be cached and returned directly from CF. Currently, the numbers for this endpoint in the last 7 days the response times are:
Footnotes
|
Move `X-RTD-Hosting-Integration-Version` header to `api-version` querystring parameter. This has some benefits: - make links to this URL clickable (there is no need to use a browser extension to send a specific header in the request) - easier to cache using the default strategy supported by CF Related #10536
Hrm, thinking a little more about this, isn't this the same as passing the Maybe option 2), even being hacky 🕵️, is the only option that works how we want 😄 |
Oh, wait! We are already returning
With that data we can call our addons API as follows: One thing that I'm not sure how to do is how to get those headers from a request that was done before our fetch(window.location.href, {method: "HEAD"})
.then((response) => {
const projectSlug = response.headers.get("x-rtd-project"))
const versionSlug = response.headers.get("x-rtd-version"))
fetch("/_/addons/?" + new URLSearchParams({ "project__slug": projectSlug, "version__slug": versionSlug})
.then((response) => {
// ...
});
}
); |
Even doing an extra With this approach, we will have two different requests:
With this, our CDN will handle the cache correctly since it will treat the URLs containing only the |
Now, with this, we will need to know before performing the request what addons "could be enabled" on the current page to decide whether or not include the I imagine something like: let addons = [ ... ];
let params = {"client-version": "...", "api-version": "..."};
let sendUrlParam = false;
for (const addon of addons) {
if (addon.requiresURL() && addon.couldBeEnabledOnPage()) {
sendUrlParam = true;
break
}
}
if (sendUrlParam) {
params.url = window.location.href;
}
...
fetch(url, ....); Then, an addons like DocDiff would implement: export class DocDiffAddon extends AddonBase {
static requiresUrl() {
return true;
}
static couldBeEnabledOnPage() {
return window.location.host.endsWith(".readthedocs.build");
}
} Links to addons code: |
@humitos I don't love the idea of doing an extra HEAD request, because that will increase latency on the rendering of the addons (2 complete HTTP round trips after page load). If we're already injecting data into the page to add the addons JS, then injecting the value of the project & version slug doesn't seem like additional hackiness, and if it saves us that round trip, likely worth doing. I don't believe there's a way to capture the headers from the request in our JS, which would be a really nice solution to the problem.
Is this the plan we're going with, assuming that we get the project & version slug somehow..., either via a HEAD request, or injected into the page? This logic looks 💯 to me. I think the above logic is great, and then we can try injecting the project/version values in the CF worker, and if that doesn't work, always fall back to a HEAD request. (Or some other idea we haven't come up with yet 🤔... I think perhaps we could also just parse the URL in JS and only send the hostname and matching version path to the API, but that's harder with the custom URLconf work we've been doing, which would be additional data coming from the server that we need to have in the client, which is the primary problem we're trying to solve here..) |
Thanks @ericholscher for your input here. Summarizing, this is the current plan that I will implement then:
Let me know if you agree with the current plan.
I thought about this already and I didn't see it clearly since we would have to re-implement the Python resolver logic into JavaScript and always keep it updated with all the changes/feature we introduce on it. I thought that would be fragile and we will end up with bugs on both making them behave differently and hard to debug in the end 🤷🏼 |
@humitos that plan sounds 💯. |
This is done and deployed already. See it in action at https://test-builds.readthedocs.io/en/latest/ |
Follow the plan discussed in readthedocs/readthedocs.org#10536 Grab the `project-slug` and `version-slug` from the `meta` tag injected by CF and send it back to the backend. If there is any addons that can be enabled in the current page that depends on the URL, it will send it also. Each Addon class should implement `requireUrlParam` to decide whether or not to send the `url=` to the backend API or not.
`/_/addons/` API endpoint now accepts `project-slug` and `version-slug`. This is useful to be able to cache these requests in our CDN. It still supports sending `url=` when necessary for those requests that relies on the full URL to generate specific data in the response. The NGINX config is updated to inject `meta` tags with project/version slugs, emulating what our CF worker does on production. Closes #10536
Follow the plan discussed in readthedocs/readthedocs.org#10536 Grab the `project-slug` and `version-slug` from the `meta` tag injected by CF and send it back to the backend. If there is any addons that can be enabled in the current page that depends on the URL, it will send it also. Each Addon class should implement `requireUrlParam` to decide whether or not to send the `url=` to the backend API or not. API changes in: readthedocs/readthedocs.org#10823
* Addons: accept `project-slug` and `version-slug` on endpoint `/_/addons/` API endpoint now accepts `project-slug` and `version-slug`. This is useful to be able to cache these requests in our CDN. It still supports sending `url=` when necessary for those requests that relies on the full URL to generate specific data in the response. The NGINX config is updated to inject `meta` tags with project/version slugs, emulating what our CF worker does on production. Closes #10536 * Add tests and update logic for API permissions
Once we are fully migrated to the new Addons, we will be hitting the
/_/addons/
endpoint a lot. This endpoint will be hit by every single request on a documentation page view. We would like to make this endpoint as fast as possible, but also try to cache it to avoid hitting our servers. It can be compared with the current Footer API.The
/_/addons/
endpoint needs to be cached by:url
query string parameterX-RTD-Hosting-Integrations-Version
HTTP headerX-RTD-Hosting-Integrations-API-Version
HTTP header (once API: return theapi_version
on the response #10276 gets implemented)Note the endpoint is behind authentication, so that it's not an issue here.
To build the response, the endpoint depends on:
We need to use the "backend resolver" to build the
base_url
required by the DocDiff addons. That URL depends on the exact filename of the page, so we need cache byurl
parameter. We could talk about different ideas here to be able to not depend on it if we want to. Some ideas the mentioned:docdiff=[true|false]
that's onlytrue
on pageviews from*.readthedocs.build
The last item seems to be the easiest one and will give us most of the benefits, since we 95% of our traffic will call the endpoint with
docdiff=false
😄The text was updated successfully, but these errors were encountered: