Database로 PostgreSQL을 선택한 이유

동재·2024년 6월 21일
post-thumbnail

왜 그랬지..?🤔

프로젝트 시작 전에 기술 스택을 정하는 일은 생각보다 중요했습니다.. 시간에 쫓겨 평소 사용하던 기술 스택을 다음 또 그 다음 프로젝트에 동일하게 사용한 경험이 한 번쯤은 있을 텐데요. 예 그 사람이 저에요ㅜㅜ~~

이번 사이드 프로젝트에서 DB를 MariaDB -> Postgresql로 변경 했던 경험을 이야기하면서, 왜 프로젝트 시작 전에 기술 스택을 충분히 고민해야 하는지 적어 보려고 합니다.🥹


본론

저는 사람들에게 백엔드 개발자라 소개했었지만, 사실은 Spring Boot + MariaDB(Mysql) 개발자였을지도 모르겠어요. (어쩌면 지금도?..)

'학교 프로젝트 -> 회사 프로젝트 -> 사이드 프로젝트'에 이르기까지 사실상 모든 프로젝트를 Spring Boot + MariaDB 로 진행했었습니다.

이번 사이드 프로젝트의 이름은 '경로상 휴게소 찾기' 였는데, 이름에서 보이듯 위치 데이터 처리가 중요한 요소였습니다.

하지만 개발 시작 전 '위치 데이터가 뭐가 다르겠어? 비슷하겠지' 란 생각을 떨쳐내지 못했고, 이번 프로젝트도 MariaDB로 개발을 시작했어요.

그리고 얼마 지나지 않아 DB 선택에 아쉬움을 느꼈습니다.

🔥첫 번째 문제

비교 로직을 만들다 첫 번째 문제에 부딪혔어요.

처음 생각한 경로 비교 로직
1. 경로 데이터 조회
2. 경로 데이터 <-> 휴게소 리스트 거리 비교
	* 둘 사이의 거리가 500M 보다 가까우면 True
3. True인 휴게소 리스트 반환

저는 비즈니스 로직은 서비스 단에서 처리해야 한다고 생각해요. 쿼리에 모든 비즈니스 로직이 들어있는 회사 코드를 유지보수하면서, 해당 코드가 얼마나 관리하기 어려운지 뼈저리게 겪었기 때문이에요.

따라서 경로(LineString) <-> 휴게소 리스트(Point[]) 비교 로직을 서비스 단에서 구현하려했습니다.

Point = 위치 객체 Ex) [128.6284588 35.8796733] 
LineString = List<Point>

문제는 제가 단순하게 이중 For문으로 구현하려 했다는 점입니다.

이중 For문의 시간 복잡도는 O(M * N)이고 M과 N의 크기에 따라 속도가 매우 느려질 수 있다는 점을 간과했습니다.

데이터의 양이 많지 않았다면 동작에 큰 문제가 없었을 수도 있습니다. 하지만 불행히도, M(경로)과 N(휴게소) 모두 데이터의 양이 많았고, 따라서 수행 시간은 급격히 증가했습니다.

아래 예시를 보면

거리 비교 메서드를 아래와 같이 정의하고,

public class DistanceCalculator {

    /**
     * 두 개의 Point 간의 Euclidean 거리를 계산하는 메서드
     * @param pointA 첫 번째 Point
     * @param pointB 두 번째 Point
     * @return 두 Point 간의 거리
     */
    public static double calculateDistance(Point pointA, Point pointB) {
        double deltaX = pointA.getX() - pointB.getX();
        double deltaY = pointA.getY() - pointB.getY();
        return Math.sqrt(deltaX * deltaX + deltaY * deltaY);
    }

    public static void main(String[] args) {
        // 예제 Point 객체 생성
        Point pointA = new org.locationtech.jts.geom.GeometryFactory().createPoint(new org.locationtech.jts.geom.Coordinate(1.0, 2.0));
        Point pointB = new org.locationtech.jts.geom.GeometryFactory().createPoint(new org.locationtech.jts.geom.Coordinate(4.0, 6.0));
        
        // 거리 계산
        double distance = calculateDistance(pointA, pointB);
        System.out.println("두 점 사이의 거리: " + distance);
    }
}
처리속도 = 0.001ms
경로 = (서울역 -> 부산역) 4231개의 Point
휴게소 리스트 = 230개의 Point
라고 가정했을 때

해당 로직에 걸리는 시간은
총 시간 (ms)=4231×230×0.001=973.13ms
즉 1초에 육박합니다.

Naver API를 통해 경로를 조회하는데 걸리는 시간, 방향 판단 메서드 처리 시간 등을 고려하면 해당 API는 적어도 2초이상 걸린다고 생각했습니다.

클릭 한번 할 때마다 2초를 기다려야 하는 웹사이트? 최소한 한국에서는 사용하지 못할 속도라고 생각했구요.

따라서 시간복잡도를 낮추기 위해 적절한 알고리즘을 찾기로 했습니다.

🔥두 번째 문제

자료조사를 통해 R-Tree에 대해 알 게 되었습니다.

R-Tree는 시간 복잡도 O(log n)으로 이중 For문에 비하면 굉장히 빨랐고, 때문에 R-tree로 서비스 단에서 비교 로직을 구현하려 했습니다.

하지만 2번째 문제에 부딪혔습니다.🥹

