From b363433ab751e2c7cd8a897f0ddb9851f8681a8c Mon Sep 17 00:00:00 2001
From: Milho001 <l.milhomemfrancochristino@uu.nl>
Date: Mon, 3 Feb 2025 11:03:18 +0000
Subject: [PATCH] feat: cache query result for one minute

---
 .env.example                |  3 ++-
 src/readers/queryService.ts | 32 +++++++++++++++++++++++++++++++-
 src/variables.ts            |  2 ++
 3 files changed, 35 insertions(+), 2 deletions(-)

diff --git a/.env.example b/.env.example
index d3b1dc6..b74250a 100644
--- a/.env.example
+++ b/.env.example
@@ -17,4 +17,5 @@ SCHEMA_RETRIEVER=neo4j
 SMTP_HOST=smtp.office365.com
 SMTP_PORT=587
 SMTP_USER=
-SMTP_PASSWORD=
\ No newline at end of file
+SMTP_PASSWORD=
+QUERY_CACHE_DURATION=
\ No newline at end of file
diff --git a/src/readers/queryService.ts b/src/readers/queryService.ts
index d1b8f3a..9da7bad 100644
--- a/src/readers/queryService.ts
+++ b/src/readers/queryService.ts
@@ -1,6 +1,6 @@
 import { graphQueryBackend2graphQuery, type DbConnection, type QueryRequest } from 'ts-common';
 
-import { rabbitMq, redis, ums, type QueryExecutionTypes } from '../variables';
+import { QUERY_CACHE_DURATION, rabbitMq, redis, ums, type QueryExecutionTypes } from '../variables';
 import { log } from '../logger';
 import { QueryPublisher } from '../utils/queryPublisher';
 import { query2Cypher } from '../utils/cypher/converter';
@@ -12,6 +12,27 @@ import { RabbitMqBroker } from 'ts-common/rabbitMq';
 import { Neo4jConnection } from 'ts-common/neo4j';
 
 export const queryService = async (db: DbConnection, query: string): Promise<GraphQueryResultMetaFromBackend> => {
+  let index = 0;
+  const disambiguatedQuery = query.replace(/\d{13}/g, () => (index++).toString());
+  const cacheKey = Bun.hash(JSON.stringify({ db: db, query: disambiguatedQuery })).toString();
+
+  if (QUERY_CACHE_DURATION === '') {
+    log.info('Query cache disabled, skipping cache check');
+  } else {
+    // check for cached results
+    log.debug('Checking cache for query, with cache ttl', QUERY_CACHE_DURATION, 'seconds');
+    const cached = await redis.client.get(cacheKey);
+    if (cached) {
+      log.info('Cache hit for query');
+      log.debug('Cache hit for query', disambiguatedQuery);
+      const buf = Buffer.from(cached, 'base64');
+      const inflated = Bun.gunzipSync(new Uint8Array(buf));
+      const dec = new TextDecoder();
+      const cachedMessage = JSON.parse(dec.decode(inflated)) as GraphQueryResultMetaFromBackend;
+      return cachedMessage;
+    }
+  }
+
   // TODO: only neo4j is supported for now
   const connection = new Neo4jConnection(db);
   try {
@@ -21,6 +42,15 @@ export const queryService = async (db: DbConnection, query: string): Promise<Gra
     // calculate metadata
     const result = graphQueryBackend2graphQuery(graph);
 
+    // cache result
+    const compressedMessage = Bun.gzipSync(JSON.stringify(result));
+    const base64Message = Buffer.from(compressedMessage).toString('base64');
+
+    if (QUERY_CACHE_DURATION !== '') {
+      // if cache enabled, cache the result
+      await redis.setWithExpire(cacheKey, base64Message, QUERY_CACHE_DURATION); // ttl in seconds
+    }
+
     return result;
   } catch (error) {
     log.error('Error parsing query result:', query, error);
diff --git a/src/variables.ts b/src/variables.ts
index 18c889b..e7f3f4a 100644
--- a/src/variables.ts
+++ b/src/variables.ts
@@ -20,6 +20,8 @@ export const REDIS_HOST = Bun.env.REDIS_HOST || 'localhost';
 export const REDIS_PORT = parseInt(Bun.env.REDIS_PORT || '6379');
 export const REDIS_PASSWORD = Bun.env.REDIS_PASSWORD || 'DevOnlyPass';
 export const REDIS_SCHEMA_CACHE_DURATION = Bun.env.REDIS_SCHEMA_CACHE_DURATION || '60m';
+export const QUERY_CACHE_DURATION =
+  Bun.env.QUERY_CACHE_DURATION === '' || !Bun.env.QUERY_CACHE_DURATION ? '' : parseInt(Bun.env.QUERY_CACHE_DURATION);
 
 export const redis = new RedisConnector(REDIS_PASSWORD, REDIS_HOST, REDIS_PORT);
 
-- 
GitLab