지도제작자의 고백: PostGIS가 나를 SQL 해커에서 공간 예술가로 바꾸다
Source: Dev.to
위에 제공된 소스 링크만으로는 번역할 본문 내용이 없습니다. 번역을 원하는 전체 텍스트(본문, 코드 블록 제외)를 제공해 주시면, 요청하신 대로 한국어로 번역해 드리겠습니다.
고백
수년 동안 나는 지리공간 데이터를 지저분한 옷장처럼 다루었다—모두를 억지로 넣고, 문을 꽝 닫고, 누군가가 “근처”라는 것을 물어보지 않기를 기도했다. 그러다 나를 무너뜨린 프로젝트가 찾아왔다: 50 k 포인트를 가진 실시간 배달 추적기와 순진한 쿼리:
WHERE sqrt((x1-x2)^2 + (y1-y2)^2)
하지만—아직도 모든 것을 스캔하나요? 맞다. 가장 중요한 부분을 잊었기 때문이다.
인덱싱 – 진정한 예술
일반적인 B‑tree 인덱스는 책장을 알파벳 순서대로 정리하는 것과 비슷합니다—title = X 같은 경우에 아주 좋죠. 하지만 공간 데이터는 지도와 같습니다. 페이지를 넘겨가며 찾는 것이 아니라 접고, 확대하고, 영역을 훑어보는 방식입니다.
GiST (Generalized Search Tree) 등장
GiST를 2‑D(또는 3‑D, 4‑D) 공간을 경계 상자들의 트리로 접어 넣는 종이접기 장인이라고 생각해 보세요. “1 km 이내의 점을 찾는다” 라고 질의하면 PostGIS가 인덱스를 활용해 전체 대륙 규모의 데이터를 즉시 제외합니다.
CREATE INDEX idx_restaurants_geom
ON restaurants
USING GIST (geom);
그 한 줄 덕분에 45초 걸리던 쿼리가 80 ms 로 단축되었습니다. 저는 정말 크게 웃었고, 고양이는 방을 떠났습니다.
Note: GiST 인덱스는 B‑tree에 비해 업데이트(INSERT/UPDATE/DELETE) 속도가 약간 느립니다. 쓰기가 많은 지리공간 테이블의 경우
autovacuum을 조정하거나 쓰기를 배치 처리해야 합니다. 자세한 내용은 나중에 다루겠습니다.
Art lesson: GiST 인덱스는 지도에 있는 범례와 같습니다—모든 나무를 보여주지는 않지만, 숲을 찾는 정확한 방법을 알려줍니다.
Source:
편리한 툴킷
PostGIS에는 수백 개의 함수가 포함되어 있습니다. 위험해지려면 열두 개만 있으면 됩니다. 아래는 실제 어려움을 겪으며 다듬은 일상적인 툴킷입니다.
| 원하는 작업 | 함수 | 아름다움의 이유 |
|---|---|---|
| 거리 필터 | ST_DWithin(geom1, geom2, radius) | 인덱스를 사용합니다. 언제나. WHERE 절에서 ST_Distance를 사용하지 마세요. |
| 정확한 교차 | ST_Intersects(geom1, geom2) | 경계, 겹침, 접촉을 모두 처리합니다. |
| 최근접 이웃 | geom ST_SetSRID(...) | 공간 인덱스의 “나이트 무브”—KNN을 사용합니다. |
| 폴리곤 면적 | ST_Area(geom::geography) | 제곱미터를 반환합니다. Geography 타입은 지구의 곡률을 고려합니다. |
| 위도/경도 → 지오메트리 변환 | ST_SetSRID(ST_MakePoint(lon, lat), 4326) | 경도가 먼저임을 기억하세요. 축이 뒤바뀐 경험 때문에 눈물 흘린 적이 있습니다. |
실제 예시
사용자와 가장 가까운 10개의 커피숍을 5 km 이내에서 거리 순으로 찾기.
SELECT name,
ST_Distance(geom, user_geom) AS dist
FROM coffee_shops
WHERE ST_DWithin(geom, user_geom, 5000)
ORDER BY geom user_geom -- KNN operator
LIMIT 10;
## Geometry vs. Geography
| 유형 | 설명 | 사용 시기 |
|------|------|----------|
| **Geometry** | 지구를 평평한 카르테시안 평면으로 간주합니다. 빠르고 간단합니다. | 몇 백 킬로미터 정도의 지역 프로젝트에 적합합니다. |
| **Geography** | 구형(구면) 모델(WGS‑84 기본)을 사용합니다. 전 세계 거리, 면적 및 방위에 대해 정확합니다. | 전 세계 거리 및 대규모 분석에 적합합니다. 다소 느립니다. |
**내 경험 법칙**
1. **`geometry`를 SRID 4326(위도/경도)으로 저장**합니다. 가볍습니다.
2. **`geography`로 변환**은 지구를 고려한 계산이 필요할 때만 수행합니다: `geom::geography`.
3. **두 타입 모두 인덱스** – `geography`에 대한 GiST 인덱스는 크기가 크고 약간 느리지만, 전 세계 쿼리에는 여전히 유용합니다.
**Pro tip:** 대규모 테이블에서 전 세계 쿼리를 수행할 경우, `geog` 컬럼을 `geography(Point, 4326)` 형태로 추가하고 인덱스합니다. 그러면 다음과 같이 깔끔한 쿼리를 작성할 수 있습니다:
```sql
SELECT *
FROM sensors
WHERE ST_DWithin(
geog,
ST_MakePoint(lon, lat)::geography,
50000 -- 50 km
);
```
쿼리에서 변환을 하지 않으면 인덱스가 주저 없이 사용됩니다.
## Silent Killers of Performance
모든 컬럼에 인덱스를 적용했음에도 불구하고 프로덕션 환경에서 성능 저하가 발생할 수 있습니다. 흔히 나타나는 원인들은 다음과 같습니다:
1. **`WHERE` 절에서의 암시적 캐스팅**
```sql
WHERE ST_DWithin(geom::geography, ...) -- cast before index lookup
```
캐스팅 때문에 전체 스캔이 발생합니다. PostGIS는 `geometry`에 대한 GiST 인덱스를 `geography` 쿼리에서 사용할 수 없기 때문입니다. 타입을 일관되게 유지하세요.
2. **필터링에 `ST_Distance` 사용**
```sql
WHERE ST_Distance(geom, point) < radius -- forces full scan
```
`ST_Distance`는 필터가 적용되기 전에 모든 행에 대해 거리를 계산하므로 전체 스캔을 유발합니다. 대신 `ST_DWithin`을 사용하세요.
3. **타입 캐스팅에 주의** – 인덱스를 조용히 우회할 수 있습니다.
4. **대용량 테이블의 경우 클러스터링** 또는 단순화된 엔벨로프(envelope) 저장을 고려하세요.
이러한 방법을 적용하면 지리공간 쿼리가 “내 노트북에서는 잘 되는데”에서 “꿈처럼 확장됩니다”로 바뀔 것입니다. 즐거운 매핑 되세요!
## 4 seconds to 200 ms
2년 동안 **PostGIS**와 씨름하면서 일종의 직관을 갖게 되었습니다. 마치 그림에서 음영을 보는 법을 배우는 것과 같습니다. 공간 쿼리를 작성하기 전에 확인하는 정신 체크리스트는 다음과 같습니다:
1. **Draw it first** – 화이트보드나 빠른 QGIS 창을 사용합니다. 경계 상자와 교차점을 시각화하면 몇 시간을 절약할 수 있습니다.
2. **Start with the index** – 인덱스가 무거운 작업을 담당한다고 가정하고 쿼리를 작성합니다. 일찍 필터링하고, 나중에 정제합니다.
3. **Test with a point** – 실행
```sql
EXPLAIN (ANALYZE, BUFFERS) <your_query>;
```
단일 좌표에 대해 실행합니다. “Seq Scan”이 보이면 인덱스가 사용되지 않은 것입니다.
4. **Think in meters, store in degrees** – 거리 계산에는 **geography**, 연산에는 **geometry**를 사용합니다. 명시적으로 캐스팅하세요.
5. **Batch your writes** – 1 M 행에 대한 GiST 인덱스 재구성은 몇 분이 걸립니다. 매 삽입마다가 아니라 매일 밤에 수행하세요.
PostGIS는 단순한 라이브러리가 아닙니다. 데이터를 보는 방식을 바꾸는 렌즈와 같습니다. 이제 “내 주변” 버튼, 배달 경로, 히트맵 하나하나가 성능 악몽이 아니라 해결 가능한 퍼즐이 됩니다.
`sqrt(lat^2 + lon^2)`에서 우아한 `ST_DWithin`과 GiST 인덱스로의 여정은 아이가 크레용으로 낙서를 하는 것과 모네의 작품 사이의 차이와 같습니다. 이제 브러시 스트로크를 익혔으니, 지도를 그려 보세요.
> 그리고 누군가 “다각형 안에 있는 모든 포인트를 찾을 수 있나요?”라고 물으면 – 미소를 짓고, 터미널을 열어, 속삭이세요: **“Watch this.”**