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

Choise.o·2025년 6월 12일

요즘 브라우저 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개의 댓글