ClickHouse의 PostGIS 대체 가능성
📿

ClickHouse의 PostGIS 대체 가능성

ClickHouse 분류
Case Study
Type
Research
작성자

Ken

PostGIS는 25년에 걸쳐 만들어진 풀스택 vector GIS다. 그것을 다른 엔진으로 대체한다는 발상은 보통 무모하다 — 함수 커버리지만 보면. PostGIS의 약 80여 개 주요 함수 중 ClickHouse가 직접 대응하는 건 6%에 불과하다.

하지만 실제 프로덕션 분석 워크로드의 80% 이상이 그 6% 안에서 끝난다. ST_Distance, ST_DWithin, point 기준의 ST_Contains, 그리고 좌표 추출 — 모빌리티·IoT·광고테크·풋트래픽 분석이 매일 호출하는 것은 거의 다 이 작은 집합이다. 함수 매트릭스의 비대칭성이 이 글의 출발점이다.

이 글은 PostGIS 79개 주요 함수 전체 비교표를 그대로 보존한 뒤, 분석 워크로드를 PostGIS에서 ClickHouse로 옮기는 관점에서 — 어디가 1:1 replace 가능한지, 어디는 H3 패턴으로 재설계해서 replace 가능한지, 어디는 PostGIS를 남겨야 하는지 — 를 정리한다. pg_clickhouse는 fallback이 아니라 마이그레이션 경로로 다룬다: 기존 PostGIS SQL을 그대로 유지하면서 데이터가 ClickHouse로 이전되게 하는 도구 (이전 글 pg_clickhouse 깊이 보기 참고).

1. PostGIS를 대체하려는가 — 스케일의 함정

PostGIS 자체는 견고하다. 문제는 PostgreSQL의 행 기반 OLTP 엔진 위에 얹혀 있다는 것. 분석 워크로드가 커지면 다음 순서로 막힌다:

PostGIS 워크로드가 분석으로 가는 순간 row-store가 발목을 잡는다 — 5억 행 위에서 SELECT count(*) GROUP BY ST_GeoHash(geom)가 메모리에 다 안 들어가서 디스크 스필 → 분 단위 쿼리. 바로 이 지점이 ClickHouse가 들어올 자리다.

핵심 질문은 단순하다 — "내 PostGIS 워크로드 중 얼마만큼이 ClickHouse로 1:1 옮겨질 수 있고, 얼마만큼은 패턴을 재설계해야 하며, 얼마만큼은 PostGIS를 남겨야 하는가?" 다음 매트릭스가 그 답이다.

2. PostGIS ↔ ClickHouse 함수 매트릭스 (전체)

다음 표는 PostGIS의 주요 79개 함수에 대해 ClickHouse 대응 함수가 있는지 / 부분적인지 / 없는지를 카테고리별로 정리한 것. 이 표가 글의 가장 중요한 reference다.

범례:

  • Supported — 동등하거나 거의 동등한 ClickHouse 함수 존재 (직접 replace 가능)
  • ⚠️ Workaround / Point Only / WGS84 only — 제한 조건 하에 replace 가능 (조건 충족 시 OK)
  • Not Supported — 직접 대응 없음 (H3 패턴으로 재설계하거나 PostGIS 유지)

2.1 Distance & Measurement

PostGIS Function
ClickHouse Equivalent
Status
Description
Use Cases
ST_Distance
geoDistance(lon1, lat1, lon2, lat2)
✅ Supported
Calculates distance between two points in meters (WGS84)
Proximity search, delivery radius, nearest neighbor, store locator
ST_Length
greatCircleDistance()
✅ Supported
Calculates great circle distance between points
Flight paths, shipping routes, long-distance planning
ST_Area
(H3 approximation)
⚠️ Workaround
Calculates polygon area
Land parcel sizing, coverage analysis, territory calculations
ST_Perimeter
❌ Not Supported
Calculates polygon boundary length
Fence estimation, boundary surveying, coastal measurement
ST_Azimuth
❌ Not Supported
Returns bearing angle between two points
Navigation direction, compass heading, directional analysis
ST_Angle
❌ Not Supported
Returns angle between three points
Turn angle calculation, road geometry, path direction
ST_ClosestPoint
❌ Not Supported
Returns closest point on geometry to another
Nearest point on road, snapping, shortest path start
ST_ShortestLine
❌ Not Supported
Returns shortest line between geometries
Distance visualization, connecting features, gap analysis
ST_LongestLine
❌ Not Supported
Returns longest line between geometries
Maximum extent, polygon diameter, coverage span
ST_MaxDistance
❌ Not Supported
Returns maximum distance between geometries
Polygon diameter, maximum separation, extent calculation
ST_3DDistance
❌ Not Supported
Calculates 3D distance with elevation
Aviation, drone paths, 3D building analysis, terrain
ST_FrechetDistance
❌ Not Supported
Measures curve similarity
GPS track comparison, route similarity, trajectory matching
ST_HausdorffDistance
❌ Not Supported
Measures shape distance
Shape similarity, polygon matching, change detection

