From b02cbb2946e9c6a65534df47c20894153b60ac14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carl-Gerhard=20Lindesva=CC=88rd?= Date: Thu, 8 Aug 2024 22:02:13 +0200 Subject: [PATCH] add better cache for getProfileId --- apps/worker/src/jobs/events.incoming-event.ts | 7 +++- packages/db/src/services/profile.service.ts | 5 ++- packages/redis/cachable.ts | 38 ++++++++++++++++++- 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/apps/worker/src/jobs/events.incoming-event.ts b/apps/worker/src/jobs/events.incoming-event.ts index 898d25e6..80e15641 100644 --- a/apps/worker/src/jobs/events.incoming-event.ts +++ b/apps/worker/src/jobs/events.incoming-event.ts @@ -11,7 +11,7 @@ import { toISOString, } from '@openpanel/common'; import type { IServiceCreateEventPayload, IServiceEvent } from '@openpanel/db'; -import { createEvent } from '@openpanel/db'; +import { createEvent, getProfileIdCached } from '@openpanel/db'; import { getLastScreenViewFromProfileId } from '@openpanel/db/src/services/event.service'; import { eventsQueue, findJobByPrefix, sessionsQueue } from '@openpanel/queue'; import type { @@ -47,7 +47,10 @@ export async function incomingEvent(job: Job) { }; // this will get the profileId from the alias table if it exists - const profileId = body.profileId ? String(body.profileId) : ''; + const profileId = await getProfileIdCached({ + profileId: body.profileId, + projectId, + }); const createdAt = new Date(body.timestamp); const url = getProperty('__path'); const { path, hash, query, origin } = parsePath(url); diff --git a/packages/db/src/services/profile.service.ts b/packages/db/src/services/profile.service.ts index 6a12e05d..9cbfd5f8 100644 --- a/packages/db/src/services/profile.service.ts +++ b/packages/db/src/services/profile.service.ts @@ -180,6 +180,7 @@ export async function createProfileAlias({ alias: string; profileId: string; }) { + await getProfileIdCached.clear({ profileId, projectId }); await ch.insert({ table: TABLE_NAMES.alias, format: 'JSONEachRow', @@ -221,7 +222,7 @@ export async function getProfileId({ profileId, projectId, }: { - profileId: string | undefined; + profileId: number | string | undefined; projectId: string; }) { if (!profileId) { @@ -240,7 +241,7 @@ export async function getProfileId({ return res[0].profile_id; } - return profileId; + return String(profileId); } export const getProfileIdCached = cacheable(getProfileId, 60 * 30); diff --git a/packages/redis/cachable.ts b/packages/redis/cachable.ts index f2c03429..c171aec9 100644 --- a/packages/redis/cachable.ts +++ b/packages/redis/cachable.ts @@ -4,11 +4,36 @@ export function cacheable any>( fn: T, expireInSec: number ) { - return async function ( + const cachePrefix = `cachable:${fn.name}`; + function stringify(obj: unknown): string { + if (obj === null) return 'null'; + if (obj === undefined) return 'undefined'; + if (typeof obj === 'boolean') return obj ? 'true' : 'false'; + if (typeof obj === 'number') return String(obj); + if (typeof obj === 'string') return obj; + if (typeof obj === 'function') return obj.toString(); + + if (Array.isArray(obj)) { + return '[' + obj.map(stringify).join(',') + ']'; + } + + if (typeof obj === 'object') { + const pairs = Object.entries(obj) + .sort(([a], [b]) => a.localeCompare(b)) + .map(([key, value]) => `${key}:${stringify(value)}`); + return pairs.join(':'); + } + + // Fallback for any other types + return String(obj); + } + const getKey = (...args: Parameters) => + `${cachePrefix}:${stringify(args)}`; + const cachedFn = async function ( ...args: Parameters ): Promise>> { // JSON.stringify here is not bullet proof since ordering of object keys matters etc - const key = `cachable:${fn.name}:${JSON.stringify(args)}`; + const key = getKey(...args); const cached = await getRedisCache().get(key); if (cached) { try { @@ -25,4 +50,13 @@ export function cacheable any>( return result; }; + + cachedFn.getKey = getKey; + cachedFn.clear = async function (...args: Parameters) { + const key = getKey(...args); + console.log('[cachable] Clear', key); + return getRedisCache().del(key); + }; + + return cachedFn; }