fix: api /super-admin/{id} memory cache
This commit is contained in:
parent
7c70229579
commit
1a324af483
5 changed files with 112 additions and 2 deletions
|
|
@ -11,7 +11,8 @@ import { AppDataSource } from "./database/data-source";
|
||||||
import { RegisterRoutes } from "./routes";
|
import { RegisterRoutes } from "./routes";
|
||||||
import { OrganizationController } from "./controllers/OrganizationController";
|
import { OrganizationController } from "./controllers/OrganizationController";
|
||||||
import logMiddleware from "./middlewares/logs";
|
import logMiddleware from "./middlewares/logs";
|
||||||
import { logMemoryStore } from "./utils/log-memory-store";
|
import { logMemoryStore } from "./utils/LogMemoryStore";
|
||||||
|
import { orgStructureCache } from "./utils/OrgStructureCache";
|
||||||
import { CommandController } from "./controllers/CommandController";
|
import { CommandController } from "./controllers/CommandController";
|
||||||
import { ProfileSalaryController } from "./controllers/ProfileSalaryController";
|
import { ProfileSalaryController } from "./controllers/ProfileSalaryController";
|
||||||
import { DateSerializer } from "./interfaces/date-serializer";
|
import { DateSerializer } from "./interfaces/date-serializer";
|
||||||
|
|
@ -24,6 +25,9 @@ async function main() {
|
||||||
// Initialize LogMemoryStore after database is ready
|
// Initialize LogMemoryStore after database is ready
|
||||||
logMemoryStore.initialize();
|
logMemoryStore.initialize();
|
||||||
|
|
||||||
|
// Initialize OrgStructureCache after database is ready
|
||||||
|
orgStructureCache.initialize();
|
||||||
|
|
||||||
// Setup custom Date serialization for local timezone
|
// Setup custom Date serialization for local timezone
|
||||||
DateSerializer.setupDateSerialization();
|
DateSerializer.setupDateSerialization();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ import {
|
||||||
CreatePosMasterHistoryEmployee,
|
CreatePosMasterHistoryEmployee,
|
||||||
CreatePosMasterHistoryOfficer,
|
CreatePosMasterHistoryOfficer,
|
||||||
} from "../services/PositionService";
|
} from "../services/PositionService";
|
||||||
|
import { orgStructureCache } from "../utils/OrgStructureCache";
|
||||||
|
|
||||||
@Route("api/v1/org")
|
@Route("api/v1/org")
|
||||||
@Tags("Organization")
|
@Tags("Organization")
|
||||||
|
|
@ -1196,6 +1197,12 @@ export class OrganizationController extends Controller {
|
||||||
rootId = posMaster.orgRootId;
|
rootId = posMaster.orgRootId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OPTIMIZED: Check cache first
|
||||||
|
const cachedResponse = await orgStructureCache.get(id, rootId);
|
||||||
|
if (cachedResponse) {
|
||||||
|
return new HttpSuccess(cachedResponse);
|
||||||
|
}
|
||||||
|
|
||||||
// OPTIMIZED: Get all position counts in ONE query (closed)
|
// OPTIMIZED: Get all position counts in ONE query (closed)
|
||||||
// const { orgRootMap, orgChild1Map, orgChild2Map, orgChild3Map, orgChild4Map, rootPosMap } =
|
// const { orgRootMap, orgChild1Map, orgChild2Map, orgChild3Map, orgChild4Map, rootPosMap } =
|
||||||
// await getPositionCounts(id);
|
// await getPositionCounts(id);
|
||||||
|
|
@ -1527,6 +1534,9 @@ export class OrganizationController extends Controller {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// OPTIMIZED: Cache the result
|
||||||
|
await orgStructureCache.set(id, rootId, formattedData);
|
||||||
|
|
||||||
return new HttpSuccess(formattedData);
|
return new HttpSuccess(formattedData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { NextFunction, Request, Response } from "express";
|
import { NextFunction, Request, Response } from "express";
|
||||||
import { Client } from "@elastic/elasticsearch";
|
import { Client } from "@elastic/elasticsearch";
|
||||||
import { logMemoryStore } from "../utils/log-memory-store";
|
import { logMemoryStore } from "../utils/LogMemoryStore";
|
||||||
|
|
||||||
if (!process.env.ELASTICSEARCH_INDEX) {
|
if (!process.env.ELASTICSEARCH_INDEX) {
|
||||||
throw new Error("Require ELASTICSEARCH_INDEX to store log.");
|
throw new Error("Require ELASTICSEARCH_INDEX to store log.");
|
||||||
|
|
|
||||||
96
src/utils/OrgStructureCache.ts
Normal file
96
src/utils/OrgStructureCache.ts
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
interface CacheEntry {
|
||||||
|
data: any;
|
||||||
|
cachedAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrgStructureCache {
|
||||||
|
private cache: Map<string, CacheEntry> = new Map();
|
||||||
|
private readonly CACHE_TTL = 10 * 60 * 1000; // 10 minutes
|
||||||
|
private isInitialized = false;
|
||||||
|
private cleanupTimer: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
// Don't auto-initialize - wait for AppDataSource to be ready
|
||||||
|
}
|
||||||
|
|
||||||
|
initialize() {
|
||||||
|
if (this.isInitialized) return;
|
||||||
|
|
||||||
|
this.isInitialized = true;
|
||||||
|
// Cleanup expired entries every 10 minutes
|
||||||
|
this.cleanupTimer = setInterval(() => {
|
||||||
|
this.cleanup();
|
||||||
|
}, this.CACHE_TTL);
|
||||||
|
|
||||||
|
console.log("[OrgStructureCache] Initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
private generateKey(revisionId: string, rootId?: string): string {
|
||||||
|
return `org-structure-${revisionId}-${rootId || "all"}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private cleanup() {
|
||||||
|
const now = Date.now();
|
||||||
|
let cleaned = 0;
|
||||||
|
|
||||||
|
for (const [key, entry] of this.cache.entries()) {
|
||||||
|
const age = now - entry.cachedAt.getTime();
|
||||||
|
if (age > this.CACHE_TTL) {
|
||||||
|
this.cache.delete(key);
|
||||||
|
cleaned++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cleaned > 0) {
|
||||||
|
console.log(`[OrgStructureCache] Cleaned ${cleaned} expired entries`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async get(revisionId: string, rootId?: string): Promise<any | null> {
|
||||||
|
const key = this.generateKey(revisionId, rootId);
|
||||||
|
const entry = this.cache.get(key);
|
||||||
|
|
||||||
|
if (!entry) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if expired
|
||||||
|
const age = Date.now() - entry.cachedAt.getTime();
|
||||||
|
if (age > this.CACHE_TTL) {
|
||||||
|
this.cache.delete(key);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[OrgStructureCache] HIT: ${key}`);
|
||||||
|
return entry.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async set(revisionId: string, rootId: string | undefined, data: any): Promise<void> {
|
||||||
|
const key = this.generateKey(revisionId, rootId);
|
||||||
|
this.cache.set(key, {
|
||||||
|
data,
|
||||||
|
cachedAt: new Date(),
|
||||||
|
});
|
||||||
|
console.log(`[OrgStructureCache] SET: ${key}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidate(revisionId: string): void {
|
||||||
|
// Invalidate all entries for this revision
|
||||||
|
for (const key of this.cache.keys()) {
|
||||||
|
if (key.startsWith(`org-structure-${revisionId}`)) {
|
||||||
|
this.cache.delete(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(`[OrgStructureCache] INVALIDATED: ${revisionId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
if (this.cleanupTimer) {
|
||||||
|
clearInterval(this.cleanupTimer);
|
||||||
|
this.cleanupTimer = null;
|
||||||
|
}
|
||||||
|
this.cache.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const orgStructureCache = new OrgStructureCache();
|
||||||
Loading…
Add table
Add a link
Reference in a new issue