2.2 Spatial Relationships

PostGIS Function
ClickHouse Equivalent
Status
Description
Use Cases
ST_Contains
pointInPolygon((x,y), polygon)
⚠️ Point Only
Tests if geometry contains another
Geofencing, delivery zones, territory assignment
ST_Within
pointInPolygon((x,y), polygon)
⚠️ Point Only
Tests if geometry is within another
Service area check, indoor positioning, asset tracking
ST_Intersects
(H3/S2 approximation)
⚠️ Workaround
Tests if geometries share space
Route overlap, coverage analysis, network intersection
ST_Overlaps
❌ Not Supported
Tests if geometries overlap
Territory conflict, overlapping coverage, collision detection
ST_Touches
❌ Not Supported
Tests if geometries touch at boundary
Adjacent parcel detection, neighboring regions, boundary validation
ST_Equals
❌ Not Supported
Tests if geometries are equal
Duplicate detection, geometry comparison, data validation
ST_Disjoint
❌ Not Supported
Tests if geometries do not intersect
Exclusion zones, non-overlapping validation
ST_Crosses
❌ Not Supported
Tests if geometries cross each other
Road/river crossings, line intersection, path crossing
ST_Covers
❌ Not Supported
Tests if geometry covers another
Full coverage validation, containment with boundary
ST_CoveredBy
❌ Not Supported
Tests if geometry is covered by another
Subset validation, within with boundary
ST_DWithin
geoDistance() < X
✅ Supported
Tests if geometries are within distance
Proximity queries, nearby search, radius filtering
ST_Relate
❌ Not Supported
Returns DE-9IM intersection matrix
Complex spatial relationship analysis

2.3 Geometry Constructors

PostGIS Function
ClickHouse Equivalent
Status
Description
Use Cases
ST_MakePoint
(lon, lat) tuple
✅ Supported
Creates a point geometry
Creating point data, coordinate storage
ST_MakeLine
❌ Not Supported
Creates a line from points
Route creation, path building, track generation
ST_MakePolygon
❌ Not Supported
Creates polygon from shell and holes
Area definition, boundary creation, zone building
ST_MakeEnvelope
❌ Not Supported
Creates rectangular polygon from bounds
Bounding box, viewport rectangle, extent definition
ST_Collect
❌ Not Supported
Collects geometries into collection
Grouping geometries, multi-geometry creation
ST_BuildArea
❌ Not Supported
Creates area from linework
Polygon construction from lines, topology building

2.4 Geometry Accessors

PostGIS Function
ClickHouse Equivalent
Status
Description
Use Cases
ST_GeometryType
❌ Not Supported
Returns geometry type name
Type checking, data validation, conditional processing
ST_NumPoints
❌ Not Supported
Returns number of points
Complexity analysis, simplification decisions
ST_NumGeometries
❌ Not Supported
Returns number of geometries in collection
Multi-geometry counting, collection analysis
ST_NRings
❌ Not Supported
Returns number of rings in polygon
Hole counting, polygon complexity
ST_IsClosed
❌ Not Supported
Tests if geometry is closed
Ring validation, path closure check
ST_IsEmpty
❌ Not Supported
Tests if geometry is empty
Null geometry check, data validation
ST_IsValid
❌ Not Supported
Tests if geometry is valid
Data quality, topology validation, error detection
ST_Dimension
❌ Not Supported
Returns geometry dimension (0,1,2)
Type classification, dimensional analysis
ST_CoordDim
❌ Not Supported
Returns coordinate dimension (2,3,4)
2D/3D/4D detection, data structure analysis
ST_Boundary
❌ Not Supported
Returns geometry boundary
Edge extraction, outline generation
ST_Envelope
❌ Not Supported
Returns bounding box
Extent calculation, viewport, indexing
ST_BoundingDiagonal
❌ Not Supported
Returns bounding box diagonal
Extent diagonal, size estimation
ST_X / ST_Y
point.1, point.2
✅ Supported
Extracts X/Y coordinate from point
Coordinate extraction, data transformation
ST_Z / ST_M
❌ Not Supported
Extracts Z/M coordinate
Elevation extraction, measure value access

