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

feat(store-indexer): add cache headers #2669

Merged
merged 24 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading