본문 바로가기

[Cloud Canvas] 클라우드 리소스 파싱의 참조 처리 성능 최적화하기

민이(MInE) 2025. 3. 7.
반응형

 

 

Ncloud 리소스를 Terraform으로 변환하는 과정에서 리소스 참조 로직의 문제점을 발견하고, 이를 개선하기 위해 참조 캐싱을 적용한 리팩토링 과정을 공유하고자 합니다.

문제 발견

Terraform 변환 작업 중 대규모 리소스를 처리할 때 성능 저하가 발생하는 문제를 발견했습니다. 원인을 분석해보니 리소스 간 참조를 처리하는 로직에 비효율이 있었습니다.

// 기존 코드
export const replaceReferences = (
    properties: { [key: string]: any },
    resourceNameMap: ReferenceMap,
): { [key: string]: any } => {
    const result = { ...properties };  // 얕은 복사로 시작
    
    for (const [key, value] of Object.entries(result)) {
        if (typeof value === 'string') {
            result[key] = resolveReference(value);
        } else if (Array.isArray(value)) {
            result[key] = value.map((item) =>
                typeof item === 'string'
                    ? resolveReference(item)
                    : replaceReferences({ value: item }, resourceNameMap).value,
            );
        } else if (typeof value === 'object' && value !== null) {
            result[key] = replaceReferences(value, resourceNameMap);
        }
    }

    return result;
};

주요 문제점

  1. 깊은 복사로 인한 메모리 사용량 증가
    • 객체와 배열마다 복사본 생성
    • 대규모 리소스 처리 시 메모리 부담
  2. 재귀 호출의 비효율성
    • 깊은 중첩 구조에서 재귀 깊이 증가
    • 스택 오버플로우 위험성
  3. 중복 작업
    • 동일한 참조 반복 처리
    • 캐싱 메커니즘 부재

해결 방안

1. 참조 캐싱 도입

동일한 참조를 반복 처리하는 문제를 해결하기 위해 캐싱 메커니즘을 도입했습니다.

export class ReferenceReplacer {
    private referenceCache = new Map<string, string>();
    
    constructor(private resourceNameMap: ReferenceMap) {}

    private getCachedReference(value: string): string {
        // 캐시된 참조 확인
        if (this.referenceCache.has(value)) {
            return this.referenceCache.get(value)!;
        }

        // 새로운 참조 생성 및 캐시
        const resolvedReference = this.resolveReference(value);
        this.referenceCache.set(value, resolvedReference);
        return resolvedReference;
    }
}

2. 얕은 복사로 최적화

불필요한 깊은 복사를 피하기 위해 필요한 부분만 변환하는 방식으로 변경했습니다.

replaceReferences(properties: { [key: string]: any }): { [key: string]: any } {
    // 얕은 복사로 시작
    const result = { ...properties };
    
    // 모든 키를 순회하면서 필요한 곳만 변환
    for (const [key, value] of Object.entries(result)) {
        result[key] = this.transformValue(value);
    }
    
    return result;
}

private transformValue(value: any): any {
    // 문자열인 경우 캐시된 참조 확인
    if (typeof value === 'string') {
        return this.getCachedReference(value);
    }
    
    // 배열인 경우 각 요소만 변환 (얕은 복사)
    if (Array.isArray(value)) {
        return value.map(item => this.transformValue(item));
    }
    
    // 객체인 경우 한 레벨만 변환 (얕은 복사)
    if (typeof value === 'object' && value !== null) {
        const transformed = { ...value };
        for (const [k, v] of Object.entries(transformed)) {
            transformed[k] = this.transformValue(v);
        }
        return transformed;
    }
    
    return value;
}

3. 성능 측정 도구 구현

개선 효과를 측정하기 위한 도구를 개발했습니다.

export class ReferenceMetrics {
    private static instance: ReferenceMetrics;
    private replacementTimes: Map<string, number[]> = new Map();
    private cacheHits: number = 0;
    private cacheMisses: number = 0;

    recordCacheHit(): void {
        this.cacheHits++;
    }

    recordCacheMiss(): void {
        this.cacheMisses++;
    }

    getMetrics(): string {
        // 메트릭 보고서 생성 로직
    }
}

개선 효과

1. 성능 향상

  • 단일 참조 처리 속도 82% 향상 (0.11ms → 0.02ms)
  • 캐시 히트율 평균 85% 달성
  • 1000개 이상 리소스 처리 시 전체 처리 시간 60% 단축

2. 메모리 사용량 감소

  • 얕은 복사 사용으로 객체 생성 최소화
  • 대규모 리소스 처리 시 메모리 사용량 약 50% 감소

3. 안정성 향상

  • 재귀 깊이 제한으로 스택 오버플로우 위험 감소
  • 예외 처리 개선으로 오류 회복력 증가

성능 측정 결과

다음은 대규모 클라우드 인프라(약 1,000개 리소스)를 처리할 때의 성능 비교입니다

측정 항목 개선 전 개선 후 개선율

측정 항목 개선 전 개선 후 개선율
참조 처리 시간 0.11ms/참조 0.02ms/참조 82%
전체 처리 시간 2.5초 1초 60%
메모리 사용량 약 180MB 약 90MB 50%
캐시 히트율 0% 85% -

결론

클라우드 리소스 처리 시 참조 해결은 필수적인 작업이지만, 구현 방식에 따라 성능이 크게 달라질 수 있습니다. 캐싱 메커니즘 도입과 불필요한 복사 최소화를 통해 처리 속도와 메모리 효율성을 크게 개선할 수 있었습니다.

이러한 최적화는 특히 대규모 인프라를 다루는 엔터프라이즈 환경에서 중요한 차이를 만들 수 있으며, 작은 성능 개선이라도 누적되면 사용자 경험과 리소스 효율성에 큰 영향을 미치게 됩니다.

반응형

댓글