조인기능 최적화
This commit is contained in:
@@ -1446,9 +1446,12 @@ export class TableManagementService {
|
||||
};
|
||||
}
|
||||
|
||||
// 조인 전략 결정
|
||||
// 조인 전략 결정 (테이블 크기 기반)
|
||||
const strategy =
|
||||
await entityJoinService.determineJoinStrategy(joinConfigs);
|
||||
console.log(
|
||||
`🎯 선택된 조인 전략: ${strategy} (${joinConfigs.length}개 Entity 조인)`
|
||||
);
|
||||
|
||||
// 테이블 컬럼 정보 조회
|
||||
const columns = await this.getTableColumns(tableName);
|
||||
@@ -1477,7 +1480,7 @@ export class TableManagementService {
|
||||
offset,
|
||||
startTime
|
||||
);
|
||||
} else {
|
||||
} else if (strategy === "cache_lookup") {
|
||||
// 캐시 룩업 방식
|
||||
return await this.executeCachedLookup(
|
||||
tableName,
|
||||
@@ -1485,6 +1488,18 @@ export class TableManagementService {
|
||||
options,
|
||||
startTime
|
||||
);
|
||||
} else {
|
||||
// 하이브리드 방식: 일부는 조인, 일부는 캐시
|
||||
return await this.executeHybridJoin(
|
||||
tableName,
|
||||
joinConfigs,
|
||||
selectColumns,
|
||||
whereClause,
|
||||
orderBy,
|
||||
options.size,
|
||||
offset,
|
||||
startTime
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`Entity 조인 데이터 조회 실패: ${tableName}`, error);
|
||||
@@ -1585,7 +1600,7 @@ export class TableManagementService {
|
||||
try {
|
||||
// 캐시 데이터 미리 로드
|
||||
for (const config of joinConfigs) {
|
||||
await referenceCacheService.preloadReferenceTable(
|
||||
await referenceCacheService.getCachedReference(
|
||||
config.referenceTable,
|
||||
config.referenceColumn,
|
||||
config.displayColumn
|
||||
@@ -1766,4 +1781,200 @@ export class TableManagementService {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 🎯 하이브리드 조인 전략 구현
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* 하이브리드 조인 실행: 일부는 조인, 일부는 캐시 룩업
|
||||
*/
|
||||
private async executeHybridJoin(
|
||||
tableName: string,
|
||||
joinConfigs: EntityJoinConfig[],
|
||||
selectColumns: string[],
|
||||
whereClause: string,
|
||||
orderBy: string,
|
||||
limit: number,
|
||||
offset: number,
|
||||
startTime: number
|
||||
): Promise<EntityJoinResponse> {
|
||||
try {
|
||||
logger.info(`🔀 하이브리드 조인 실행: ${tableName}`);
|
||||
|
||||
// 각 조인 설정을 캐시 가능 여부에 따라 분류
|
||||
const { cacheableJoins, dbJoins } =
|
||||
await this.categorizeJoins(joinConfigs);
|
||||
|
||||
console.log(
|
||||
`📋 캐시 조인: ${cacheableJoins.length}개, DB 조인: ${dbJoins.length}개`
|
||||
);
|
||||
|
||||
// DB 조인이 있는 경우: 조인 쿼리 실행 후 캐시 룩업 적용
|
||||
if (dbJoins.length > 0) {
|
||||
return await this.executeJoinThenCache(
|
||||
tableName,
|
||||
dbJoins,
|
||||
cacheableJoins,
|
||||
selectColumns,
|
||||
whereClause,
|
||||
orderBy,
|
||||
limit,
|
||||
offset,
|
||||
startTime
|
||||
);
|
||||
}
|
||||
// 모든 조인이 캐시 가능한 경우: 기본 쿼리 + 캐시 룩업
|
||||
else {
|
||||
return await this.executeCachedLookup(
|
||||
tableName,
|
||||
cacheableJoins,
|
||||
{ page: Math.floor(offset / limit) + 1, size: limit, search: {} },
|
||||
startTime
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error("하이브리드 조인 실행 실패", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 조인 설정을 캐시 가능 여부에 따라 분류
|
||||
*/
|
||||
private async categorizeJoins(joinConfigs: EntityJoinConfig[]): Promise<{
|
||||
cacheableJoins: EntityJoinConfig[];
|
||||
dbJoins: EntityJoinConfig[];
|
||||
}> {
|
||||
const cacheableJoins: EntityJoinConfig[] = [];
|
||||
const dbJoins: EntityJoinConfig[] = [];
|
||||
|
||||
for (const config of joinConfigs) {
|
||||
// 캐시 가능성 확인
|
||||
const cachedData = await referenceCacheService.getCachedReference(
|
||||
config.referenceTable,
|
||||
config.referenceColumn,
|
||||
config.displayColumn
|
||||
);
|
||||
|
||||
if (cachedData && cachedData.size > 0) {
|
||||
cacheableJoins.push(config);
|
||||
console.log(
|
||||
`📋 캐시 사용: ${config.referenceTable} (${cachedData.size}건)`
|
||||
);
|
||||
} else {
|
||||
dbJoins.push(config);
|
||||
console.log(`🔗 DB 조인: ${config.referenceTable}`);
|
||||
}
|
||||
}
|
||||
|
||||
return { cacheableJoins, dbJoins };
|
||||
}
|
||||
|
||||
/**
|
||||
* DB 조인 실행 후 캐시 룩업 적용
|
||||
*/
|
||||
private async executeJoinThenCache(
|
||||
tableName: string,
|
||||
dbJoins: EntityJoinConfig[],
|
||||
cacheableJoins: EntityJoinConfig[],
|
||||
selectColumns: string[],
|
||||
whereClause: string,
|
||||
orderBy: string,
|
||||
limit: number,
|
||||
offset: number,
|
||||
startTime: number
|
||||
): Promise<EntityJoinResponse> {
|
||||
// 1. DB 조인 먼저 실행
|
||||
const joinResult = await this.executeJoinQuery(
|
||||
tableName,
|
||||
dbJoins,
|
||||
selectColumns,
|
||||
whereClause,
|
||||
orderBy,
|
||||
limit,
|
||||
offset,
|
||||
startTime
|
||||
);
|
||||
|
||||
// 2. 캐시 가능한 조인들을 결과에 추가 적용
|
||||
if (cacheableJoins.length > 0) {
|
||||
const enhancedData = await this.applyCacheLookupToData(
|
||||
joinResult.data,
|
||||
cacheableJoins
|
||||
);
|
||||
|
||||
return {
|
||||
...joinResult,
|
||||
data: enhancedData,
|
||||
entityJoinInfo: {
|
||||
...joinResult.entityJoinInfo!,
|
||||
strategy: "hybrid",
|
||||
performance: {
|
||||
...joinResult.entityJoinInfo!.performance,
|
||||
cacheHitRate: await this.calculateCacheHitRate(cacheableJoins),
|
||||
hybridBreakdown: {
|
||||
dbJoins: dbJoins.length,
|
||||
cacheJoins: cacheableJoins.length,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return joinResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* 데이터에 캐시 룩업 적용
|
||||
*/
|
||||
private async applyCacheLookupToData(
|
||||
data: any[],
|
||||
cacheableJoins: EntityJoinConfig[]
|
||||
): Promise<any[]> {
|
||||
const enhancedData = [...data];
|
||||
|
||||
for (const config of cacheableJoins) {
|
||||
const cachedData = await referenceCacheService.getCachedReference(
|
||||
config.referenceTable,
|
||||
config.referenceColumn,
|
||||
config.displayColumn
|
||||
);
|
||||
|
||||
if (cachedData) {
|
||||
enhancedData.forEach((row) => {
|
||||
const keyValue = row[config.sourceColumn];
|
||||
if (keyValue) {
|
||||
const lookupValue = cachedData.get(String(keyValue));
|
||||
if (lookupValue) {
|
||||
row[config.aliasColumn] = lookupValue;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return enhancedData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 캐시 적중률 계산
|
||||
*/
|
||||
private async calculateCacheHitRate(
|
||||
cacheableJoins: EntityJoinConfig[]
|
||||
): Promise<number> {
|
||||
if (cacheableJoins.length === 0) return 0;
|
||||
|
||||
let totalHitRate = 0;
|
||||
for (const config of cacheableJoins) {
|
||||
const hitRate = referenceCacheService.getCacheHitRate(
|
||||
config.referenceTable,
|
||||
config.referenceColumn,
|
||||
config.displayColumn
|
||||
);
|
||||
totalHitRate += hitRate;
|
||||
}
|
||||
|
||||
return totalHitRate / cacheableJoins.length;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user