공간데이터 타입과 R-tree 구조

dyeon-dev·2025년 10월 29일
post-thumbnail

지도 기능을 구현하기 위해서 데이터베이스에 위도, 경도 좌표 저장이 필요했다.
효율적으로 좌표를 저장하기 위해서 "공간데이터" 타입을 설정해줄 수 있다.

공간데이터를 적용하기 전에, 공간데이터 타입의 종류와 데이터베이스에 따른 차이점 등을 알아보자.
R-tree 자료구조 기반으로 공간인덱싱이 활용이 되는데, 데이터베이스에 따라 실제 R-tree 기반의 종류도 조금씩 다르다. 이에 대해 더 자세히 살펴보자.

공간데이터 타입 종류

MySQL에서 제공하는 공간 데이터의 종류는 총 7가지이다.
단일 타입으로는 Point, LineString, Polygon 세 가지이다.

  • Point: 좌표 공간의 한 지점
  • LineString: 다수의 Point를 연결해주는 선분
  • Polygon: 다수의 선분이 연결되어 닫혀있는 상태

나머지 타입들은 이 세 가지 타입의 조합이다.

  • Geometry, Multipoint, Multilinestring, Multipolygon, Geometrycollection

TypeORM 공식문서를 참고해보면 MySQL, PostgreSQL 등 DB에서 공간데이터를 생성하는 방법으로 크게 두 가지가 나온다.
(먼저 공간 인덱스를 사용하는 열에 spatial: true 옵션을 사용하여 인덱스를 추가한다.)

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 없음: 좌표계 정보 없음, 단순 2D 좌표

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
}

⚙️ 내부 동작

  • geometry는 공간객체를 추상화된 타입으로 저장
  • srid: 4326을 지정하면, MySQL이 해당 좌표계를 기준으로 지구구면 연산(거리, 면적 등)을 수행
  • 인덱스는 단순 bounding box가 아니라, 좌표계 변환 + 공간 연산까지 고려

특징

  • 공간 객체의 모든 타입을 포괄하는 슈퍼 타입
  • 저장 형태: WKT(POINT(lon lat)) 또는 WKB(Binary)
  • 표현 가능 객체: Point, LineString, Polygon, MultiPoint 등 모든 공간 객체
  • 인덱스: R-Tree 기반 Spatial Index (GiST/Spatial) 좌표계 기반 공간 인덱싱

POINT / GEOMETRY 타입 핵심 차이 정리

GEOMETRY
 ├── POINT
 ├── LINESTRING
 ├── POLYGON
 └── MULTI* (복수형 버전)
비교 항목pointgeometry
데이터타입단일 Point만 저장Point, Line, Polygon 등 다양한 공간 객체
공간 인덱스단순 R-Tree (Bounding Box)고급 GiST / R-Tree 기반
공간 연산 함수 지원MBRContainsMBRWithin 등 단순 직사각형 기반, (ST_DistanceST_Within 일부만)ST_ContainsST_IntersectsST_Distance 등 모든 ST_* 함수 완전 지원
(정밀 공간 연산)
좌표계 변환불가능 (단순 숫자좌표)가능 (예: EPSG:3857 ↔ EPSG:4326)
사용 목적내부 좌표 계산, 단순 위치정렬지도/GIS용 실제 좌표계 기반 분석

어떤 상황에 어떤 데이터 타입이 적합할까?

상황적합한 타입
“유저가 마지막으로 클릭한 위치를 기록”point
“서울시 내 카페를 반경 2km 이내 검색”geometry + SRID=4326
“행정구역 폴리곤과 포함 여부 판정”geometry
“지도 렌더링 / 거리 계산 / 좌표 변환”geometry

하지만 어떤 데이터베이스를 사용하느냐에 따라 같은 point, geometry 타입이어도 내부 동작이 다르다.

  • MySQL에서는 POINT + SRID가 부분지원(메타데이터 수준)
  • PostGIS에서는 POINT + SRID가 완전한 공간객체 지원
항목MySQLPostGIS
POINT + SRIDSRID 저장만 가능, 좌표계 변환/지구거리 불가완전한 geometry 연산 지원
GEOMETRY(Point) + SRID동일하게 SRID 적용, 모든 공간연산 가능완전 동일 (내부적으로도 같은 구조)
권장 방식GIS 분석 목적이면 geometry(Point, srid)둘 다 가능

정리하자면

  • POINT + SRID는 단순히 좌표계 정보만 메타데이터로 포함한 버전
  • GEOMETRY(Point, SRID)는 GIS 전용 연산, 좌표계 변환, 거리 계산 등 고급 기능까지 가능한 완전한 공간객체

