Skip to content

Commit

Permalink
feat(store-indexer): add cache headers (#2669)
Browse files Browse the repository at this point in the history
Co-authored-by: Kevin Ingersoll <[email protected]>
  • Loading branch information
alvrs and holic authored Apr 16, 2024
1 parent 8c3dcf7 commit 3635499
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .changeset/fifty-dryers-enjoy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@latticexyz/store-indexer": patch
---

Added `Cache-Control` and `Content-Type` headers to the postgres indexer API.
25 changes: 24 additions & 1 deletion packages/store-indexer/src/postgres/apiRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export function apiRoutes(database: Sql): Middleware {
options = input.parse(typeof ctx.query.input === "string" ? JSON.parse(ctx.query.input) : {});
} catch (e) {
ctx.status = 400;
ctx.set("Content-Type", "application/json");
ctx.body = JSON.stringify(e);
debug(e);
return;
Expand All @@ -33,6 +34,11 @@ export function apiRoutes(database: Sql): Middleware {
const logs = records.map(recordToLog);
benchmark("map records to logs");

// Ideally we would immediately return an error if the request is for a Store that the indexer
// is not configured to index. Since we don't have easy access to this information here,
// we return an error if there are no logs found for a given Store, since that would never
// be the case for a Store that is being indexed (since there would at least be records for the
// Tables table with tables created during Store initialization).
if (records.length === 0) {
ctx.status = 404;
ctx.body = "no logs found";
Expand All @@ -45,10 +51,27 @@ export function apiRoutes(database: Sql): Middleware {
}

const blockNumber = records[0].chainBlockNumber;
ctx.body = JSON.stringify({ blockNumber, logs });
ctx.status = 200;

// max age is set to several multiples of the uncached response time (currently ~10s, but using 60s for wiggle room) to ensure only ~one origin request at a time
// and stale-while-revalidate below means that the cache is refreshed under the hood while still responding fast (cached)
const maxAgeSeconds = 60 * 5;
// we set stale-while-revalidate to the time elapsed by the number of blocks we can fetch from the RPC in the same amount of time as an uncached response
// meaning it would take ~the same about of time to get an uncached response from the origin as it would to catch up from the currently cached response
// if an uncached response takes ~10 seconds, we have ~10s to catch up, so let's say we can do enough RPC calls to fetch 4000 blocks
// with a block per 2 seconds, that means we can serve a stale/cached response for 8000 seconds before we should require the response be returned by the origin
const staleWhileRevalidateSeconds = 4000 * 2;

ctx.set(
"Cache-Control",
`public, max-age=${maxAgeSeconds}, stale-while-revalidate=${staleWhileRevalidateSeconds}`,
);

ctx.set("Content-Type", "application/json");
ctx.body = JSON.stringify({ blockNumber, logs });
} catch (e) {
ctx.status = 500;
ctx.set("Content-Type", "application/json");
ctx.body = JSON.stringify(e);
error(e);
}
Expand Down

0 comments on commit 3635499

Please sign in to comment.