[리팩토링] 마커 렌더링 최적화

seeen·2024년 1월 18일
1
post-thumbnail

23년 11월 둘째 주에 있었던 일을 정리한 글이다.

마커 렌더링 최적화 도입 배경

이전 편에서 마커 클러스터링 구현에 대해서 다뤘었다. 이는 마커가 지도를 가려 사용자가 마커 및 지도 위치 식별이 어려운 문제를 해결하는 것도 있었지만, 지도를 움직일 때마다 버벅대는 문제도 해결하기 위함이었다.

마커 클러스터링을 적용하기 전엔 약 800여개의 마커가 있는 '대동붕어빵여지도'를 75fps 주사율의 모니터 환경에서 지도를 드래그 해보았을 때 43~49fps까지 떨어지는 문제점이 있었다. fps 측정은 크롬 개발자 도구의 다음 사진의 것으로 측정하였다.

40 프레임 대는 일반 사용자가 흔히 '렉 걸린다.' 라고 느낄 수 있는 수준이다. 프레임 측정이라는게 여러 가지 변수가 있겠지만 PC 환경에서 40 프레임 대면 모바일에서는 거의 사용할 수 없는 수준인 셈이다. 🫠

아무튼 마커 클러스터링을 적용하면서 일정 줌 레벨에서는 프레임율이 70fps 이상으로까지 회복할 수 있었다. 대부분의 사용자는 60fps 환경이기에 이는 충분한 수치였다. 그런데 문제는 지도를 줌인할수록 프레임율이 떨어진다는 것이다. 지도를 줌인하면 마커 클러스터링이 해제되면서 마커의 개수가 늘어나게 되고, 최대 줌인 상태에서는 마커 클러스터링을 적용하지 않은 것과 동일한 환경이 된 셈이다. 그림으로 보면 다음과 같다.

사용자가 화면에서 보는 화면에서는 마커가 4개에 불과하지만, 실제 브라우저 상에서는 수많은 마커가 존재하는 것이다. 따라서 사용자가 지도를 드래그하거나 줌 이벤트를 발생시키는 순간 한 번에 수많은 마커가 조정되면서 렌더링 프레임이 떨어지는 것이다. 따라서 마커 렌더링 최적화 작업이 필요로했다.

마커 렌더링 최적화

마커 렌더링 최적화 방법은 여러 가지가 있겠지만, 선택한 방법은 사용자 스크린 사이즈에 해당하는 마커만 렌더링하는 것이다. 마커 클러스터링을 구현할 때처럼 사용자가 보고 있는 지도의 좌우 끝 좌표값을 지도 조작 이벤트 후 구하여 스크린에 해당하는 마커를 식별할 셈이었다.

최적화 로직 도입 위치 선정

마커 렌더링 최적화 로직을 도입할 수 있는 곳이 두 곳 있었다. 서버에서부터 데이터를 받아와서 마커로 렌더링하기까지를 그림으로 나타내면 다음과 같다.

우리 서비스에는 여러 지도를 한 번에 모아볼 수 있는 '모아보기'와 하나의 지도만 조회하는 단일 지도 조회, 이렇게 조회 방식이 두 가지가 있다. 사용자의 지도 핸들링 이벤트가 끝나고 각 조회 방식에 맞게 서버로부터 데이터를 요청한다.

마커를 생성하는 로직을 모두 좌표에 의존성을 걸어두었다. 마커를 생성하기 위해서는 좌표값이 필수이고, 좌표값이 변하는 상황은 마커를 다시 그려야하는 상황이기에 그렇게 진행하였다.

렌더링 최적화 방법으로는 서버로부터 받아온 데이터로 스크린 사이즈에 맞는 좌표만 갱신해서 최적화 하는 방법(1)과, 좌표는 우선 일괄 갱신하되 마커 생성 전에 스크린 사이즈에 해당하는 마커만 생성하는 방법(2)으로 추려볼 수 있겠다.

당연하게도 처음에는 전자의 방법으로 진행하였다. 좌표의 갱신이 곧 마커 생성을 의미하기에 스크린 사이즈에 맞는 좌표만 갱신하면 최적화 효과를 더 많이 볼 수 있을 것이다.

첫 번째 방식의 문제점

스크린에 해당하는 좌표만 갱신하도록 하자 그 즉시 프레임율이 평균 60fps 이상을 웃돌았다. 그런데 문제점이 있었다. 지도만 조작하면 문제가 없었지만, 마커가 렌더링 되지 않은 사이드바의 리스트를 클릭하면 포커싱이 되지 않는 문제다.

원래는 사이드 바를 클릭할 경우 아래와 같이 해당 마커를 지도 중앙에 올 수 있도록하고, 사이드바를 확장하여 추가 정보를 불러올 수 있어야한다.

하지만 좌표를 스크린 사이즈에 해당하는 것들만 갱신했으니, 스크린 사이즈에 해당하는 마커만 생성되었을테고, 이에 해당하지 않는 사이드바의 리스트를 클릭하면 아무런 반응이 없는 것이다. 이 문제를 해결하려면 사이드바가 가지고 있는 좌표값과 지도가 가지고 있는 좌표값을 별도로 가져갈 필요가 있어보였다.

이는 서버로부터 동일한 값을 두 개의 전역 상태로 나눠 갖는 상황 자체가 복잡했고 추후 휴먼 에러가 발생할 확률이 높아보였다. 따라서 모든 마커를 생성하되 마커 표시 전에 스크린 사이즈에 해당하는 마커만 표시하는 방법으로 변경하였다.

두 번째 방식 도입

모든 좌표를 우선 갱신하였니 Marker Context 에 모든 좌표값이 존재했고, 화면상에서 보이지 않는 마커여도 전역 상태로 갖고 있을 수 있었다. 즉, 렌더링 되지 않은 사이드바의 리스트라도 클릭 시 정상적으로 포커싱 되는 것을 확인할 수 있었다. 😇

스크린 사이즈에 해당하는 마커만 표시하는 기능을 코드로 보면 다음과 같다.

const createElementsInScreenSize = (elementType: ElementType) => {
  if (!mapInstance) return;

  // 지도 우상단, 좌하단 좌표값 도출
  const mapBounds = mapInstance.getBounds();
  const northEast = mapBounds._ne;
  const southWest = mapBounds._sw;

  // 단일 토픽 조회 및 모아보기 핀 구별 후 마커 리스트 반환
  const addedMarkerTypeCoordinates = createElementsColor(elementType);

  // 스크린의 꼭짓점 내에 해당하는 마커만 렌더링하기
  const coordinatesInScreenSize = addedMarkerTypeCoordinates.filter(
    (coordinate: any) =>
    coordinate.latitude <= northEast._lat &&
    coordinate.latitude >= southWest._lat &&
    coordinate.longitude <= northEast._lng &&
    coordinate.longitude >= southWest._lng,
  );

  return coordinatesInScreenSize;
};

리팩토링 결과

27인치 FHD 모니터 120fps 환경에서 '대동붕어빵여지도'가 90fps ~ 105fps 대로 나오는 것을 확인할 수 있었다. 모바일 환경에서도 버벅대는 것 없이 원할하게 사용할 수 있었다. 😇

profile
woowacourse FE 5th, depromeet Web 15th

1개의 댓글

comment-user-thumbnail
2024년 2월 4일

그래서 사이드바 눌렀을 때 마커로 안갔던거구나 이제 알았어요

답글 달기