👉 “지도 서비스 수준의 공간 연산”을 한다면 geometry(Point, 4326)
👉 반면 “그냥 위치좌표만 저장”하는 경우라면 POINT SRID 4326으로 충분하다.

데이터베이스의 R-tree 구조

두 가지 타입은 공간 인덱스를 만들기 위해 R-Tree를 기반으로 동작한다.
하지만 그 구현 방식과 제한사항도 DB 엔진(MyISAM, InnoDB, PostGIS)에 따라 다르다.

DB 엔진POINT 인덱스 구조내부 알고리즘한계
MyISAM (MySQL)R-Tree전통적 R-TreeSRID/좌표계, 트랜잭션 미지원, 단순 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 variantR-Tree의 일반화 버전 (GiST)완전한 공간 연산, SRID/좌표계 지원

MyISAM (MySQL)

  • MySQL의 초기 공간 인덱스 엔진으로, 정통적인 R-Tree 구조를 사용한다.
  • 인덱스 노드에는 각 포인트나 도형을 감싸는 MBR(Minimum Bounding Rectangle, 최소경계사각형) 이 키로 저장된다.
  • 공간 연산(MBRContainsMBRIntersectsMBRWithin 등) 시 R-Tree 탐색 과정을 통해 MBR이 겹치는 후보군을 빠르게 필터링한다.
  • 인덱스는 다차원 분할 로직을 사용하며, 삽입/삭제 시 노드 분할(splitting)재삽입(reinsertion) 등의 전통적인 R-Tree 알고리즘을 수행한다.
  • 트랜잭션과 외래키 제약은 지원하지 않지만, 순수 공간 탐색 성능은 InnoDB보다 빠른 경우가 많다.

✏️ 내부 연산 과정
1. 쿼리(예: MBRContains()) 시, R-Tree 인덱스가 각 객체의 MBR을 계층적으로 탐색
2. MBR이 겹치는 후보군만 빠르게 반환
3. 필요 시 애플리케이션 레벨에서 추가적인 geometry 연산 수행

➡️ 즉, 진짜 R-Tree 구조를 구현한 MySQL의 고전적 공간 인덱스 엔진으로,
MBR 단위의 공간 필터링을 빠르게 수행하는 구조이다.

MySQL InnoDB의 Spatial Index

  • 이름은 “R-Tree 기반”이지만, 진짜 R-Tree는 아니다.
  • InnoDB의 페이지 구조는 기본적으로 B-Tree 입니다.
    그래서 공간 인덱스도 B-Tree 페이지를 확장해서 저장한다.
  • 이때 키로 쓰이는 건 MBR (Minimum Bounding Rectangle) 값이다.
  • 즉,
    "x_min, y_min, x_max, y_max" → 1차원 key로 변환해서 B-Tree에 저장

➡️ R-Tree의 개념(공간 분할, bounding box) 은 차용하지만,
알고리즘과 트리 구조 자체는 B-Tree이다.

✏️ 내부 연산 과정
1. 쿼리(예: ST_Within()) 시, 인덱스가 각 포인트의 MBR을 조회
2. 후보군을 MBR 기준으로 필터링
3. 그 다음 “정확한 계산” 없이 결과 반환 (MBR 기반만 수행)

즉, “Bounding box overlap check”까지만 하는 1단계 필터이다.

PostGIS GiST 인덱스

  • GiST(Generalized Search Tree)는 PostgreSQL이 제공하는 인덱스 프레임워크로,
    R-Tree, B-Tree, kNN, HNSW 등 다양한 인덱스를 일반화할 수 있다.
  • PostGIS는 이 GiST 프레임워크 위에 R-Tree-like 알고리즘을 구현했다.

✏️ GiST의 구조

  • 각 노드에 저장되는 값은 Bounding Box + Consistency Function
  • Consistency Function이란:
    “이 인덱스 항목이 쿼리 조건에 부합하는가?”를 판단하는 함수.
    (예: does overlap?is within?distance < threshold?)

⚙️ 두 단계 필터링

  1. Rough filter: Bounding box로 후보군을 찾음
    → R-Tree처럼 빠름
  2. Exact filter: 실제 geometry 연산 수행 (ST_WithinST_Intersects 등)

➡️ 즉, R-Tree의 공간 계층 구조 + 정확한 geometry 연산까지 포함된 진짜 공간 인덱스

MySQL InnoDB
 └── B-Tree Page → MBR(x_min, y_min, x_max, y_max)
      └── Record → WKB binary of geometry
      (정렬 기준: Bounding Box 좌표)

PostGIS GiST
 └── GiST Node → Bounding Box + Consistency Function
      ├── Leaf → geometry binary (w/ SRID)
      └── 연산 단계: Rough Filter → Exact Geometry Check

0개의 댓글