Browse Source

perf: chunk cache

Alexandru Cambose 3 tháng trước cách đây
mục cha
commit
90b3668a2c

+ 0 - 3
apps/insights/next.config.js

@@ -1,11 +1,8 @@
 const config = {
   experimental: {
     useCache: true,
-    cacheComponents: true,
     reactCompiler: true,
   },
-
-  cacheMaxMemorySize: 200 * 1024 * 1024, // 200MB
   reactStrictMode: true,
 
   pageExtensions: ["ts", "tsx", "mdx"],

+ 4 - 2
apps/insights/src/app/actions.ts

@@ -1,6 +1,8 @@
 'use server';
-import { unstable_cache } from 'next/cache';
-import { unstable_cacheTag as cacheTag } from 'next/cache'
+
+
+
+import { unstable_cacheTag as cacheTag, unstable_cache } from 'next/cache';
 
 
 export const funcA = async () => {

+ 87 - 56
apps/insights/src/server/pyth.ts

@@ -1,81 +1,112 @@
+import { unstable_cache } from "next/cache";
+import { cache } from 'react';
 import superjson from "superjson";
-import { z } from 'zod';
+import { z } from "zod";
 
+// Your imports
 import { Cluster, clients, priceFeedsSchema } from "../services/pyth";
 
-export const getPublishersForFeed = async (
-  cluster: Cluster,
-) => {
-  "use cache";
-  const start = performance.now();
-  const data = await clients[cluster].getData();
+const getDataCached = cache(async (cluster: Cluster) => {
+  return clients[cluster].getData();
+});
+const MAX_CACHE_SIZE_STRING = 2 * 1024 * 1024;
+
+const getPublishersForFeed = unstable_cache(async (cluster: Cluster, chunk?: number) => {
+  const data = await getDataCached(cluster);
   const result: Record<string, string[]> = {};
   for (const key of data.productPrice.keys()) {
     const price = data.productPrice.get(key);
     result[key] = price?.priceComponents.map(({ publisher }) => publisher.toBase58()) ?? [];
   }
-  const end = performance.now();
-  // eslint-disable-next-line no-console, @typescript-eslint/restrict-template-expressions
-  console.log(`getPublishersForFeed: ${end - start}ms`);
-  return result;
-};
+  const stringifiedResult = superjson.stringify(result);
 
-const getFeeds = async (cluster: Cluster) => {
-  "use cache";
-  const start = performance.now();
-  const data = await clients[cluster].getData();
-  
-  const result = superjson.stringify(priceFeedsSchema.parse(data.symbols.filter(
-        (symbol) =>
-          data.productFromSymbol.get(symbol)?.display_symbol !== undefined,
-      ).map((symbol) => ({
-        symbol,
-        product: data.productFromSymbol.get(symbol),
-        price: {
-          ...data.productPrice.get(symbol),
-          priceComponents: data.productPrice.get(symbol)?.priceComponents.map(({ publisher }) => ({
-            publisher: publisher.toBase58(),
-          })) ?? [],
-        },
-      }))))
-  const end = performance.now();
-  // eslint-disable-next-line no-console, @typescript-eslint/restrict-template-expressions
-  console.log(`getFeeds: ${end - start}ms`);
-  return result;
-}
+    const chunksNumber = Math.ceil(stringifiedResult.length / MAX_CACHE_SIZE_STRING);
+    const chunks = [];
+    for(let i = 0; i < chunksNumber; i++) {
+      chunks.push(stringifiedResult.slice(i * MAX_CACHE_SIZE_STRING, (i + 1) * MAX_CACHE_SIZE_STRING));
+    }
+  return {
+    chunk: chunks[chunk ?? 0],
+    chunksNumber,
+  };
+}, [], { revalidate: false });
 
-export const getFeedsForPublisherCached = async (
-  cluster: Cluster,
-  publisher: string,
-) => {
-  const start = performance.now();
-  const rawFeeds = await getFeeds(cluster);
-  const feeds = superjson.parse<z.infer<typeof priceFeedsSchema>>(rawFeeds);
-  const end = performance.now();
-  // eslint-disable-next-line no-console, @typescript-eslint/restrict-template-expressions
-  console.log(`getFeedsForPublisherCached: ${end - start}ms`);
-  return priceFeedsSchema.parse(feeds.filter(({ price }) =>
-    price.priceComponents.some(
-      (component) => component.publisher.toString() === publisher,
-    ),
-  ));
-};
+const _getFeeds = unstable_cache(async (cluster: Cluster, chunk?: number) => {
+  // eslint-disable-next-line no-console
+  console.log('getFeeds', cluster, chunk);
+  const data = await getDataCached(cluster);
+  const parsedData = priceFeedsSchema.parse(
+      data.symbols
+        .filter(
+          (symbol) =>
+            data.productFromSymbol.get(symbol)?.display_symbol !== undefined
+        )
+        .map((symbol) => ({
+          symbol,
+          product: data.productFromSymbol.get(symbol),
+          price: {
+            ...data.productPrice.get(symbol),
+            priceComponents:
+              data.productPrice
+                .get(symbol)
+                ?.priceComponents.map(({ publisher }) => ({
+                  publisher: publisher.toBase58(),
+                })) ?? [],
+          },
+        }))
+    )
+  const result = superjson.stringify(
+    parsedData
+  );
+  const chunksNumber = Math.ceil(result.length / MAX_CACHE_SIZE_STRING);
+  const chunks = [];
+  for(let i = 0; i < chunksNumber; i++) {
+    chunks.push(result.slice(i * MAX_CACHE_SIZE_STRING, (i + 1) * MAX_CACHE_SIZE_STRING));
+  }
+  return {
+    chunk: chunks[chunk ?? 0],
+    chunksNumber,
+  };
+}, [], { revalidate: false });
 
 export const getFeedsCached = async (cluster: Cluster) => {
-  "use cache";
   const start = performance.now();
-  const rawFeeds = await getFeeds(cluster);
+  const { chunk, chunksNumber } = await _getFeeds(cluster); // uses cache
+  const rawResults = await Promise.all(Array.from({ length: chunksNumber-1 }, (_, i) => _getFeeds(cluster, i+1)));
+  const rawJson = [chunk, ...rawResults.map(({ chunk }) => chunk)].join('');
+  const data = superjson.parse<z.infer<typeof priceFeedsSchema>>(rawJson);
   const end = performance.now();
   // eslint-disable-next-line no-console, @typescript-eslint/restrict-template-expressions
   console.log(`getFeedsCached: ${end - start}ms`);
-  return superjson.parse<z.infer<typeof priceFeedsSchema>>(rawFeeds);
+  return data;
 };
 
 export const getPublishersForFeedCached = async (cluster: Cluster, symbol: string) => {
   const start = performance.now();
-  const data = await getPublishersForFeed(cluster);
+  const { chunk, chunksNumber } = await getPublishersForFeed(cluster); // uses cache
+  const rawResults = await Promise.all(Array.from({ length: chunksNumber-1 }, (_, i) => getPublishersForFeed(cluster, i+1)));
+  const rawJson = [chunk, ...rawResults.map(({ chunk }) => chunk)].join('');
+  const data = superjson.parse<Record<string, string[]>>(rawJson);
   const end = performance.now();
   // eslint-disable-next-line no-console, @typescript-eslint/restrict-template-expressions
   console.log(`getPublishersForFeedCached: ${end - start}ms`);
   return data[symbol];
-};
+};
+
+export const getFeedsForPublisherCached = async (
+  cluster: Cluster,
+  publisher: string
+) => {
+  const start = performance.now();
+  const data = await getFeedsCached(cluster); // uses cache
+  const end = performance.now();
+  // eslint-disable-next-line no-console, @typescript-eslint/restrict-template-expressions
+  console.log(`getFeedsForPublisherCached: ${end - start}ms.`);
+  return priceFeedsSchema.parse(
+    data.filter(({ price }) =>
+      price.priceComponents.some(
+        (component) => component.publisher.toString() === publisher
+      )
+    )
+  );
+};