2.5 Format Conversion

PostGIS Function
ClickHouse Equivalent
Status
Description
Use Cases
ST_AsText (WKT)
❌ Not Supported
Converts geometry to WKT string
Data export, GIS interoperability, debugging
ST_GeomFromText
readWKTPolygon()
⚠️ Polygon Only
Creates geometry from WKT
Data import, boundary loading, GIS migration
ST_AsGeoJSON
❌ Not Supported
Converts to GeoJSON format
Web maps, API responses, Leaflet/Mapbox integration
ST_GeomFromGeoJSON
❌ Not Supported
Creates geometry from GeoJSON
Web API import, map editor outputs, user polygons
ST_AsBinary (WKB)
❌ Not Supported
Converts to Well-Known Binary
Binary storage, efficient transfer, database exchange
ST_GeomFromWKB
❌ Not Supported
Creates geometry from WKB
Binary import, database migration
ST_AsKML
❌ Not Supported
Converts to KML format
Google Earth export, KML applications
ST_AsGML
❌ Not Supported
Converts to GML format
OGC web services, enterprise GIS
ST_AsSVG
❌ Not Supported
Converts to SVG path
Web graphics, vector visualization
ST_SetSRID
(WGS84 only)
⚠️ WGS84 only
Sets coordinate reference system
Projection assignment, CRS management
ST_Transform
(WGS84 only)
⚠️ WGS84 only
Transforms between coordinate systems
Projection conversion, local CRS, survey data

2.6 Geometry Processing

PostGIS Function
ClickHouse Equivalent
Status
Description
Use Cases
ST_Centroid
❌ Not Supported
Calculates geometric center
Label placement, representative point, center icon
ST_Buffer
❌ Not Supported
Creates buffer around geometry
Safety zones, influence areas, setbacks, proximity buffers
ST_Union
❌ Not Supported
Combines geometries into one
Merging parcels, combining areas, dissolving boundaries
ST_Intersection
❌ Not Supported
Returns shared area of geometries
Overlap extraction, common area, clipping
ST_Difference
❌ Not Supported
Returns part of A not in B
Subtraction, exclusion zones, carve-out areas
ST_SymDifference
❌ Not Supported
Returns non-overlapping parts
XOR operation, exclusive areas, difference zones
ST_Simplify
❌ Not Supported
Reduces geometry complexity
Map generalization, data reduction, zoom optimization
ST_SimplifyPreserveTopology
❌ Not Supported
Simplifies while preserving topology
Safe simplification, valid output guaranteed
ST_ConvexHull
❌ Not Supported
Returns convex hull of geometry
Bounding shape, point cloud envelope, extent polygon
ST_ConcaveHull
❌ Not Supported
Returns concave hull of geometry
Tighter boundary, natural shape fitting
ST_OffsetCurve
❌ Not Supported
Creates parallel offset line
Road lanes, setback lines, buffer outlines
ST_VoronoiPolygons
❌ Not Supported
Creates Voronoi diagram
Nearest neighbor regions, coverage areas, tessellation
ST_DelaunayTriangles
❌ Not Supported
Creates Delaunay triangulation
TIN creation, mesh generation, terrain modeling
ST_Subdivide
❌ Not Supported
Splits geometry into smaller parts
Tile creation, parallel processing, chunking
ST_Split
❌ Not Supported
Splits geometry by another
Parcel division, line splitting, area partitioning
ST_Snap
❌ Not Supported
Snaps geometry to another
Topology correction, alignment, gap closing
ST_Reverse
❌ Not Supported
Reverses point order
Direction correction, winding order fix
ST_ForcePolygonCW
❌ Not Supported
Forces clockwise winding
Polygon orientation, standard compliance
ST_MakeValid
❌ Not Supported
Repairs invalid geometry
Data cleaning, topology fixing, error correction

