Skip to content
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

Added experimental headers to allow caching members content #20200

Conversation

cmraible
Copy link
Contributor

@cmraible cmraible commented May 13, 2024

ref https://linear.app/tryghost/issue/KTLO-45/deploy-members-caching-solution-to-a-single-site-to-validate-and-test

Currently we only cache publicly available content. Any content that is accessed by a logged in member is only cached for that specific member based on their cookie. As a result, almost all requests from logged in members bypass our caching layer and reach Ghost, which adds unnecessary load to Ghost and its database.

This change adds experimental headers that allow our CDN to understand which tier to cache the content against, and securely tell the CDN which tier a logged in member has access to. With these changes, we can cache the member content against the tier, rather than the individual member, which should result in a higher cache HIT ratio and reduce the load on Ghost.

For requests to the frontend of the site, Ghost will set a custom X-Member-Cache-Tier header to the ID of the tier of the member who is accessing the content. This tells the CDN which tier to cache the content against.

For requests to either /members/?token=... endpoint (the magic link endpoint) or /members/api/member, Ghost will set a ghost-access and ghost-access-hmac cookie with the ID of the tier of the logged in member. With these two pieces of information, our CDN can serve cached content to logged in members.

These headers are experimental, and can only be enabled via Ghost's config. To enable these headers, set cacheMembersContent:enabled to true and provide an HMAC key in cacheMembersContent:hmacSecret.

@cmraible
Copy link
Contributor Author

cmraible commented May 14, 2024

Currently in a custom build on staging: 2898 2912 2918 2924

Staging site: https://cache-members-content-test.ghost.is/ghost/ (owner user info in 1pass)
Staging site ID: 312694

@cmraible cmraible force-pushed the chris-ktlo-45-deploy-caching-solution-to-a-single-site-to-validate-and branch 5 times, most recently from 6b2162f to dbd8afc Compare May 21, 2024 20:41
res.setHeader('Set-Cookie', cookiesToSet);
return;
}
const activeSubscription = req.member.subscriptions?.find(sub => sub.status === 'active');
Copy link
Contributor Author

@cmraible cmraible May 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic is in two places, which isn't ideal. I'm surprised we don't have an established pattern for "get member's active tier(s)" — surely we'd need this in a lot of different places?

* @param {GetFreeTier} [getFreeTier] - Async function that takes no arguments and resolves to the free tier object.
* @returns {function} Middleware function.
*/
const getMiddleware = async (getFreeTier = async () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need a way to get the ID of the free tier in the middleware, but for the sake of unit testing I've tried to do a bit of DI here. It works, and it makes testing easier, but I wonder if there is a better way to do this that I've overlooked. Feels a bit overly complicated for what it's doing.

Comment on lines +50 to +54
if (shouldCacheMembersContent) {
membersApp.get('/api/member', middleware.loadMemberSession, middleware.accessInfoSession, middleware.getMemberData);
} else {
membersApp.get('/api/member', middleware.getMemberData);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🫣 Would like to refactor this if I have time — feels like this complexity should be in the middleware rather than in the app.

@cmraible cmraible force-pushed the chris-ktlo-45-deploy-caching-solution-to-a-single-site-to-validate-and branch from 51226f7 to f835c2d Compare May 23, 2024 20:04
@cmraible cmraible changed the title Added a minimal WIP version of caching members content Added experimental headers to allow caching members content May 23, 2024
ref https://linear.app/tryghost/issue/KTLO-45/deploy-members-caching-solution-to-a-single-site-to-validate-and-test

Currently we only cache publicly available content. Any content that is accessed by a logged in member is only cached for that specific member based on their cookie. As a result, almost all requests from logged in members bypass our caching layer and reach Ghost, which adds unnecessary load to Ghost and its database.

This change adds experimental headers that allow our CDN to understand which tier to cache the content against, and securely tell the CDN which tier a logged in member has access to. With these changes, we can cache the member content against the tier, rather than the individual member, which should result in a higher cache HIT ratio and reduce the load on Ghost.

For requests to the frontend of the site, Ghost will set a custom `X-Member-Cache-Tier` header to the ID of the tier of the member who is accessing the content. This tells the CDN which tier to cache the content against.

For requests to either `/members/?token=...` endpoint (the magic link endpoint) or `/members/api/member`, Ghost will set a `ghost-access` and `ghost-access-hmac` cookie with the ID of the tier of the logged in member. With these two pieces of information, our CDN can serve cached content to logged in members.

These headers are experimental, and can only be enabled via Ghost's config. To enable these headers, set `cacheMembersContent:enabled` to true and provide an HMAC key in `cacheMembersContent:hmacSecret`.
@cmraible cmraible force-pushed the chris-ktlo-45-deploy-caching-solution-to-a-single-site-to-validate-and branch from eccec51 to cc638b5 Compare May 24, 2024 01:31
@cmraible cmraible marked this pull request as ready for review May 24, 2024 02:06
@cmraible cmraible merged commit 98d49f5 into TryGhost:main May 24, 2024
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

1 participant