회사에서 PostGIS 도입을 고민하면서, 어떤 이슈가 발생할지 모르기 때문에 행정구역 경계 값을 TEXT로 처리하여 JSON 형태로 변환 후 Elasticsearch의 geo-bounding box query로 조회하는 방안을 계획했습니다. 하지만 elasticsearch도 가기 전에, 행정구역 경계 값을 TEXT로 저장하니 간단한 SELECT 문임에도 불구하고 조회 시간이 무려 11초가 걸렸습니다. 전체 데이터 개수가 5,300개 정도밖에 안 되는데도 말이죠. 게다가 테스트 환경이 AWS RDS에서 나쁘지 않은 등급이었음에도 불구하고 이 정도로 속도가 안 나올 줄은 상상도 하지 못했어요. 하긴 저장된 text 경계 값이 평균 2 ~ 3.5 MB 정도 되더라고요 😂
PostGIS vs TEXT: 테이블 설계 및 단순 SELECT 조회 비교
TEXT 방식과 PostGIS 방식의 성능을 비교하기 위해 각각의 테이블을 생성했습니다.
CREATE TABLE geo_bound_text (
region_cd VARCHAR(30) primary key,
bbox Text,
geo Text
);
CREATE TABLE geo_bound_gis (
region_cd VARCHAR(30) primary key,
bbox Text,
geo GEOMETRY(MultiPolygon, 4326) -- GeoJSON 데이터를 MultiPolygon으로 변환하여 저장
);
단순 SELECT 조회 속도를 비교해 보았습니다.
select * from geo_bound_gis gbg;
-- Seq Scan on geo_bound_gis gbg (cost=0.00..1906.41 rows=5141 width=17331) (actual time=0.008..1.003 rows=5265 loops=1)
-- Planning Time: 0.031 ms
-- Execution Time: 1.168 ms
select * from geo_bound_text gbt;
-- Seq Scan on geo_bound_text gbt (cost=0.00..177.44 rows=5144 width=163) (actual time=0.012..0.544 rows=5265 loops=1)
-- Planning Time: 0.061 ms
-- Execution Time: 0.767 ms
단순한 전체 데이터 SELECT 조회 속도만 보면, TEXT 저장 방식이 오히려 더 빠른 것처럼 보였습니다. 하지만 공간 연산이 포함된 조회에서는 완전히 다른 결과가 나옵니다.
공간 데이터를 다루는 목적이라면 단순 SELECT보다는 공간 연산을 활용한 조회 성능이 중요해요. 예를 들어, 특정 좌표가 어느 행정구역 내에 포함되는지 확인하는 ST_Contains 함수를 사용해 보았어요.
SELECT *
FROM
geo_bound_gis
WHERE
ST_Contains(geo, ST_GeomFromText('POINT(127.0 37.5)', 4326));
-- Gather (cost=1000.00..40686.81 rows=1 width=17331) (actual time=5.638..65.989 rows=3 loops=1)
-- Workers Planned: 1
-- Workers Launched: 1
-- -> Parallel Seq Scan on geo_bound_gis (cost=0.00..39686.71 rows=1 width=17331) (actual time=26.649..55.813 rows=2 loops=2)
-- " Filter: st_contains(geo, '0101000020E61000000000000000C05F400000000000C04240'::geometry)"
-- Rows Removed by Filter: 2631
-- Planning Time: 0.216 ms
-- Execution Time: 66.010 ms
TEXT로 저장된 데이터를 동일한 조건으로 조회하려면 JSON을 GEOMETRY로 변환하는 과정이 추가됩니다.
SELECT *
FROM
geo_bound_text
WHERE
ST_Contains(ST_GeomFromGeoJSON(geo), ST_GeomFromText('POINT(127.0 37.5)', 4326));
-- Seq Scan on geo_bound_text (cost=0.00..67692.44 rows=1 width=163) (actual time=276.251..3788.527 rows=3 loops=1)
-- " Filter: st_contains(st_geomfromgeojson(geo), '0101000020E61000000000000000C05F400000000000C04240'::geometry)"
-- Rows Removed by Filter: 5262
-- Planning Time: 0.082 ms
-- Execution Time: 3788.608 ms
공간 연산에서는 GEOMETRY 저장 방식이 훨씬 좋은 성능을 가지는 것을 확인할 수 있어요. 무려 50배 이상이 차이가 나네요. 그렇다면 왜 이렇게 확연한 차이가 날까요? 🤔
GEOMETRY 타입은 내부적으로 압축된 바이너리 형식으로 저장되어 크기가 상대적으로 작습니다. 또한 공간 인덱스(GIST, SP-GIST)를 활용하여 빠르게 조회가 가능하죠. 또한 ST_Contains, ST-Intersects 같은 공간 연산에 대해 최적화되어 있습니다. 반면에 TEXT 저장 방식은 공간 데이터로 사용하려면 문자열 파싱이 필요하고, 공간 인덱스를 사용하지 못하죠. 그렇기 때문에 JSON 형태로 elasticsearch에서 조회하는 방식으로 사용하는 게 유리합니다.
Elasticsearch와 DB 조회 성능 비교
CREATE TABLE location_obj
(
lat double precision,
lng double precision,
location_nm varchar,
location_cd varchar
);
행정구역 바운드에 해당 테이블이 몇 개 포함되는지 확인하는 테스트를 진행했습니다. 하나는 PostGIS를 사용하여 location_obj 테이블의 데이터를 카운팅 하고, 다른 하나는 geo_bound_text에서 행정구역 코드와 바운드를 가져와 Elasticsearch에 있는 location_obj 인덱스의 데이터를 조회하여 몇 개가 포함되는지 비교했습니다.
1. DB(PostGIS)에서 공간 쿼리 실행
select
gbg.region_cd,
(select
count(1)
from
location_obj lo
where
st_contains(gbg.geo, st_setsrid(st_makepoint(lo.lng, lo.lat), 4326))) AS cnt
FROM
geo_bound_gis gbg
-- Execution Time: 933.603 ms
2. Elasticsearch에서 geo_shape query 실행
{
"query": {
"bool": {
"filter": {
"geo_shape": {
"location": {
"shape": {
"type": "polygon",
"coordinates": [[[126.5, 38.0], [127.5, 38.0], [127.5, 37.0], .... , [126.5, 37.0], [126.5, 38.0]]]
},
"relation": "within"
}
}
}
}
}
}
-- Execution Time: 180 ms
PostGIS는 복잡한 공간 연산을 수행하는 데 최적화되어 있어 공간 인덱스를 활용한 빠른 계산이 가능하며, 정확한 경계 판별이 필요할 때 강력한 성능을 발휘하지만, 대량의 데이터를 실시간으로 검색하기에는 부담이 큽니다. 반면 Elasticsearch는 geo_shape 쿼리를 활용하여 대량의 위치 데이터를 빠르게 필터링할 수 있으며, 특정 영역 내 검색 성능이 우수하지만, 정확한 공간 연산보다는 범위 기반 검색에 강점이 있습니다. 따라서 실시간 위치 검색이 필요한 경우 Elasticsearch를 활용하고, 정확한 공간 분석이 필요한 경우 PostGIS를 사용하는 하이브리드 접근 방식이 가장 적절한 해결책이 될 수 있습니다.
단순 조회만 보면 TEXT 방식이 빠를 수 있지만, 공간 연산이 포함된 경우 PostGIS가 압도적인 성능을 보이며, Elasticsearch는 빠른 검색을 위해 적절히 활용될 수 있습니다. 따라서 시스템의 요구사항에 따라 PostGIS와 Elasticsearch를 적절히 조합하여 사용하는 것이 최적의 전략이 될 것입니다.
저 같은 경우에는 postgis만 사용하거나 elasticsearch만 사용하거나 둘 중에 하나를 해야 할 거 같네요. TEXT를 조회하는 것 자체가 매우 속도가 느리기 때문에, elasticsearch로 바운드를 조회하고 location_obj 인덱스에 조건을 달아서 조회를 하거나, 아예 한 쿼리로 조회를 하는 방식으로 가야 할 거 같네요 🤓
'RDMS > PostgreSQL' 카테고리의 다른 글
[PostgreSQL] PostGIS 설치 및 사용 - 컴도리돌이 (3) | 2025.01.05 |
---|---|
[PostgreSQL] 텍스트 검색 최적화: phraseto_tsquery - 컴도리돌이 (6) | 2024.09.07 |
[PostgreSQL] 제약조건 설정시 주의해야할 부분을 고려하면서(PRIMARY KEY, NOT NULL, UNIQUE, CHECK) - 컴도리돌이 (8) | 2024.08.30 |
[PostgreSQL] 제약조건에 대해서(PRIMARY KEY, UNIQUE, NOT NULL, CHECK) - 컴도리돌이 (0) | 2024.08.29 |
[PostgreSQL] 해시 인덱스(Hash Index)에 대해서 - 컴도리돌이 (0) | 2024.08.28 |