[TIL]브라우저 OOM 트러블슈팅

Choise.o·2025년 6월 12일
0

요즘 브라우저 OOM(Out of Memory) 이슈를 처리하고 있다.
대량 데이터가 한꺼번에 브라우저 메모리에 로드되도록 코드가 작성되어있어 수정 중이다.
데이터 크기를 줄이는 작업과는 별개로 리팩토링이 필요한 코드를 발견하여 정리해본다.


💡 현재 구조

  • QueryInfo 컴포넌트는 부모 컴포넌트로부터 queryList 라는 배열 형태의 props 전달 받음
  • queryList로 v-for를 사용하여 v-expansion-panel을 반복 생성
  • 각 panel 내부에 ResultGrid 컴포넌트가 있고 이 컴포넌트는 gridOption 을 props 로 전달받음
  • gridOptionaction.mkOption(item) function을 호출하여 생성

🚨 문제 코드 (개선 전)

<template>
  <v-expansion-panels>
    <v-expansion-panel
      v-for="item in queryList"
      :key="item.id"
    >
      <v-expansion-panel-header>
        {{ item.title }}
      </v-expansion-panel-header>

      <v-expansion-panel-content>
        <ResultGrid v-if="showGrid" 
        			:grid-option="action.mkOption(item)" />
      </v-expansion-panel-content>
    </v-expansion-panel>
  </v-expansion-panels>
</template>

<script lang="ts">
export default defineComponent({
  name: 'QueryInfo',
  props: {
  queryList: {
      type: Array,
      default: () => [],
    },
    ...
  }
  setup(props: Props) {
  	const action = {
    	mkOption(data: string): Grid.Options {
        // 매우 오래 걸리는 연산
        }
    }
    
    return {
      action,
      ...
    };
  }

</script>

❗ 문제

  • ResultGrid에 전달하는 gridOption은 queryList 가 setup 됐을 때 한번만 연산해주면 되는 값이다.
  • 그런데 위의 코드에서는 ResultGridv-if 조건이 변경될 때마다action.mkOption(item)이 호출되고 있었다. ⇒ ResultGrid가 mount/unmount 될 때마다 실행하기 때문
  • action.mkOption()은 매우 긴 문자열을 파싱하는 연산을 수행하기 때문에 시간이 오래 걸리고 성능에 부담을 준다.
  • 이는 메모리 과다 사용을 유발하여 브라우저 OOM 발생에 기여한다.

✅ 해결 방안: computed 로 캐싱 처리

gridOption을 한 번만 연산하여 사용하도록 코드를 리팩토링 해보자.

✅ 개선 코드 (computed 사용)

<template>
  <v-expansion-panels>
  	<!-- queryList 대신 computedQueryList -->
    <v-expansion-panel
      v-for="item in computedQueryList"
      :key="item.id"
    >
      <v-expansion-panel-header>
        {{ item.title }}
      </v-expansion-panel-header>

      <v-expansion-panel-content>
        <ResultGrid v-if="showGrid" :grid-option="item.gridOption" />
      </v-expansion-panel-content>
    </v-expansion-panel>
  </v-expansion-panels>
</template>

<script lang="ts">
export default defineComponent({
  name: 'QueryInfo',
  props: {
  queryList: {
      type: Array,
      default: () => [],
    },
    ...
  }
  setup(props: Props) {
  	const state = reactive({
    	// props 의 queryList를 사용하는 computed 추가. template에서 사용.
    	computedQueryList: computed(() =>
        props.queryList.map((query: QueryInfo) => ({
          ...query,
          gridOption: action.mkOption(query),
        }))
      ),

    });
  
  	const action = {
    	mkOption(data: string): Grid.Options {
        // 매우 오래 걸리는 연산
        }
    }
    
    return {
      ...toRefs(state),
      action,
      ...
    };
  }

</script>

🧪 개선

  • props 로 전달받은 queryList에 gridOption 속성을 만들어주기 위해 computed를 사용.
  • template에서 queryList가 사용되던 부분에 computedQueryList 사용.
    ResultGrid가 렌더링될 때 매번 gridOption 연산하지 않음(showGrid 변경하며 확인)
  • computed는 의존 관계가 있는 데이터에 변경이 감지됐을 때 다시 계산하기 때문에 queryList props가 변경됐을 때 action.mkOption(item)이 호출

✅ 결론

  • v-if로 mount/unmount가 자주 일어나는 구조라면
  • props 를 연산하는 function이 복잡하고 오래 걸린다면
  • template에서 props 연산을 피하고 computed를 사용하자.

0개의 댓글