Skip to content

Commit

Permalink
making query cache work with the disk
Browse files Browse the repository at this point in the history
  • Loading branch information
mikecot committed Jan 14, 2024
1 parent cab97bd commit 814d655
Showing 1 changed file with 62 additions and 30 deletions.
92 changes: 62 additions & 30 deletions src/queryCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,66 @@
import { FastifyRequest, FastifyReply } from 'fastify';
import url from 'url';

import fs from 'fs';
import os from 'os';
import path from 'path';
interface CacheEntry {
isFetching: boolean;
data: any;
expiry: number;
}
class QueryCache {
private cacheDir: string;
private memoryCache: Record<string, CacheEntry>;

constructor() {
this.cacheDir = path.join(os.tmpdir(), 'query-cache');
this.memoryCache = {};
if (!fs.existsSync(this.cacheDir)) {
fs.mkdirSync(this.cacheDir, { recursive: true });
}
}

getNewExpiry(): number {
return Date.now() + Math.floor(Math.random() * (25 - 15 + 1) + 15) * 1000;
}

get(key: string): CacheEntry {
// First, try to get the cache entry from the in-memory cache
let cacheEntry = this.memoryCache[key];

// If the cache entry is not in the in-memory cache, create a new cache entry with empty data
if (!cacheEntry) {
this.memoryCache[key] = cacheEntry = {
isFetching: false,
data: {},
expiry: this.getNewExpiry()
};
}

// If the data in the cache entry is empty, try to load it from the disk
if (!cacheEntry.isFetching && Object.keys(cacheEntry.data).length === 0) {
const cacheFilePath = path.join(this.cacheDir, encodeURIComponent(key));
if (fs.existsSync(cacheFilePath)) {
const data: any = JSON.parse(fs.readFileSync(cacheFilePath, 'utf-8'));
console.log(`QueryCache: Loaded data for key "${key}" from disk`);
cacheEntry.data = data;
}
}

return cacheEntry;
}

updateData(key: string, newData: any): void {
const cacheEntry = this.get(key);
cacheEntry.data = newData;
const cacheFilePath = path.join(this.cacheDir, encodeURIComponent(key));
fs.writeFileSync(cacheFilePath, JSON.stringify(cacheEntry.data));
this.get(key).expiry = this.getNewExpiry();
}
}
class RequestCache {
cache: Record<string, CacheEntry> = {};
cache: QueryCache = new QueryCache()

async getOrFetchData(request: FastifyRequest, reply: FastifyReply, handler: (request: FastifyRequest, reply: FastifyReply) => Promise<any>) {
const key = url.parse(request.url).pathname || request.url;
Expand All @@ -20,52 +72,32 @@ class RequestCache {
throw new Error('Key cannot be an empty string');
}

if (!this.cache[key]) {
// refetch data?
if (Object.keys(this.cache.get(key).data).length === 0 || Date.now() > this.cache.get(key).expiry) {
console.log(`QueryCache: No cache entry for ${key}. Fetching data...`);
this.tryFetchData(key, request, reply, handler);

} else if (this.cache[key].isFetching) {
console.log(`QueryCache: Data for ${key} is currently being fetched...`);

} else if (Date.now() > this.cache[key].expiry) {
console.log(`QueryCache: Cache entry for ${key} has expired. Refreshing...`);
this.tryFetchData(key, request, reply, handler);
}

return this.cache[key]?.data || {};
return this.cache.get(key).data;
}

async tryFetchData(key: string, request: FastifyRequest, reply: FastifyReply, handler: (request: FastifyRequest, reply: FastifyReply) => Promise<any>, retryCount: number = 0) {
// generates a random number between 25 and 35
// blocks are 30 seconds . from gil: if the latest block is 30 seconds old, refresh the cache
// adding a 5 second margin to not make all the quries at the same time

if (this.cache[key]) {
// If the key exists, only update the properties
this.cache[key].isFetching = true;
this.cache[key].expiry = Date.now() + Math.floor(Math.random() * (25 - 15 + 1) + 15) * 1000; // Random number between 15 and 25 seconds
} else {
// If the key doesn't exist, create a new entry
this.cache[key] = {
isFetching: true,
data: {},
expiry: Date.now() + Math.floor(Math.random() * (25 - 15 + 1) + 15) * 1000, // Random number between 15 and 25 seconds
};
}
if (this.cache.get(key).isFetching) return;
this.cache.get(key).isFetching = true;

try {
console.time(`QueryCache: handler execution time for ${key}`);
const data = await handler(request, reply);
console.timeEnd(`QueryCache: handler execution time for ${key}`);
this.cache[key].data = data;
this.cache[key].isFetching = false;
this.cache.updateData(key, data);
this.cache.get(key).isFetching = false;
console.log(`QueryCache: Data fetched for ${key}`);
} catch (error) {
console.log(`QueryCache: Error fetching data for ${key} on attempt ${retryCount + 1}`);
this.cache[key].isFetching = false;
this.cache.get(key).isFetching = false;
if (retryCount < 2) { // If it's not the last attempt
setTimeout(() => {
this.tryFetchData(key, request, reply, handler, retryCount + 1);
this.tryFetchData(key, request, reply, handler, retryCount + 1);
}, 1000);
}
}
Expand Down

0 comments on commit 814d655

Please sign in to comment.