Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/src/packages/database/pool/cached.ts
Views: 687
/*1* This file is part of CoCalc: Copyright © 2021 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45/*6Caches queries for a certain amount of time.7Also, if there are multiple queries coming in8at the same time for the same thing, only9one actually goes to the database.1011IMPORTANT: This *only* caches a query if the query actually12returns at least one row. If the query returns nothing,13that fact is not cached. This is usually what we want, e.g.,14if somebody access https://cocalc.com/wstein/myproject, and15notices that myproject isn't the set name, then they may16set it and immediately try https://cocalc.com/wstein/myproject again.17In that case, we don't want to the cache to mean that they don't18see the page for a while. On the other hand, if querying for the19project that myproject resolves to is cached for a minute that is20fine, since this would only be a problem when they change the name21of multiple projects.22*/2324import { reuseInFlight } from "@cocalc/util/reuse-in-flight";25import LRU from "lru-cache";26import { Pool } from "pg";2728import getLogger from "@cocalc/backend/logger";29import getPool from "./pool";3031const L = getLogger("db:pool:cached");3233const MAX_AGE_S = {34"": 0, // no cache at all35short: 5, // just to avoid a very rapid fire sequence of re-requests36medium: 15, // usually use this.37long: 30,38minutes: 10 * 60, // a really long time -- for now, 10 minutes. example, the owner of a project.39infinite: 60 * 60 * 24 * 365, // effectively forever; e.g., getting path from share id is really just a reversed sha1 hash, so can't change.40} as const;4142export type CacheTime = keyof typeof MAX_AGE_S;4344const caches = new Map<CacheTime, LRU<string, any>>();4546for (const cacheTime in MAX_AGE_S) {47if (!cacheTime) continue;48caches[cacheTime] = new LRU<string, any>({49max: 1000,50ttl: 1000 * MAX_AGE_S[cacheTime],51});52}5354const cachedQuery = reuseInFlight(async (cacheTime: CacheTime, ...args) => {55const cache = caches[cacheTime];56if (cache == null) {57throw Error(`invalid cache "${cacheTime}"`);58}59const key = JSON.stringify(args);60if (cache.has(key)) {61// console.log(`YES - using cache for ${key}`);62return cache.get(key);63}64// console.log(`NOT using cache for ${key}`);6566const pool = getPool();67try {68// @ts-ignore - no clue how to typescript this.69const result = await pool.query(...args);70if (result.rows.length > 0) {71// We only cache query if it returned something.72cache.set(key, result);73}74return result;75} catch (err) {76L.error(`cachedQuery error: ${err}`);77throw err;78}79});8081export default function getCachedPool(cacheTime: CacheTime) {82if (!cacheTime) {83return getPool();84}85return {86query: async (...args) => await cachedQuery(cacheTime, ...args),87} as any as Pool; // obviously not really a Pool, but is enough for what we're doing.88}899091