마커 개수에 따른 페이지 로딩시간 해결방법 문의

[FAQ] 지도/로컬 API 문의 전 꼭 읽어 주세요.

안녕하세요
클러스터 기능 관련해서 궁금한게 있어서 문의 드렸습니다.

현재 아래 코드처럼 클러스터기능을 사용하고 있는데 마커가 점점 많아지다 보니 페이지를 로딩하는데 시간이 너무 오래걸리는 문제가 생깁니다.

카카오맵 지도 즐겨찾기 기능을 보면 수천 개의 장소를 등록해도 페이지 사용에 대한 불편함을 못 느끼겠던데 혹시 어떤 방식으로 문제를 해결했는지, 그리고 문제를 해결하기 위해서 어떤 방식을 적용할 수 있는지 궁금합니다.

            // 마커 클러스터러를 생성합니다
            var clusterer = new kakao.maps.MarkerClusterer({
                map: map, // 마커들을 클러스터로 관리하고 표시할 지도 객체
                averageCenter: false, // 클러스터에 포함된 마커들의 평균 위치를 클러스터 마커 위치로 설정
                gridSize: 70, //클러스터의 격자 크기. 화면 픽셀 단위이며 해당 격자 영역 안에 마커가 포함되면 클러스터에 포함시킨다
                minLevel: 3, // 클러스터 할 최소 지도 레벨
                calculator : 2, // 클러스터의 크기 구분 값
                styles: [{
                  fontSize: '15px',
                  width : '24px',
                  height : '40px',
                  background: 'url("https://t1.daumcdn.net/localimg/localimages/07/mapapidoc/markerStar.png") no-repeat',
                  positon: 'getCenter'
                }],
                default : null
            });

            // 오버레이 클러스터러를 생성합니다
            var clusterer1 = new kakao.maps.MarkerClusterer({
                map: map, // 마커들을 클러스터로 관리하고 표시할 지도 객체
                averageCenter: false, // 클러스터에 포함된 마커들의 평균 위치를 클러스터 마커 위치로 설정
                gridSize: 70, //클러스터의 격자 크기. 화면 픽셀 단위이며 해당 격자 영역 안에 마커가 포함되면 클러스터에 포함시킨다
                minLevel: 3, // 클러스터 할 최소 지도 레벨
                calculator : 2, // 클러스터의 크기 구분 값
                styles: [{
                  fontSize: '0px',
                  background: null
                }],
                default : null
            });

        // 기존 클러스터 오버레이를 저장하는 변수
        var currentClusterOverlay = null;

        // 지도에 마커를 표시하는 함수입니다
        function displayMarker(title, name, lat, lng, assets, type, speed, install, run, supply, manege) {
          titleandname = title + " " + name;
          // 이전 클러스터 오버레이가 있다면 제거합니다
          if (currentClusterOverlay) {
            currentClusterOverlay.setMap(null);
          }

          // 마커 생성 및 설정 코드
          var marker = new kakao.maps.Marker({
            map: map,
            position: new kakao.maps.LatLng(lat, lng)
          });
          var overlay = new kakao.maps.CustomOverlay({     // 클릭시 뜨는 글자
            clickable: true,
            yAnchor: 1.3,
            position: marker.getPosition()
          });
          var overlay1 = new kakao.maps.CustomOverlay({    // 마커 밑에 글자
            yAnchor: -0.3,
            position: marker.getPosition()
          });

          var content = '<div class="wrapboard">' +     // 클릭시 뜨는 글자
            '        <div id="divright"> ' +
            '        <input id="updatebutton" type="button" onclick ="viewupdatebox();" value="수정" />' +
            '        <input id="deletebutton" type="button" onclick ="viewdeletebox();" value="삭제" />' +
            '        </div> ' +
            '        <div class="half">' +
            '          <a id="share-location"  href="javascript:shareLocation()"> ' +
            '            <img src="https://developers.kakao.com/assets/img/about/buttons/navi/kakaonavi_btn_medium.png" />' +
            '          </a>' +
            '        </div>' +
            '        <div class="board">' +
            '          1: ' + assets + '<br>' +
            '          2: ' + type + '<br>' +
            '          3: ' + speed + '<br>' +
            '          4: ' + install + '<br>' +
            '          5: ' + run + '<br>' +
            '          6: ' + supply + '<br>' +
            '          7:' + manege +
            '        </div>' +
            '    </div>';

          var content1 = document.createElement('div');   // 마커 밑에 글자
          content1.innerHTML =  titleandname;
          content1.style.cssText = 'font-size: 10px; font-weight: bolder;color : blue ;text-shadow: -1px 0 #FFF, 0 1px #FFF, 1px 0 #FFF, 0 -1px #FFF;';

          overlay.setContent(content);
          overlay1.setContent(content1);
          overlay1.setMap(map);
          clusterer.addMarker(marker);
          clusterer1.addMarker(overlay1);

          // 마커에 클릭 이벤트를 등록합니다
          kakao.maps.event.addListener(marker, 'click', function(mouseEvent) {
            // 이전 클러스터 오버레이가 있다면 제거합니다
            if (currentClusterOverlay) {
              currentClusterOverlay.setMap(null);
            }
            overlay.setMap(map); // 클러스터 오버레이를 표시합니다
            // 클릭한 마커에 대한 클러스터 오버레이를 저장합니다
            currentClusterOverlay = overlay;

            var markercenter = new kakao.maps.LatLng(lat, lng);
            map.panTo(markercenter);


          // 지도에 클릭 이벤트를 등록합니다
          kakao.maps.event.addListener(map, 'click', function(mouseEvent) {
            overlay.setMap(null);
          });
        }

안녕하세요~
그 부분은 저희가 담당하는 부분이 아닌 다른 부서에서 개발한 내용으로,
해당 내용을 알 수는 없습니다;;;
그래서 어떻게 구현했는지는 알기가 어렵습니다 ㅎㅎ

그래서 그냥 제가 그런 기능을 구현한다면,
초기에 로딩이 느린 것이라면,
처리 해야 되는 마커의 수를 구분하여, 60프레임, 즉 16.7ms 안에서만 처리될 수 있도록 한번에 처리될 수 있는 양을 조절할 것 같습니다.

그래서 하나의 로직이 처리되느라 UI가 렌더링 되야되는 시간을 잡아먹지 않도록 처리를 하면, 초반의 느리다는 느낌이 많이 줄어들 것 같습니다.

그리고 이후에 클러스터나 마커, 오버레이등은 모두 DOM노드로 구분되기 때문에, 화면에서 보이는 양이 작다면, 이 외부에 있는 데이터를 제외하고 처리하여 DOM 노드가 많아지는 것을 제어할 필요도 있어보입니다.
무조건 DOM노드가 많아지면, 화면 조작시마다 reflow 처리를 해야하는 노드수가 많아져서 느려질 수 밖에 없습니다.

즉 한번에 많은 연산을 한번에 수행하기 보다는 이를 나눠서 수행하는 것으로 초점을 맞춰보시는게 어떠실까 합니다.

보여지는 화면 이외에 마커를 제외하고 현재 화면에 보이는 마커만 우선적으로 표시되도록 제공하는 기능이 있을까요?

아뇨. 그런 기능은 없습니다.
지도의 바운더리를 알아내는 API는 존재하지만 (bounds)
그외의 기능은 없습니다. 모두 개발자분께서 구현해야 하는 기능입니다.

지도에 idle 이벤트가 발생할 때 지도 바운더리 값을 가져와 그 값 이내에 있는 마커들만 markerclusterer.setMap(map); 을 통해 지도에 표시함으로 페이지 로딩 시간을 줄이려고 합니다.

혹시 아래와 같이 마커들을 클러스터에 추가 하고 난 후 개별 마커들의 주소만 가져오고 싶다면 어떻게 해야할지 도움 주실 수 있으실가요?


            // 마커 클러스터러를 생성합니다
            var markerclusterer = new kakao.maps.MarkerClusterer({
                averageCenter: false, // 클러스터에 포함된 마커들의 평균 위치를 클러스터 마커 위치로 설정
                gridSize: 70, //클러스터의 격자 크기. 화면 픽셀 단위이며 해당 격자 영역 안에 마커가 포함되면 클러스터에 포함시킨다
                minLevel: 3, // 클러스터 할 최소 지도 레벨
                calculator : 2, // 클러스터의 크기 구분 값
                styles: [{
                  fontSize: '15px',
                  width : '24px',
                  height : '40px',
                  background: 'url("../PNGFolder/markerStar.png") no-repeat',
                  positon: 'getCenter'
                }],
                default : null
            });
          var marker = new kakao.maps.Marker({
            position: new kakao.maps.LatLng(lat, lng)
          });
          markerclusterer.addMarker(marker);
          markerclusterer.setMap(map);

개별마커들의 주소라는게 어떤걸 말씀이실까요?

올려주신 코드에 따라서, 현재 지도가 zoom이 되거나 할때마다, 표시되야 되는 정보를 기반으로 Marker들을 계속 새로 생성하고 계시다면, 이후에 GC(가비지컬렉터)에 의해서 화면의 블락킹이 발생할 수 있습니다.

마커 클러스터러에 모든걸 맡기기보다는, 해당 레벨에서 표시되어야 되는 마커들을 처음 생성시에,
별도의 HashMap을 통해 저장을 하고, 이후에는 Map에서 뽑아서 쓰는게 더 좋은 방법으로 보입니다.

이미 주어진 API를 사용하면 성능을 올리기가 어려울때가 많습니다. 마커클러스터가 내부적으로 입력된 마커들로부터 좌표 연산을 수행하기 때문에, 이러한 연산을 직접 제어 하실려면, 클러스터링을 직접 구현하시는 것도 방법입니다.
이외 여러가지 공간분할 관련된 알고리즘들이 있는데(쿼드트리, K/D트리, BSP트리 등) 이러한 자료구조 및 알고리즘을 이용해서 미리 첫 페이지 로딩시에 마커들을 분할 시켜두고 필요에 따라 보여주는 방식으로 구현을 해볼 수도 있습니다.
이러한 알고리즘의 최대 목적은 현재의 보이는 화면에서 특정 노드들을 연산할때 모든 노드를 가지고 연산을 수행하는게 아니라, 보여줘야만 하는 것들로 한정해서 연산을 할려는 목적이므로 성능 향상을 꽤할 수 있습니다.

다만 알고리즘마다, 초기연산비용이 클지, 중간의 검색시 연산비용이 클지는 조금씩 다르므로 이를 고려하실 필요는 있습니다.

아무튼, 보통 느려지는 이유는 표출량이 많거나, 모두 표출은 안되더라도 그 노드들의 탐색 및 연산량이 많아서, 브라우저가 화면을 렌더링 하는 주기인(60프레임 1프레임당 16.7ms) 이내에 처리가 안되고 딜레이가 되서 끊김이 발생하거나, 너무 긴 시간 할해시 다른게 렌더링이 안되는 현상이 대부분이므로, 이 연산량 자체를 줄일 수 있게 여러가지 방법을 사용하시는게 좋을 것 같습니다.

그리고 무조건 new로 객체를 생성하면 메모리 이슈가 생기게 되므로 중간에 필연적으로 GC에 의해서 블락이 걸리기 때문에, 적당히 캐싱도 하시면서 처리를 하는게 좋아 보입니다.

  • DOM에 추가되는 노드는 최소한으로
  • 한번에 연산되야 되는 노드도 최소한으로
  • 이를 해결하기 위해 여러 공간분할 알고리즘, 시분할 처리, 클립핑 등을 한번 고려해보시면 좋을 것 같습니다.

대량의 데이터를 통한 마커를 다루는데에는 대부분의 벤더사가 제공하는 API만으로는 부족합니다.
API로는 화면에 표출하는 부분에만 이용하시고 그외 나머지 데이터 관리는 직접적으로 구현을 하셔야 됩니다.

클러스터 속 마커들의 위치를 받아오는 방법이 궁금해서 여쭤봤습니다
답변해주셔서 감사합니다. 답변 참고해서 해결방안 찾아보겠습니다.

넵. 그리고 한가지 더 말씀드리면,

한번에 대량의 데이터를 가져온 후에,
이를 지도에 표시하는데, 지도의 레벨 변화에 따라 MarkerClusterer를 통해서만 표출을 한다면,

이는 매순간 연산을 그 수많은 마커를 통해서 해야되므로 느려질 수 밖에 없습니다.

이러한경우엔, LOD(level of detail)를 구성해서, 각 레벨별로 연산할 마커 갯수 자체를 줄이는 것도 좋은 방법입니다.
어차피 클러스터링을 통해서 화면에 보여진다면 마커의 정확한 위치 및 갯수는 그리 중요치 않습니다. 점점 지도를 확대해야지만 제대로 마커가 보일테니까요.

즉,
서버에서 LOD구성이 안된다면,
처음에 데이터 로딩시에 표출 레벨별로 마커들을 구분하여 별도의 자료구조에 저장하고 (이때 위에 설명드린 알고리즘들을 선택해서 적용해 볼수 있습니다)
그 뒤엔 지도의 bounds값에 따라 보이는 영역의 마커들만 가져와서 연산을 하는 방식으로 하면 웹어플리케이션의 속도 향상에 많은 도움이 될것 같습니다.