<aside>
서비스를 설계하던 중 내 주위 병원 또는 약국에 대한 정보를 가져와야 했다.
따라서 아래와 같은 자세한 요구사항을 고려할 필요가 있다.
내가 현재 있는 위치와 그 위치를 기반으로 가장 가까운 병원 또는 약국 정보가 필요했다. 먼저 병원 정보는 공공 API를 통해서 전국 병원 정보를 가지고 왔다. 이 정보에는 좌표 정보가 있었다. 따라서 좌표 정보를 통해서 내 현재 좌표와 거리가 가까운 병원 또는 약국 정보를 가지고 온다.
병원 또는 약국에 대한 상세 정보를 가지고 오는 것 뿐만 아니라, 추후에 리뷰, 추천해요 등과 같은 추가 정보를 조인 연산으로 가지고 와야할 수 있다. 따라서 이 과정에서 조인 등을 활용해야할 수 있으므로 이를 고려해야 한다.
</aside>
<aside>
MongoDB의 2dsphere 인덱스는 지구 표면과 같은 구면 좌표계상에서 지리 데이터를 효율적으로 조회하기 위한 인덱스이다.
이 인덱스는 위치 정보를 저장하여 구면 기하 연산을 수행하는데, 이를 통해 특정 위치를 기준으로 일정 반경 내에 위치한 지점을 효율적으로 찾을 수 있다.
2dsphere 인덱스는 S2 지오메트리 라이브러리를 활용한다.
S2 라이브러리는 지구 표면을 격자 형태의 셀로 분할하며, 각 지리 데이터의 좌표나 도형은 이 셀들의 집합으로 표현된다.
MongoDB는 이러한 셀들을 B-트리 구조의 인덱스로 저장함으로써 공간적으로 인접한 데이터들을 빠르게 탐색할 수 있도록 지원한다.
지구를 정육면체로 만들고 이후 각 면을 Level 0라 하고 이후 부터 Level 30까지 계속 4등분한다.
즉, Level 0는 큐브의 6면이 루트 셀이며, Level 1은 Level 0 셀을 4 등분해 얻는 자식 셀인 것이다.
이와 같이, LEVEL 30까지 나눠지며모든 셀은 64-bit 정수 Cell ID 하나로 표현된다.
앞쪽 비트는 어느 면 어느 레벨인지, 뒷쪽 비트는 면 안에서의 위치 정보를 담는다. 덕분에 숫자가 비슷하면 지리적으로도 가깝다는 성질이 생기는 것이다.
서울 광화문을 Level 15에서 보면, 딱 1개 셀 ID에 담긴다. 만약 반경 1 km 원이라면 원을 완전히 덮는 다수의 셀을 골라 커버링이라는 셀 집합으로 기록한다.
<aside>
사용자의 현재 좌표를 기준으로 가장 가까운 병원이나 약국을 검색해야 할 경우, 2dsphere 인덱스를 활용하면 거리에 따라 정렬된 결과를 빠르게 얻을 수 있다. 또한, maxDistance를 지정하면 불필요하게 먼 곳까지 조회하지 않고 일정 반경 내의 결과로 제한할 수 있어 효율적이다.
하지만 병원 또는 약국과 관련된 운영 시간, 리뷰, 평점 등 다양한 부가 정보를 조회할 때는 MongoDB에 자동 조인 기능이 없어 추가적인 처리가 필요하다. 또한 MongoDB의 조인은 제한된 범위 내에서 동작하며 복잡한 다단계 조인이나 대용량 데이터 간의 조인은 성능 저하가 발생할 가능성이 존재한다.
</aside>
<aside>
PostGIS는 Geometry와 Geography 두 가지 주요 공간 데이터 타입을 제공한다.
Geometry 타입은 평면 좌표계를 기반으로, 모든 좌표 연산을 평면상에서 수행하지만, Geography 타입은 위도와 경도를 통해 지리 좌표계를 사용하여 지구 표면의 곡률을 고려한 연산을 수행한다.
즉, Geometry는 투영된 평면 좌표계를 가정(위도와 경도를 평면으로 투영)하고, Geography는 지구를 타원체로 간주한 둥근 지구 모델을 사용한다.
Geometry는 평면 좌표 단위의 연산을 수행한다. 반면 Geography는 위도와 경도 좌표계를 사용하고 거리를 미터 단위로 계산하는 등 지표면상의 실제 거리를 바로 다루지만 지원되는 함수의 종류가 적다.
예를 들어, Geometry 타입은 PostGIS의 거의 모든 공간 함수를 지원하는 반면, Geography 타입은 ST_Distance, ST_DWithin, ST_Intersects 등 일부 함수만 지원된다.
이런한 구조적 차이로 인해 성능상의 차이도 존재한다. Geometry는 계산이 단순한 평면 기하 연산이므로 처리 속도가 빠르고 대부분의 공간 함수를 지원하지만, Geography는 복잡한 삼각함수 연산을 포함하여 계산 비용이 높아 대용량 연산 시 Geometry보다 상대적으로 느릴 수 있다.
실제로 동일한 거리를 계산할 때 평면상의 피타고라스 공식을 쓰는 경우보다 Geography는 여러 번의 삼각함수 연산이 추가되어 비용이 높아지는 것이다.
따라서 데이터 범위가 비교적 좁거나 지역적인 경우에는 적절한 투영 좌표계를 설정한 Geometry 타입을 사용하는 것이 유리하고, 지구 전역에 걸친 광범위한 데이터를 다루거나 여러 좌표계 변환이 번거로운 경우 Geography 타입을 사용하는 것이 좋다.
간단히 말하면 Geometry를 사용했을때 발생하는 투영 오차가 발생할 수 있지만 이는 적은 거리에서 오차가 크지 않기에 가까운 거리일 수 성능을 위해 Geometry를 사용하는 것이 더 좋은 선택지가 된다.
</aside>
<aside>
PostGIS는 PostgreSQL에 공간 기능을 추가하는 확장으로, GiST(Generic Search Tree) 기반의 R-트리(Rectangle-tree) 인덱스를 사용한다
GiST는 인덱스 프레임워크로 어떤 자료형이든 전용 인덱스를 만들 수 있게 해주는 것이다.
즉, 하나의 공통 코드로 R-tree, B-tree 등 다양한 인덱스를 플러그인처럼 꽂아 쓸 수 있는것이다.
따라서 필요에 따라 인덱스 자료구조를 선택하는 것이고, 기본적으로 B-tree를 인덱스 자료구조로 사용하지만 PostGIS는 R-tree를 인덱스 자료구조로 사용한다.
N차원 최소 경계 사각형(MBR) 계층으로 각 노드는 자식의 MBR을 담고, 리프 노드는 실제 객체(점, 라인, 폴리곤)의 MBR을 담는 것이다. 이는 GiST 노드는 MBR만 갖고 있고, 실제 geometry / geography 데이터는 본문 테이블에 저장된다는 것을 의미한다.
즉 각 노드는 자식 노드에 대한 포인터를 가지고 있고, 실제 리프 노드에 점 라인 폴리곤이 담기는 것이다.
리프 노드의 MBR는 2-차원 공간에서 한 개의 점 선 폴리곤을 완전히 감싸는 가장 작은 직사각형이며 영역이라고 보면 된다.
37.5700, 126.9830인 데이터를 삽입할때 리프 노드에는 MBR=(126.9830, 37.5700, 126.9830, 37.5700)와 같이 저장된다. 해당 위 경도를 포함하는 가장 작은 직사각형 영역이다.
만약 페이지 초과 시 두 그룹으로 분할하고 부모 MBR 갱신하며 필요하면 루트까지 전파된다.
결국 상위 노드는 자식 MBR을 품은 더 큰 MBR을 가지게 되는 것이다.
</aside>