우선 R-Tree를 이용한 거리 비교 메서드의 구현이 생각만큼 쉽지 않았습니다. 완벽한 이해 없이 ChatGPT의 도움을 받아 구현했는데, 이게 제대로 구현이 된건지 확신이 서지 않았고 테스트 결과도 제가 예상한 바와 달랐습니다.

그러다 "R-Tree를 굳이 내가 구현해야 하나?" 이미 R-Tree 기반의 로직을 제공하는 라이브러리가 있지 않을까? 하는 생각이 들었어요. 처음부터 자료조사를 제대로 할걸 ㅜㅜ

추가적인 검색을 통해 여러 DB에서 R-Tree 기반의 인덱싱과 공간함수를 제공한다는 걸 알게 되었습니다.

🔥세 번째 문제..

3번째 문제이자 고민은 제가 이미 MariaDB를 사용해 프로젝트를 진행한지 시간이 꽤 지났다는 점이었어요.

MariaDB에서 제공하는 공간 함수인 ST_Distance_Sphere(MariaDB)A Point와 B Point의 거리를 계산하는 함수입니다. 따라서 A Point의 일정 범위 안에 B Point가 존재하는지 체크하려면 결과 값과 범위 값을 비교하는 추가 비즈니스 로직이 쿼리에 포함되어야 했습니다.

반면, ST_DWithin(PostGIS)특정 'A Point'의 일정 범위 안에 'B Point'가 존재하는지 판단하는 함수입니다. 따라서 추가 비즈니스 로직이 필요하지 않았고, 위의 단점을 개선할 수 있었습니다. 하지만 이로 인해 지금까지 작성한 모든 코드를 PostgreSQL에 맞춰 수정해야 했습니다.

PostGIS: PostgreSQL 데이터베이스에 지리 공간 데이터(GIS 데이터)를 저장하고 처리할 수 있는 기능을 추가해주는 확장 모듈입니다.

제가 위와 같은 단점을 고려했을 때 '쿼리에 비즈니스 로직 좀 들어갈 수 있지 뭐 어때' 라는 생각이 들 수도 있을 것 같습니다.

사실 저도 회사 프로젝트였다면, 절대 어길 수 없는 마감기한이 정해져 있었다면, '아키텍처적인 문제나 비즈니스 로직이 어디에 위치하는가?' 같은 문제는 넘겼을지도 모릅니다.

하지만 저는 사이드 프로젝트를 진행하는 중이었고, 더 나은 대안이 있는 상황에서 그냥 기능이 동작한다는 이유만으로 개선을 멈춘다면 발전이 없을 것이라고 생각했습니다.

그래서 더 나은 방법이라고 판단된 PostgreSQL을 사용하기로 했습니다.


⚙️ ST_Distance_Sphere(MariaDB) VS ST_DWithin(PostGIS)

특성ST_Distance_Sphere (MariaDB)ST_DWithin (PostGIS)
기능두 지점 간의 구면 거리 계산두 지점 간의 거리가 주어진 값 내에 있는지 확인
사용법ST_Distance_Sphere(point1, point2)ST_DWithin(geometry1, geometry2, distance)
반환 값두 지점 간의 구면 거리 (미터 단위)두 지점 간의 거리가 주어진 값 내에 있는지 여부 (boolean)
거리 계산 방식구면 거리 (지구를 구로 가정하여 거리 계산)평면 거리 또는 구면 거리 (SRID에 따라 다름)
정확성대략적인 구면 거리 계산SRID에 따라 정확한 거리 계산 가능
지원하는 형식점 (point) 형식점, 선, 다각형 등 다양한 형식 지원
예시ST_Distance_Sphere(Point(1, 1), Point(2, 2))ST_DWithin(geom1, geom2, 1000)
사용 가능한 SRID4326 (WGS 84)여러 SRID 지원
다른 조건과 결합일반적인 SELECT 문에서 거리 계산으로 사용WHERE 조건에서 주로 사용

속도개선 방식
개선 전 : Point <-> Point [경로(List) 개수 * 휴게소(List) 개수만큼 실행]
개선 후 : LineString <-> Point [휴게소(List)개수만큼 실행]

그리고 ST_DWithin (PostGIS)으로 변경한 후 알게 된 큰 장점이 있었습니다. 그것은 지원하는 형식의 차이였습니다.

속도 개선을 했을 때 Point To Point와 LineString to Point의 속도차이는 상상이었습니다. 만약 제가 ST_Distance_Sphere(MariaDB)를 사용했다면, MariaDB는 LineString을 지원하지 않기 때문에 속도 개선을 할 수 없었을 거에요.


결론

제가 만약 ST_Distance_Sphere(MariaDB)를 사용 후 "이중 For문 보다는 개선되었잖아, 이정도면 사용자가 쓸 수 있을 거야" 생각했다면, 추후 방향판단로직 등을 추가한 후 다시금 성능 향상의 필요성을 느끼고 Postgresql로의 변경을 위해 더 많은 시간을 사용했을거에요.

하지만 프로젝트 초기에 기술 스택에 대한 충분한 고민이 있었다면 지금보다 시간을 덜 썼을지도 모르죠.

그래서 다음에는 저도 이러한 실수를 하지 않고자, 그리고 이 글을 읽어주신 여러분의 시간도 아껴지길 바라면서.

사이드 프로젝트를 진행하면서 느꼈던 아쉬움과 생각들을 기록했습니다.
긴 글 읽어주셔서 감사합니다.👍

끝!

profile
Backend Developer

0개의 댓글