2.7 Clustering

PostGIS Function
ClickHouse Equivalent
Status
Description
Use Cases
ST_ClusterDBSCAN
❌ Not Supported
Density-based clustering (DBSCAN)
Point clustering, hotspot detection, pattern discovery
ST_ClusterKMeans
❌ Not Supported
K-means clustering
Territory division, zone creation, grouping
ST_ClusterWithin
❌ Not Supported
Distance-based clustering
Proximity grouping, neighborhood clustering

2.8 Linear Referencing

PostGIS Function
ClickHouse Equivalent
Status
Description
Use Cases
ST_LineInterpolatePoint
❌ Not Supported
Returns point at fraction along line
Mile markers, progress points, route positions
ST_LineLocatePoint
❌ Not Supported
Returns fraction of closest point
Position on route, progress calculation, snapping
ST_LineSubstring
❌ Not Supported
Returns portion of line
Route segments, partial paths, subsections
ST_AddMeasure
❌ Not Supported
Adds measure values along line
Linear referencing setup, distance markers
ST_LocateAlong
❌ Not Supported
Returns points at measure value
Position lookup, measure-based queries

2.9 H3 Indexing (ClickHouse 전용 — PostGIS에는 별도 H3-pg 익스텐션 설치 필요)

ClickHouse Function
Description
Use Cases
geoToH3(lon, lat, resolution)
Converts coordinates to H3 index
Spatial aggregation, heatmaps, surge pricing, analytics
h3ToGeo(h3Index)
Converts H3 to center coordinates
Cell visualization, reverse geocoding, centroids
h3kRing(h3Index, k)
Returns cells within k rings
Nearby search, proximity analysis, catchment areas
h3ToGeoBoundary(h3Index)
Returns H3 cell boundary
Cell visualization, hexagon rendering, boundaries
h3GetResolution(h3Index)
Returns H3 resolution level
Data validation, multi-resolution analysis
h3ToParent(h3Index, res)
Returns parent cell at resolution
Hierarchical aggregation, zoom levels
h3ToChildren(h3Index, res)
Returns child cells at resolution
Drill-down, subdivision, detail levels
h3IndexesAreNeighbors()
Tests if cells are neighbors
Adjacency check, connectivity analysis
h3Line(start, end)
Returns cells along line
Path coverage, route cells, linear features
h3Distance(h3Index1, h3Index2)
Returns grid distance between cells
Cell distance, grid-based routing
h3HexAreaM2(resolution)
Returns cell area at resolution
Area calculations, density normalization

2.10 S2 Indexing (ClickHouse 전용)

ClickHouse Function
Description
Use Cases
geoToS2(lon, lat)
Converts coordinates to S2 index
Global indexing, Google Maps integration, range queries
s2ToGeo(s2Index)
Converts S2 to coordinates
S2 cell lookup, coordinate retrieval
s2RectContains(rect, s2Index)
Tests if cell in rectangle
Bounding box queries, viewport filtering
s2GetNeighbors(s2Index)
Returns neighboring S2 cells
Adjacency queries, neighbor lookup
s2CellsIntersect()
Tests if S2 cells intersect
Overlap detection, intersection queries
s2CapContains()
Tests if point in spherical cap
Radius queries on sphere, circular regions

2.11 ClickHouse Spherical Geo (구면 기반 폴리곤)

ClickHouse Function
Description
Use Cases
polygonAreaSpherical(polygon)
Calculates spherical polygon area
Geographic area on sphere, accurate area
polygonPerimeterSpherical(polygon)
Calculates spherical perimeter
Geographic perimeter, accurate boundary length
polygonsIntersectionSpherical()
Spherical polygon intersection
Geographic intersection on sphere
polygonsUnionSpherical()
Spherical polygon union
Geographic union on sphere

3. 매트릭스 재해석 — "6%의 함정"

표를 단순 카운트로 보면 PostGIS replace는 가망 없어 보인다.

