Implemented user deserialize cache #163
@@ -14,6 +14,7 @@ import { toDateTime } from '@app/shared/utils/time';
|
|||||||
import { logger } from '../services/logging/logger';
|
import { logger } from '../services/logging/logger';
|
||||||
const querystring = require('querystring');
|
const querystring = require('querystring');
|
||||||
import { performance } from 'perf_hooks';
|
import { performance } from 'perf_hooks';
|
||||||
|
import { CacheService } from '../services/cache/cache';
|
||||||
|
|
||||||
|
|
||||||
function parseJwt(token) {
|
function parseJwt(token) {
|
||||||
@@ -189,8 +190,31 @@ passport.deserializeUser(function (user, cb) {
|
|||||||
let con;
|
let con;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let t;
|
//cache lookup
|
||||||
|
let t = performance.now();
|
||||||
|
const cachedData: UserData | undefined = userCache.Get(memberID);
|
||||||
|
timings.cache_lookup = performance.now() - t;
|
||||||
|
|
||||||
|
if (cachedData) {
|
||||||
|
timings.total = performance.now() - start;
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
'profiling',
|
||||||
|
'passport.deserializeUser (cache hit)',
|
||||||
|
{
|
||||||
|
memberId: memberID,
|
||||||
|
cache_hit: true,
|
||||||
|
source: 'cache',
|
||||||
|
total_ms: timings.total,
|
||||||
|
breakdown_ms: timings,
|
||||||
|
},
|
||||||
|
'profiling'
|
||||||
|
);
|
||||||
|
|
||||||
|
return cb(null, cachedData);
|
||||||
|
}
|
||||||
|
|
||||||
|
//cache miss, db load
|
||||||
t = performance.now();
|
t = performance.now();
|
||||||
con = await pool.getConnection();
|
con = await pool.getConnection();
|
||||||
timings.getConnection = performance.now() - t;
|
timings.getConnection = performance.now() - t;
|
||||||
@@ -202,30 +226,30 @@ passport.deserializeUser(function (user, cb) {
|
|||||||
);
|
);
|
||||||
timings.memberQuery = performance.now() - t;
|
timings.memberQuery = performance.now() - t;
|
||||||
|
|
||||||
const userData: {
|
const userData: UserData = userResults[0];
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
roles: Role[];
|
|
||||||
state: MemberState;
|
|
||||||
discord_id?: string;
|
|
||||||
} = userResults[0];
|
|
||||||
|
|
||||||
t = performance.now();
|
t = performance.now();
|
||||||
const userRoles = await getUserRoles(memberID);
|
userData.roles = await getUserRoles(memberID) || [];
|
||||||
timings.roles = performance.now() - t;
|
timings.roles = performance.now() - t;
|
||||||
userData.roles = userRoles || [];
|
|
||||||
|
|
||||||
t = performance.now();
|
t = performance.now();
|
||||||
userData.state = await getUserState(memberID);
|
userData.state = await getUserState(memberID);
|
||||||
timings.state = performance.now() - t;
|
timings.state = performance.now() - t;
|
||||||
|
|
||||||
// 📊 PROFILING LOG
|
t = performance.now();
|
||||||
|
userCache.Set(userData.id, userData);
|
||||||
|
timings.cache_set = performance.now() - t;
|
||||||
|
|
||||||
|
timings.total = performance.now() - start;
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
'profiling',
|
'profiling',
|
||||||
'passport.deserializeUser completed',
|
'passport.deserializeUser (db load)',
|
||||||
{
|
{
|
||||||
memberId: memberID,
|
memberId: memberID,
|
||||||
total_ms: performance.now() - start,
|
cache_hit: false,
|
||||||
|
source: 'db',
|
||||||
|
total_ms: timings.total,
|
||||||
breakdown_ms: timings,
|
breakdown_ms: timings,
|
||||||
},
|
},
|
||||||
'profiling'
|
'profiling'
|
||||||
@@ -243,14 +267,12 @@ passport.deserializeUser(function (user, cb) {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
return cb(error);
|
return cb(error);
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
if (con) con.release();
|
if (con) con.release();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
namespace Express {
|
namespace Express {
|
||||||
interface Request {
|
interface Request {
|
||||||
@@ -265,5 +287,15 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UserData {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
roles: Role[];
|
||||||
|
state: MemberState;
|
||||||
|
discord_id?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const userCache = new CacheService<number, UserData>();
|
||||||
|
|
||||||
export const authRouter = router;
|
export const authRouter = router;
|
||||||
|
export const memberCache = userCache;
|
||||||
19
api/src/services/cache/cache.ts
vendored
Normal file
19
api/src/services/cache/cache.ts
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
export class CacheService<Key, Value> {
|
||||||
|
private cacheMap: Map<Key, Value>
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.cacheMap = new Map<Key, Value>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Get(key: Key): Value {
|
||||||
|
return this.cacheMap.get(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set(key: Key, value: Value) {
|
||||||
|
this.cacheMap.set(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Invalidate(key: Key): boolean {
|
||||||
|
return this.cacheMap.delete(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import { Role } from "@app/shared/types/roles";
|
import { Role } from "@app/shared/types/roles";
|
||||||
import pool from "../../db";
|
import pool from "../../db";
|
||||||
import { Member, MemberCardDetails, MemberLight, memberSettings, MemberState } from '@app/shared/types/member'
|
import { Member, MemberCardDetails, MemberLight, memberSettings, MemberState } from '@app/shared/types/member'
|
||||||
|
import { logger } from "../logging/logger";
|
||||||
|
import { memberCache } from "../../routes/auth";
|
||||||
|
|
||||||
export async function getUserData(userID: number): Promise<Member> {
|
export async function getUserData(userID: number): Promise<Member> {
|
||||||
const sql = `SELECT * FROM view_member_rank_unit_status_latest WHERE member_id = ?`;
|
const sql = `SELECT * FROM view_member_rank_unit_status_latest WHERE member_id = ?`;
|
||||||
@@ -9,10 +11,16 @@ export async function getUserData(userID: number): Promise<Member> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function setUserState(userID: number, state: MemberState) {
|
export async function setUserState(userID: number, state: MemberState) {
|
||||||
const sql = `UPDATE members
|
try {
|
||||||
|
const sql = `UPDATE members
|
||||||
SET state = ?
|
SET state = ?
|
||||||
WHERE id = ?;`;
|
WHERE id = ?;`;
|
||||||
return await pool.query(sql, [state, userID]);
|
return await pool.query(sql, [state, userID]);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('app', 'Error setting user state', error);
|
||||||
|
} finally {
|
||||||
|
memberCache.Invalidate(userID);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getUserState(user: number): Promise<MemberState> {
|
export async function getUserState(user: number): Promise<MemberState> {
|
||||||
|
|||||||
@@ -1,12 +1,20 @@
|
|||||||
import { MemberLight } from '@app/shared/types/member';
|
import { MemberLight } from '@app/shared/types/member';
|
||||||
import pool from '../../db';
|
import pool from '../../db';
|
||||||
import { Role, RoleSummary } from '@app/shared/types/roles'
|
import { Role, RoleSummary } from '@app/shared/types/roles'
|
||||||
|
import { logger } from '../logging/logger';
|
||||||
|
import { memberCache } from '../../routes/auth';
|
||||||
|
|
||||||
export async function assignUserGroup(userID: number, roleID: number) {
|
export async function assignUserGroup(userID: number, roleID: number) {
|
||||||
const sql = `INSERT INTO members_roles (member_id, role_id) VALUES (?, ?);`;
|
try {
|
||||||
const params = [userID, roleID];
|
const sql = `INSERT INTO members_roles (member_id, role_id) VALUES (?, ?);`;
|
||||||
|
const params = [userID, roleID];
|
||||||
|
|
||||||
return await pool.query(sql, params);
|
return await pool.query(sql, params);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('app', 'Failed to assign user group', error);
|
||||||
|
} finally {
|
||||||
|
memberCache.Invalidate(userID);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createGroup(name: string, color: string, description: string) {
|
export async function createGroup(name: string, color: string, description: string) {
|
||||||
|
|||||||
Reference in New Issue
Block a user