에어비앤비 프로젝트에서 지도 기능을 구현하면서 위도·경도 좌표를 데이터베이스에 저장해야 했다. 단순히 FLOAT 두 컬럼으로 저장하는 방법도 있지만, 반경 검색이나 지도 영역 필터링처럼 공간 연산이 필요한 경우엔 공간 데이터 타입을 쓰는 것이 훨씬 효율적이다.
이 글에서는 MySQL이 제공하는 공간 데이터 타입의 종류, POINT와 GEOMETRY의 차이, 그리고 DB 엔진별 R-tree 구현 방식을 정리한다.
R-tree 자료구조 기반으로 공간인덱싱이 활용이 되는데, 데이터베이스에 따라 실제 R-tree 기반의 종류도 조금씩 다르다. 이에 대해 더 자세히 살펴보자.
공간데이터 타입 종류
MySQL이 제공하는 공간 데이터 타입은 총 7가지다. 단일 타입 3가지와, 이를 조합한 컬렉션 타입 4가지로 나뉜다.
단일 타입
| 타입 | 설명 |
|---|---|
POINT | 좌표 공간의 한 지점 |
LINESTRING | 다수의 Point를 연결한 선분 |
POLYGON | 닫혀 있는 다각형 |
컬렉션 타입
GEOMETRY,MULTIPOINT,MULTILINESTRING,MULTIPOLYGON,GEOMETRYCOLLECTION
나머지 타입들은 이 세 가지 타입의 조합이다. 이 중 GEOMETRY는 위 모든 타입을 포괄하는 슈퍼 타입이다.
POINT vs GEOMETRY — 무엇을 선택할까?
TypeORM 공식문서를 참고해보면 MySQL, PostgreSQL 등 DB에서 공간데이터를 생성하는 방법으로 크게 두 가지가 나온다.
(먼저 공간 인덱스를 사용하는 열에 spatial: true 옵션을 사용하여 인덱스를 추가한다.)
단순 Point와 geometry 타입이 있는데, 처음에 엔티티 생성할때는 Point로 생성했다.
그런데 공간데이터에 대해 좀 더 학습을 해보니 geometry이 공간 객체의 모든 타입을 포괄하는 슈퍼 타입으로 유연하게 다양한 함수와 객체를 제공한다는 것을 알게 되었다.
더 찾아본 결과, 나의 프로젝트 요구사항에 맞춰 지도 서비스를 진행하려면 geometry 타입이 더 적합하다는 것을 알게되었다.
그래도 일단 Point와 geometry 타입이 어떤 차이점이 있는지 살펴보자.
1. POINT 타입 (MySQL POINT 타입)
@Entity()
export class Thing {
@Column("point")
@Index({ spatial: true })
point: string;
}
⚙️ 내부 동작 및 특징
- 내부적으로 2차원 좌표 (x, y) 만 저장
- 인덱스는 R-Tree 기반(MyISAM) 또는 B-Tree-like Spatial Index(InnoDB)로 구현
ST_Distance(),MBRContains()등 Bounding Box 기반 연산만 지원- 저장 형태: POINT(x, y) 또는 WKB(Binary)
- 표현 가능 객체: 단일 점(Point)만 가능
- 좌표계(SRID) 정보 없음 — 단순 숫자 좌표
2. GEOMETRY 타입 + SRID (PostGIS 또는 MySQL Geometry 타입)
export interface Geometry {
type: "Point";
coordinates: [Number, Number];
}
@Entity()
export class Thing {
@Column("geometry", {
spatialFeatureType: "Point",
srid: 4326,
})
@Index({ spatial: true })
point: Geometry;
}
⚙️ 내부 동작 및 특징
- Point, LineString, Polygon 등 모든 공간 객체를 포괄하는 슈퍼 타입
geometry는 공간객체를 추상화된 타입으로 저장- 인덱스는 단순 bounding box가 아니라, 좌표계 변환 + 공간 연산까지 고려
srid: 4326지정를 지정하면 지구 구면 기준으로 거리·면적 등 지리 연산 수행ST_Contains,ST_Intersects,ST_Distance등 모든 ST_* 함수 완전 지원- 저장 형태: WKT(
POINT(lon lat)) 또는 WKB Binary
POINT / GEOMETRY 타입 핵심 차이 정리
타입 계층 구조
GEOMETRY
├── POINT
├── LINESTRING
├── POLYGON
└── MULTI* (복수형 버전)
| 비교 항목 | point | geometry |
|---|---|---|
| 데이터타입 | 단일 Point만 저장 | Point, Line, Polygon 등 다양한 공간 객체 |
| 공간 인덱스 | 단순 R-Tree (Bounding Box) | 고급 GiST / R-Tree 기반 |
| 공간 연산 함수 지원 | MBRContains, MBRWithin 등 단순 직사각형 기반, (ST_Distance, ST_Within 일부만) | ST_Contains, ST_Intersects, ST_Distance 등 모든 ST_* 함수 완전 지원 (정밀 공간 연산) |
| 좌표계 변환 | 불가능 (단순 숫자좌표) | 가능 (예: EPSG:3857 ↔ EPSG:4326) |
| 사용 목적 | 내부 좌표 계산, 단순 위치정렬 | 지도/GIS용 실제 좌표계 기반 분석 |
어떤 상황에 어떤 데이터 타입이 적합할까?
| 상황 | 적합한 타입 |
|---|---|
| “유저가 마지막으로 클릭한 위치를 기록” | point |
| “서울시 내 카페를 반경 2km 이내 검색” | geometry + SRID=4326 |
| “행정구역 폴리곤과 포함 여부 판정” | geometry |
| “지도 렌더링 / 거리 계산 / 좌표 변환” | geometry |
DB별 SRID 지원 차이
하지만 어떤 데이터베이스를 사용하느냐에 따라 같은 point, geometry 타입이어도 내부 동작이 다르다.
- MySQL에서는
POINT + SRID가 부분지원(메타데이터 수준) - PostGIS에서는
POINT + SRID가 완전한 공간객체 지원
| 항목 | MySQL | PostGIS |
|---|---|---|
POINT + SRID | SRID 저장만 가능, 좌표계 변환/지구거리 불가 | 완전한 geometry 연산 지원 |
GEOMETRY(Point) + SRID | 동일하게 SRID 적용, 모든 공간연산 가능 | 완전 동일 (내부적으로도 같은 구조) |
| 권장 방식 | GIS 분석 목적이면 geometry(Point, srid) | 둘 다 가능 |
정리하면,
POINT + SRID→ 좌표계 정보를 메타데이터로만 포함 (그냥 위치좌표만 저장하는 경우에 충분)GEOMETRY(Point, SRID)→ GIS 전용 연산, 좌표계 변환, 거리 계산까지 가능한 완전한 공간 객체 (지도 서비스 수준의 공간 연산하는 경우 사용)
DB 엔진별 R-tree 구조
두 가지 타입은 공간 인덱스를 만들기 위해 R-Tree를 기반으로 동작한다.
하지만 그 구현 방식과 제한사항도 DB 엔진(MyISAM, InnoDB, PostGIS)에 따라 다르다.
MyISAM (MySQL)
MySQL의 초기 공간 인덱스 엔진으로, 정통적인 R-Tree 구조를 사용한다.
- 인덱스 노드에는 각 포인트나 도형을 감싸는 MBR(Minimum Bounding Rectangle, 최소경계사각형) 이 키로 저장된다.
- 공간 연산(
MBRContains,MBRIntersects,MBRWithin등) 시 R-Tree 탐색 과정을 통해 MBR이 겹치는 후보군을 빠르게 필터링한다. - 삽입/삭제 시 노드 분할(splitting), 재삽입(reinsertion) 등의 전통적인 R-Tree 알고리즘을 수행한다.
- 트랜잭션과 외래키 제약은 지원하지 않지만, 순수 공간 탐색 성능은 InnoDB보다 빠른 경우가 많다.
⚙️ 내부 연산 과정
- 쿼리(예:
MBRContains()) 시, R-Tree 인덱스가 각 객체의 MBR을 계층적으로 탐색 - MBR이 겹치는 후보군만 빠르게 반환
- 필요 시 애플리케이션 레벨에서 추가적인 geometry 연산 수행
즉, 진짜 R-Tree 구조를 구현한 MySQL의 고전적 공간 인덱스 엔진으로, MBR 단위의 공간 필터링을 빠르게 수행하는 구조이다.
InnoDB (MySQL 5.7+)
이름은 “R-Tree 기반”이지만, 진짜 R-Tree는 아니다.
- InnoDB의 페이지 구조는 기본적으로 B-Tree 이며, 공간 인덱스도 B-Tree 페이지를 확장해 구현한다.
- 이때 키로 쓰이는 건 MBR (Minimum Bounding Rectangle) 값이다.
- MBR의
(x_min, y_min, x_max, y_max)값을 1차원 키로 변환해 B-Tree에 저장한다.
R-Tree의 개념(공간 분할, bounding box) 은 차용하지만, 알고리즘과 트리 구조 자체는 B-Tree이다.
⚙️ 내부 연산 과정
ST_Within()호출 시 인덱스가 각 포인트의 MBR 조회- MBR 기준으로 후보군 필터링
- Bounding Box overlap check까지만 수행 — 정밀 geometry 연산은 없음 (1단계 필터 수행)
PostGIS GiST 인덱스 (PostgreSQL)
GiST(Generalized Search Tree)는 PostgreSQL이 제공하는 인덱스 프레임워크로, R-tree를 포함한 다양한 인덱스를 일반화해 구현한다. PostGIS는 이 위에 R-tree-like 알고리즘을 올렸다.
⚙️ GiST의 구조
- 각 노드에
Bounding Box+ Consistency Function 저장 - Consistency Function: "이 인덱스 항목이 쿼리 조건에 부합하는가?"를 판단하는 함수 (
does overlap?,is within?,distance < threshold?)
2단계 필터링 과정
- Rough filter: Bounding box로 후보군을 찾음 → R-Tree처럼 빠름
- Exact filter: 실제 geometry 연산 수행 (
ST_Within,ST_Intersects등)
즉, R-Tree의 공간 계층 구조로 두 단계 필터링을 거쳐 정확한 geometry 연산까지 포함된 진짜 공간 인덱스이다.
엔진별 비교 요약
| DB 엔진 | POINT 인덱스 구조 | 내부 알고리즘 | 한계 |
|---|---|---|---|
| MyISAM (MySQL) | R-Tree | 전통적 R-Tree | SRID/좌표계, 트랜잭션 미지원, 단순 Bounding Box, InnoDB 대비 안정성 낮음 |
| InnoDB (MySQL 5.7+) | “R-Tree-like” (B-Tree + Spatial extension) | B-Tree 기반으로 구현된 공간인덱스 구조 (R-Tree와 유사한 구조로 최적화) | MBR(최소경계사각형) 단위 비교, 진짜 R-Tree는 아님 |
| PostGIS (PostgreSQL) | GiST 기반 R-Tree variant | R-Tree의 일반화 버전 (GiST) | 완전한 공간 연산, SRID/좌표계 지원 |
내부 저장 구조 요약
MySQL InnoDB
└── B-Tree Page
└── Key: MBR(x_min, y_min, x_max, y_max)
└── Record: WKB binary of geometry
PostGIS GiST
└── GiST Node
├── Bounding Box + Consistency Function
└── Leaf: geometry binary (w/ SRID)
└── 연산: Rough Filter → Exact Geometry Check
ADR — 공간 데이터 타입 선택
Context
에어비앤비 클론 프로젝트에서 숙소의 위도·경도 좌표를 저장하고, 지도 영역(Bounding Box) 기반의 숙소 재검색 기능을 구현해야 한다. 사용 DB는 MySQL(InnoDB)이다. 주요 요구사항은 다음과 같다.
- 지도 이동·확대 후 현재 화면 영역 내 숙소 재검색
- 향후 반경 검색(원형 범위), 행정구역 경계 기반 필터 등 확장 가능성 존재
ST_Contains,ST_MakeEnvelope등 공간 함수 사용 예정
Alternatives
| 옵션 | 장점 | 단점 |
|---|---|---|
FLOAT lat + FLOAT lng 분리 저장 | 구현 단순, 별도 학습 불필요 | 공간 인덱스 불가, 범위 검색 시 풀스캔 발생 |
POINT 타입 | 공간 인덱스 사용 가능 | SRID 없음, ST_* 함수 일부만 지원, 좌표계 변환 불가 |
GEOMETRY(Point, 4326) | 모든 ST_* 함수 지원, 좌표계 기반 정밀 연산 가능 | 설정 복잡도 소폭 증가 |
Decision
GEOMETRY(Point, SRID=4326) 타입을 채택한다.
POINT 타입은 단순 좌표 저장엔 충분하지만, ST_Contains처럼 지도 영역 기반 필터링에 필요한 함수들이 제한된다.
반면 GEOMETRY(Point, 4326)은 WGS84 좌표계를 기준으로 모든 공간 연산을 지원하며, 향후 반경 검색이나 폴리곤 기반 필터로 확장할 때도 타입 변경 없이 대응 가능하다.
백엔드 쿼리는 아래와 같은 방식으로 적용한다.
WHERE ST_Contains(
ST_MakeEnvelope(swLng, swLat, neLng, neLat, 4326),
coordinates
)
Consequences
- 지도 영역 기반 재검색, 반경 검색 등 모든 공간 연산을
ST_*함수로 일관되게 처리 가능 - 타입 변경 없이 기능 확장 가능 (Polygon 필터, 거리 계산 등)
- 공간 인덱스(R-tree 기반 Spatial Index)로 대규모 데이터에서도 검색 성능 유지
트레이드오프
- MySQL InnoDB의 공간 인덱스는 진짜 R-tree가 아닌 B-Tree 기반 MBR 확장으로, 정밀 geometry 연산은 인덱스 외부에서 수행됨
- 향후 더 정밀한 GIS 연산이 필요해지면 PostGIS(PostgreSQL)로의 전환을 고려해야 함