카테고리
함수 수
✅ Direct
⚠️ 조건부
❌ 미지원
Distance & Measurement
13
2
1
10
Spatial Relationships
12
1
3
8
Geometry Constructors
6
1
0
5
Geometry Accessors
14
1
0
13
Format Conversion
11
0
3
8
Geometry Processing
19
0
0
19
Clustering
3
0
0
3
Linear Referencing
5
0
0
5
합계
83
5
7
71
+H3 / S2 / Spherical (CH 전용)
21

직접 replace 가능: 6%. 미지원: 86%. 이 수치만 보면 결론은 "PostGIS 못 대체한다"다.

그런데 이건 함정이다. 이유는 두 가지.

3.1 함수 분포 ≠ 호출 분포

프로덕션 분석 워크로드의 SQL을 모아 보면 호출 빈도가 극도로 편향된다:

ClickHouse가 직접 replace하는 5개 함수가 분석 워크로드 호출 빈도의 80%를 차지한다. 미지원 71개 대부분은 GIS 앱(지적도, 도시계획, surveying, cartographic generalization)에서 무거운 비중이지, 분석 대시보드에서는 가끔 손가락에 꼽힐 정도로 등장.

3.2 H3 패턴은 ❌ 함수 일부를 재설계해서 replace한다

미지원 71개 중에서도 H3/S2 패턴으로 의미적으로 동등하게 풀 수 있는 함수가 상당수다.

PostGIS 패턴
ClickHouse 재설계
노트
ST_Buffer(point, R) + ST_Contains
WHERE pickup_h3 IN h3kRing(center, k)
k = R / edgeLen, 정확도 trade-off
ST_ClusterDBSCAN(points, ε, minPts)
hex 카운트 + WHERE cnt > minPts
격자 기반 DBSCAN — 빠르고 노이즈 강건
ST_Centroid(polygon)
h3ToGeo(h3PolygonToCells(poly, res)) 평균
hex 중심 평균 — 근사값
ST_Area(polygon)
length(h3PolygonToCells) * h3HexAreaM2(res)
hex 합산 — res 높을수록 정확
ST_VoronoiPolygons(points)
hex 별로 nearest point 할당
격자 Voronoi — 실용적 근사
ST_Intersects(line, polygon)
h3Line(a,b)h3PolygonToCells(poly)
hex 셋 교집합
Continuous aggregates (Timescale)
AggregatingMergeTree + MV
패턴 자체가 더 강력

핵심 패턴 전환: vector geometry 기반에서 hex grid 기반으로 사고를 바꾸면, 미지원 함수의 큰 부분이 재설계로 replace 가능해진다. 게다가 hex 격자는 equal-area (H3) — 위도에 따라 면적이 변하지 않아서 PostGIS의 long-lat 평면 계산보다 정확도가 더 높은 경우도 많다.

3.3 결론 — Replace 가능성의 그라디언트

Tier 1 + Tier 2 — 즉 모빌리티·IoT·광고테크·풋트래픽 같은 분석 워크로드의 대다수는 ClickHouse로 완전히 옮길 수 있다. Tier 3는 남기는 게 정답이지만, 그 규모는 보통 원본 PostGIS의 작은 부분집합이다.

4. 마이그레이션 경로 — pg_clickhousereplace를 가능하게 한다

PostGIS replace의 진짜 어려움은 기능 매핑이 아니라 기존 SQL 자산을 한꺼번에 못 바꾼다는 것. 대시보드, ORM, 크론, 분석 노트북 등 PostGIS SQL이 박힌 곳이 수백~수천 곳에 흩어져 있고, 한 번에 다 재작성할 수 없다.

여기서 pg_clickhouse마이그레이션 도구로서 결정적인 역할을 한다.

Phase 3가 핵심이다. pg_clickhouse가 ClickHouse 테이블을 PG의 foreign table처럼 노출하면서 PostGIS SQL의 일부 함수(ST_Distance, ST_DWithin, point에 대한 ST_Contains, ST_X/ST_Y 등)는 번역돼서 ClickHouse에서 실행된다.

즉:

  • 데이터: ClickHouse에 있다 (스케일 문제 해결)
  • SQL: PostgreSQL 표준 그대로 (대시보드/ORM 코드 변경 0)
  • 실행: ClickHouse의 컬럼 엔진 + geo 함수가 처리 (분석 성능)

