Skip to content

Commit 90b3668

Browse files
committed
perf: chunk cache
1 parent 970fec1 commit 90b3668

File tree

3 files changed

+91
-61
lines changed

3 files changed

+91
-61
lines changed

apps/insights/next.config.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
const config = {
22
experimental: {
33
useCache: true,
4-
cacheComponents: true,
54
reactCompiler: true,
65
},
7-
8-
cacheMaxMemorySize: 200 * 1024 * 1024, // 200MB
96
reactStrictMode: true,
107

118
pageExtensions: ["ts", "tsx", "mdx"],

apps/insights/src/app/actions.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
'use server';
2-
import { unstable_cache } from 'next/cache';
3-
import { unstable_cacheTag as cacheTag } from 'next/cache'
2+
3+
4+
5+
import { unstable_cacheTag as cacheTag, unstable_cache } from 'next/cache';
46

57

68
export const funcA = async () => {

apps/insights/src/server/pyth.ts

Lines changed: 87 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,112 @@
1+
import { unstable_cache } from "next/cache";
2+
import { cache } from 'react';
13
import superjson from "superjson";
2-
import { z } from 'zod';
4+
import { z } from "zod";
35

6+
// Your imports
47
import { Cluster, clients, priceFeedsSchema } from "../services/pyth";
58

6-
export const getPublishersForFeed = async (
7-
cluster: Cluster,
8-
) => {
9-
"use cache";
10-
const start = performance.now();
11-
const data = await clients[cluster].getData();
9+
const getDataCached = cache(async (cluster: Cluster) => {
10+
return clients[cluster].getData();
11+
});
12+
const MAX_CACHE_SIZE_STRING = 2 * 1024 * 1024;
13+
14+
const getPublishersForFeed = unstable_cache(async (cluster: Cluster, chunk?: number) => {
15+
const data = await getDataCached(cluster);
1216
const result: Record<string, string[]> = {};
1317
for (const key of data.productPrice.keys()) {
1418
const price = data.productPrice.get(key);
1519
result[key] = price?.priceComponents.map(({ publisher }) => publisher.toBase58()) ?? [];
1620
}
17-
const end = performance.now();
18-
// eslint-disable-next-line no-console, @typescript-eslint/restrict-template-expressions
19-
console.log(`getPublishersForFeed: ${end - start}ms`);
20-
return result;
21-
};
21+
const stringifiedResult = superjson.stringify(result);
2222

23-
const getFeeds = async (cluster: Cluster) => {
24-
"use cache";
25-
const start = performance.now();
26-
const data = await clients[cluster].getData();
27-
28-
const result = superjson.stringify(priceFeedsSchema.parse(data.symbols.filter(
29-
(symbol) =>
30-
data.productFromSymbol.get(symbol)?.display_symbol !== undefined,
31-
).map((symbol) => ({
32-
symbol,
33-
product: data.productFromSymbol.get(symbol),
34-
price: {
35-
...data.productPrice.get(symbol),
36-
priceComponents: data.productPrice.get(symbol)?.priceComponents.map(({ publisher }) => ({
37-
publisher: publisher.toBase58(),
38-
})) ?? [],
39-
},
40-
}))))
41-
const end = performance.now();
42-
// eslint-disable-next-line no-console, @typescript-eslint/restrict-template-expressions
43-
console.log(`getFeeds: ${end - start}ms`);
44-
return result;
45-
}
23+
const chunksNumber = Math.ceil(stringifiedResult.length / MAX_CACHE_SIZE_STRING);
24+
const chunks = [];
25+
for(let i = 0; i < chunksNumber; i++) {
26+
chunks.push(stringifiedResult.slice(i * MAX_CACHE_SIZE_STRING, (i + 1) * MAX_CACHE_SIZE_STRING));
27+
}
28+
return {
29+
chunk: chunks[chunk ?? 0],
30+
chunksNumber,
31+
};
32+
}, [], { revalidate: false });
4633

47-
export const getFeedsForPublisherCached = async (
48-
cluster: Cluster,
49-
publisher: string,
50-
) => {
51-
const start = performance.now();
52-
const rawFeeds = await getFeeds(cluster);
53-
const feeds = superjson.parse<z.infer<typeof priceFeedsSchema>>(rawFeeds);
54-
const end = performance.now();
55-
// eslint-disable-next-line no-console, @typescript-eslint/restrict-template-expressions
56-
console.log(`getFeedsForPublisherCached: ${end - start}ms`);
57-
return priceFeedsSchema.parse(feeds.filter(({ price }) =>
58-
price.priceComponents.some(
59-
(component) => component.publisher.toString() === publisher,
60-
),
61-
));
62-
};
34+
const _getFeeds = unstable_cache(async (cluster: Cluster, chunk?: number) => {
35+
// eslint-disable-next-line no-console
36+
console.log('getFeeds', cluster, chunk);
37+
const data = await getDataCached(cluster);
38+
const parsedData = priceFeedsSchema.parse(
39+
data.symbols
40+
.filter(
41+
(symbol) =>
42+
data.productFromSymbol.get(symbol)?.display_symbol !== undefined
43+
)
44+
.map((symbol) => ({
45+
symbol,
46+
product: data.productFromSymbol.get(symbol),
47+
price: {
48+
...data.productPrice.get(symbol),
49+
priceComponents:
50+
data.productPrice
51+
.get(symbol)
52+
?.priceComponents.map(({ publisher }) => ({
53+
publisher: publisher.toBase58(),
54+
})) ?? [],
55+
},
56+
}))
57+
)
58+
const result = superjson.stringify(
59+
parsedData
60+
);
61+
const chunksNumber = Math.ceil(result.length / MAX_CACHE_SIZE_STRING);
62+
const chunks = [];
63+
for(let i = 0; i < chunksNumber; i++) {
64+
chunks.push(result.slice(i * MAX_CACHE_SIZE_STRING, (i + 1) * MAX_CACHE_SIZE_STRING));
65+
}
66+
return {
67+
chunk: chunks[chunk ?? 0],
68+
chunksNumber,
69+
};
70+
}, [], { revalidate: false });
6371

6472
export const getFeedsCached = async (cluster: Cluster) => {
65-
"use cache";
6673
const start = performance.now();
67-
const rawFeeds = await getFeeds(cluster);
74+
const { chunk, chunksNumber } = await _getFeeds(cluster); // uses cache
75+
const rawResults = await Promise.all(Array.from({ length: chunksNumber-1 }, (_, i) => _getFeeds(cluster, i+1)));
76+
const rawJson = [chunk, ...rawResults.map(({ chunk }) => chunk)].join('');
77+
const data = superjson.parse<z.infer<typeof priceFeedsSchema>>(rawJson);
6878
const end = performance.now();
6979
// eslint-disable-next-line no-console, @typescript-eslint/restrict-template-expressions
7080
console.log(`getFeedsCached: ${end - start}ms`);
71-
return superjson.parse<z.infer<typeof priceFeedsSchema>>(rawFeeds);
81+
return data;
7282
};
7383

7484
export const getPublishersForFeedCached = async (cluster: Cluster, symbol: string) => {
7585
const start = performance.now();
76-
const data = await getPublishersForFeed(cluster);
86+
const { chunk, chunksNumber } = await getPublishersForFeed(cluster); // uses cache
87+
const rawResults = await Promise.all(Array.from({ length: chunksNumber-1 }, (_, i) => getPublishersForFeed(cluster, i+1)));
88+
const rawJson = [chunk, ...rawResults.map(({ chunk }) => chunk)].join('');
89+
const data = superjson.parse<Record<string, string[]>>(rawJson);
7790
const end = performance.now();
7891
// eslint-disable-next-line no-console, @typescript-eslint/restrict-template-expressions
7992
console.log(`getPublishersForFeedCached: ${end - start}ms`);
8093
return data[symbol];
81-
};
94+
};
95+
96+
export const getFeedsForPublisherCached = async (
97+
cluster: Cluster,
98+
publisher: string
99+
) => {
100+
const start = performance.now();
101+
const data = await getFeedsCached(cluster); // uses cache
102+
const end = performance.now();
103+
// eslint-disable-next-line no-console, @typescript-eslint/restrict-template-expressions
104+
console.log(`getFeedsForPublisherCached: ${end - start}ms.`);
105+
return priceFeedsSchema.parse(
106+
data.filter(({ price }) =>
107+
price.priceComponents.some(
108+
(component) => component.publisher.toString() === publisher
109+
)
110+
)
111+
);
112+
};

0 commit comments

Comments
 (0)