yarbsemaj / sveltekit-adapter-lambda

An adapter to build a SvelteKit app into a lambda ready for deployment with lambda proxy via the Serverless Framework or CDK.
https://www.npmjs.com/package/@yarbsemaj/adapter-lambda
MIT License
77 stars 16 forks source link

Creating variable outside handler function in SSR code #14

Closed bxgrant closed 1 year ago

bxgrant commented 2 years ago

First, this library rocks. Super impressive and I'm excited to get going with it.

To adopt, I will need a server-side cache in the lambda across lambda invocations. This is easy in a lambda. Just define a variable outside your handler function. How would I achieve it?

let html; // this is reused between invocations
const loadHtml = async () => {
  if (!html) {
    // load HTML from somewhere
    // this is only run during cold start and then cached
  }
  return html
}
module.exports.handler = async (event) => {
  return {
    statusCode: 200,
    body: await loadHtml(),
    headers: {
      'content-type': 'text/html; charset=UTF-8'
    }
  }
}
yarbsemaj commented 2 years ago

Hi @bxgrant. Thank You for your interest in my project!

Yes, it certainly is possible to implement caching in this way. However, as every SSR request is handled by the same lambda in my implementation (this includes POSTs and PUTs etc), you would have to implement your own way to determine if you wish to cache a request and derive its cache key yourself. This could be done by having your html variable be an object, keyed by some derivative of the request, only taking into account the headers, cookies, and query params you are interested in caching. I would envisage the flow, in this case, to be as follows;

  1. Request comes in, a cache key is derived from it, and that keys existence is then checked for in the global object containing all requests cached by that lambda.
  2. If the key exists, return the cached HTML. If not, render the response.
  3. Before responding to the user with the rendered response, check for a cache header indicating you want the lambda to cache this response, if present store the rendered html in the global object against the key derived in step 1.

If you care about TTL, you would also need a system to handle that manually as well. All these changes would be made in src/serverless.js with the html var existing outside the handler function.

However, this seems overly fiddly and, as you may know, will not persist between concurrent invocations. May I suggest you look at API gateway caching. I'm not using an API gateway for this adapter because for most use cases it's just an unnecessary added cost in the chain now Amazon has added support for Lambda function urls. This approach will give you all the benefits of server-side caching (as API gateway caching is done by a dedicated provisioned node rather than at the edge) but as a managed service with data sharing between concurrent functions. The downside of course is cost. If you're interested, this is the serverless plugin I've used in the past to config this https://www.serverless.com/plugins/serverless-api-gateway-caching. You will also need to rejig your serverless.yml file to use an API gateway, this is fairly simple to do and you can see this old version of the adapter to see how it could be done.

If I were you tho, before I embark on any of these tho, I would try testing with just the edge caching CloudFront provides, and see how you get on, with the right TTL and cache policy (only caching headers, cookies, and query params that are strictly necessary) you can achieve very high cache hit rates without having to resort to any of these methods.

Hope this helps

James