그 다음 Phase 4 — 우선순위 높은 무거운 쿼리부터 H3 MATERIALIZED 컬럼 + AggregatingMergeTree MV로 재설계하면서 진정한 ClickHouse 네이티브 패턴으로 옮긴다. 한 번에 다 바꿀 필요 없이 점진적으로.

이게 replace의 의미다 — fallback이 아니라 마이그레이션의 안전선. pg_clickhouse가 없으면 PostGIS replace는 "어느 날 모든 SQL을 갈아엎는" 위험한 빅뱅이 된다. pg_clickhouse가 있으면 "데이터부터 옮기고, 쿼리는 살아 있는 동안 점진적으로 네이티브화"하는 안전한 전환이 된다.

5. PostGIS를 ClickHouse로 옮긴 use case — Before / After

이 섹션은 실제 PostGIS에서 ClickHouse로 옮긴 후의 모습을 5가지 워크로드로 보여준다. 각각 PostGIS에서 어떻게 했는지ClickHouse로 옮긴 후 어떻게 되는지를 나란히.

Use Case 1 — 모빌리티 수요 히트맵 (5M+ 트립 GROUP BY)

Before (PostGIS):

-- 그리드 셀별 픽업 카운트
SELECT ST_GeoHash(pickup_geom, 7) AS cell,
       count(*)
FROM trips
WHERE pickup_ts >= now() - interval '7 days'
GROUP BY cell
ORDER BY count(*) DESC LIMIT 100;

문제: ST_GeoHash가 함수 호출이라 인덱스 활용 못 함 → 5M 행 풀스캔 + 셀 인코딩 5M회 → 수십 초~분.

After (ClickHouse, native):

CREATE TABLE trips (
    -- ...
    pickup_h3_r8 UInt64 MATERIALIZED geoToH3(pickup_lon, pickup_lat, 8)
) ENGINE = MergeTree
ORDER BY (pickup_h3_r8, pickup_ts, trip_id);

SELECT h3ToString(pickup_h3_r8) AS cell, count()
FROM trips
WHERE pickup_ts >= now() - INTERVAL 7 DAY
GROUP BY pickup_h3_r8
ORDER BY count() DESC LIMIT 100;

핵심 변화:

  • MATERIALIZED 컬럼 — INSERT 시점에 hex 변환, 매 쿼리마다 재계산 안 함
  • ORDER BY pickup_h3_r8 첫째 — 같은 hex의 트립이 디스크 인접 → sequential scan
  • 컬럼 압축 — UInt64 hex 값이 LZ4로 매우 잘 압축

500만 행 → top 100 hex가 수십 ms. PostGIS 대비 10–100배 latency 개선이 일반적.

Migration via pg_clickhouse (Phase 3):

-- 기존 SQL이 그대로 살아 있다 — foreign table에 점만 박혀 있고, ST_GeoHash 대신 hex 컬럼을 쓰는 정도만 변경
SELECT cell, count(*) FROM imported_trips
WHERE pickup_ts >= now() - interval '7 days'
GROUP BY cell ORDER BY count DESC LIMIT 100;

ClickHouse가 hex 집계 + count를 푸시다운으로 처리한다.

Use Case 2 — Geofencing at scale (수억 점 × N 폴리곤)

Before (PostGIS):

-- 100개 서비스 존 × 1억 GPS 핑 = 100억 ST_Contains
SELECT z.zone_id, count(*) AS pings
FROM gps_pings p
JOIN service_zones z ON ST_Contains(z.geom, p.geom)
WHERE p.ping_ts >= now() - interval '1 hour'
GROUP BY z.zone_id;

문제: GIST 인덱스가 bbox prefilter는 해주지만 정밀 ST_Contains는 매 행 winding 검사 → 핑 1억 × 평균 폴리곤 검사 수십회 → 수 시간.

After (ClickHouse, hybrid hex prefilter + pointInPolygon):

핵심 변화:

  • hex 집합 prefilter로 99%+ 행을 인덱스 단계에서 탈락
  • 경계 hex의 점만 pointInPolygon 정밀 검사
  • ClickHouse가 hex IN 필터를 ORDER BY 활용해 sequential scan으로 처리

수 시간 → 수십 초 수준의 개선이 흔하다.

좌표 순서 함정: geoToH3(lon, lat, res)pointInPolygon((lon, lat), poly)경도 먼저, h3PolygonToCells([(lat, lon)…], res)위도 먼저. 같은 ClickHouse 안에서 정반대 컨벤션이라 반드시 함수 시그니처 확인. h3PolygonToCells 는 unnamed tuple만 받으니 컬럼명이 있으면 arrayMap(p -> (p.1, p.2), polygon)로 strip.

Use Case 3 — Nearest neighbor 근접 검색

Before (PostGIS):

-- 사용자 위치 주변 1km 내 가까운 매장 10개
SELECT store_id,
       ST_Distance(geom, ST_MakePoint(:lon, :lat)::geography) AS dist
FROM stores
WHERE ST_DWithin(geom, ST_MakePoint(:lon, :lat)::geography, 1000)
ORDER BY geom <-> ST_MakePoint(:lon, :lat)
LIMIT 10;

PG에서 KNN GIST는 잘 동작하지만, 매장 수가 수백만 + 사용자 동시 검색이 많으면 GIST 인덱스 메모리·동시성 압박.

After (ClickHouse):

WITH :lon AS lon, :lat AS lat
SELECT store_id,
       geoDistance(lon, lat, store_lon, store_lat) AS dist
FROM stores
WHERE store_h3_r8 IN h3kRing(geoToH3(lon, lat, 8), 3)  -- 1차 후보 hex prefilter
ORDER BY dist ASC LIMIT 10;

핵심 변화:

  • h3kRing(center, k=3) = 37개 hex로 후보 제한 → 수백만 매장 중 인근 수십~수백 개만 거리 계산
  • geoDistance로 정확한 미터 거리 산출 (WGS84 ellipsoid)

수만 동시 검색에서도 ClickHouse는 수십 ms 일관성. read replica 늘릴 필요 없음.

Use Case 4 — 실시간 surge pricing (continuous aggregates 대체)

Before (PostGIS / Timescale):

-- Timescale continuous aggregate
CREATE MATERIALIZED VIEW demand_hourly
WITH (timescaledb.continuous) AS
SELECT time_bucket('1 hour', pickup_ts) AS bucket,
       ST_GeoHash(pickup_geom, 7) AS cell,
       count(*) AS pickups, sum(fare) AS revenue
FROM trips
GROUP BY bucket, cell;

문제: PostGIS와 결합한 continuous aggregate는 refresh 주기·invalidation·메모리 관리 모두 운영자가 챙겨야 함. cell 카디널리티가 높으면 refresh가 무거워진다.

After (ClickHouse, AggregatingMergeTree + MV):

핵심 변화:

  • INSERT 시점에 자동 집계 갱신 (refresh 정책 없음)
  • State() / Merge() 합산 보장 — 부분 집계도 정확
  • 대시보드 쿼리는 ~72k 행만 스캔 (원본 5M 대비)

운영 자동화 측면에서도 훨씬 단순하다. Timescale의 continuous aggregate refresh 정책을 운영해본 사람이면 이 부분 차이를 즉시 체감한다.

Use Case 5 — IoT/광고테크 지리 집계 (위치 임프레션)

Before (PostGIS):

-- 분당 위치 임프레션 × hex 집계
SELECT ST_GeoHash(geom, 8) AS cell,
       date_trunc('minute', impression_ts) AS minute,
       count(*) AS impressions,
       count(DISTINCT device_id) AS uniq_devices
FROM impressions
WHERE impression_ts >= now() - interval '1 hour'
GROUP BY cell, minute;

count(DISTINCT) + ST_GeoHash + 1억 행 → 분 단위 결과가 분 단위로 안 나옴.

After (ClickHouse):

SELECT h3ToString(geoToH3(lon, lat, 9)) AS cell,
       toStartOfMinute(impression_ts) AS minute,
       count() AS impressions,
       uniqExact(device_id) AS uniq_devices,        -- 정확
       -- 또는 uniqHLL12(device_id) — 더 빠른 근사
       sumIf(1, conversion = 1) AS conversions
FROM impressions
WHERE impression_ts >= now() - INTERVAL 1 HOUR
GROUP BY cell, minute;

핵심 변화:

  • uniqExact/uniqHLL12 같은 ClickHouse 네이티브 unique 추정기 — count(DISTINCT) 대비 수십 배 빠름
  • H3 인덱스가 GeoHash보다 equal-area이므로 위도 편향 없음
  • LowCardinality device_id 압축 효과까지

리테일 풋트래픽도 같은 패턴 — 매장 주변 hex k-ring 내 디바이스 카운트로 매장 방문 의도 측정.

6. Replace가 어려운 워크로드 — 어떻게 다룰 것인가

Tier 3 영역(geometry processing, 정밀 vector 작업)이 워크로드에 조금 있다면 그것 때문에 전체 마이그레이션을 포기할 필요는 없다. 세 가지 대응 옵션:

옵션
적용 시점
a. H3 패턴으로 우회
ST_Bufferh3kRing, ST_ClusterDBSCAN → hex 밀도, ST_Centroid → hex 중심 평균. 근사가 허용되는 분석 워크로드에 가장 흔함
b. 작은 PG instance 유지 + pg_clickhouse 양방향
정밀 geometry 처리는 PG에 남기고, 데이터는 ClickHouse가 들고, PG는 pg_clickhouse로 ClickHouse를 외부 테이블로 본다. 결과를 다시 ClickHouse로 적재할 수도 있음
c. 외부 도구 (오프라인)
지적도/도시계획 같은 풀스택 GIS는 ETL 단계에서 GeoPandas·Shapely 등으로 사전 가공 후 ClickHouse에 적재

가장 극단적인 케이스 — 정밀 vector 가공이 워크로드의 50% 이상 — 이라면 PostGIS replace 자체를 재고해야 한다. 하지만 분석 OLAP 맥락에서 그런 워크로드는 흔치 않다.

7. 운영 함정 — Replace 과정에서 알아둘 5가지

함정
영향
해결
좌표 순서 비일관
geoToH3(lon, lat) vs h3PointDistM(lat, lon) — 함수마다 다르다
매 함수 호출 시 시그니처 재확인. 컬럼명을 pickup_lon/pickup_lat처럼 명시적으로
Named tuple 거부
h3PolygonToCells는 unnamed Tuple(Float64, Float64)만 받음
arrayMap(p -> (p.1, p.2), polygon)으로 column 이름 strip
H3 계층 근사성
같은 raw 좌표의 r10 hex의 parent가 같은 좌표의 r8 hex와 다를 수 있음
가장 세밀한 해상도만 저장, 거친 해상도는 h3ToParent로 유도
WGS84 고정
비-WGS84 CRS는 미지원 (ST_Transform 없음)
적재 단계에서 WGS84로 통일. 비-WGS 필요 시 ETL에서 처리
MATERIALIZED 컬럼 backfill
기존 데이터에 새 H3 컬럼 추가하면 빈 컬럼이 남음
ALTER TABLE ... MATERIALIZE COLUMN으로 백필 (대용량은 시간 소요)

8. 마무리 — Replace는 가능하다, 단 그라디언트로

세 줄 요약:

  1. PostGIS 함수의 6%만이 ClickHouse에 직접 대응한다. 하지만 분석 워크로드 호출의 80%+가 그 6%에서 일어난다. 매트릭스의 비대칭이 replace 가능성의 핵심.
  2. H3 패턴으로 재설계 replace 가능한 영역(radius geofencing, DBSCAN 클러스터링, 버퍼/Voronoi 근사, 실시간 surge)이 또 한 층 있다. 사고를 벡터 geometry에서 hex grid로 옮기면 가능한 영역이 크게 늘어난다.
  3. pg_clickhouse가 마이그레이션의 안전선이다. 기존 PostGIS SQL을 그대로 유지하면서 데이터를 ClickHouse로 옮기고, 우선순위 높은 쿼리부터 네이티브 ClickHouse로 점진 재작성한다. 빅뱅 없는 replace가 가능한 이유.

PostGIS replace 결정을 그라디언트로 보면, 답이 명확해진다. 모빌리티·IoT·광고테크·풋트래픽 분석은 대부분 전면 replace 가능하다. 지적도·도시계획·GIS 앱은 PostGIS를 남긴다. 그 사이의 회색지대는 pg_clickhouse 하이브리드가 답이다. ClickHouse Geo는 PostGIS 대체 불가도 아니고 단순 보완재도 아니다 — 분석 워크로드에 한해, 점진적·안전하게 PostGIS를 대체할 수 있는 실용적 경로다.

참고 자료

소스 코드