Elasticsearch를 직접 체험해 보세요. Elasticsearch Labs 리포지토리의 샘플 노트북을 살펴보거나, 무료 클라우드 체험을 시작해 보세요. 지금 바로 로컬 컴퓨터에서 Elastic을 사용해 보셔도 좋습니다.
Elasticsearch는 수년 동안 강력한 위치 기반 정보 검색 및 분석 기능을 제공해 왔지만, API는 일반적인 GIS 사용자들에게 익숙한 것과는 상당히 달랐습니다. 작년에 저희는 SQL만큼 쉽거나 그보다 더 쉬운 파이프라인 쿼리 언어인 ES|QL 쿼리 언어를 추가했습니다. 특히 Elastic이 탁월한 검색, 보안 및 통합 가시성 사용 사례에 적합합니다. 또한 ES|QL 내에서 지리공간 검색 및 분석에 대한 지원을 추가하여 특히 SQL 또는 GIS 커뮤니티에서 온 사용자들이 훨씬 쉽게 사용할 수 있도록 하고 있습니다.
Elasticsearch 8.12와 8.13은 ES|QL에 위치 기반 정보 유형에 대한 기본 지원을 도입했습니다. 이 기능은 8.14에서 지리공간 검색 기능이 추가되면서 크게 향상되었습니다. 무엇보다도 이 지원은 PostGIS와 같은 다른 공간 데이터베이스에서 사용하는 오픈 지리공간 컨소시엄(OGC) 의 간단한 기능 액세스 표준을 밀접하게 준수하도록 설계되어 이러한 표준에 익숙한 GIS 전문가가 훨씬 쉽게 사용할 수 있습니다.
이 블로그에서는 ES|QL을 사용하여 위치 기반 정보 검색을 수행하는 방법과 SQL 및 Query DSL과 비교하는 방법을 보여드리겠습니다. 또한 ES|QL을 사용하여 공간 조인을 수행하는 방법과 Kibana Maps에서 결과를 시각화하는 방법도 보여드리겠습니다. 여기에 설명된 모든 기능은 "기술 미리보기" 에서 확인할 수 있으며, 개선 방법에 대한 피드백을 보내주시면 감사하겠습니다.
지리공간 데이터 검색
쿼리 예제부터 시작하겠습니다:
이것은 싼야 피닉스 국제공항(SYX) 주변의 직사각형 검색 다각형과 교차하는 모든 도시 경계 다각형을 검색합니다.

공항, 도시 및 도시 경계로 구성된 샘플 데이터 세트에서 이 검색은 교차하는 다각형을 찾아 일치하는 문서에서 원하는 필드를 반환합니다:
| abbrev | 공항 | 지역 | 도시 | 도시_위치 |
|---|---|---|---|---|
| SYX | 산야 피닉스 인터내셔널 | 天涯区 | Sanya | point(109.5036 18.2533) |
쉬웠습니다! 이제 동일한 쿼리에 대한 기존 Elasticsearch 쿼리 DSL과 비교해 보세요:
두 쿼리 모두 의도가 상당히 명확하지만 ES|QL 쿼리는 SQL과 매우 유사합니다. PostGIS에서 동일한 쿼리는 다음과 같습니다:
ES|QL 예제를 다시 살펴보세요. 비슷하지 않나요?
Elasticsearch API의 기존 사용자들은 ES|QL이 훨씬 더 사용하기 쉽다는 것을 알게 되었습니다. 이제 기존 SQL 사용자, 특히 공간 SQL 사용자는 ES|QL이 익숙한 것과 매우 유사하게 느껴질 것으로 예상합니다.
SQL은 왜 안 될까요?
Elasticsearch SQL은 어떻습니까? 오래 전부터 사용되어 왔으며 몇 가지 지리 공간적 특징을 가지고 있습니다. 그러나 Elasticsearch SQL은 원래 쿼리 API 위에 래퍼로 작성되었기 때문에 원래 API로 변환할 수 있는 쿼리만 지원되었습니다. ES|QL에는 이러한 제한이 없습니다. 완전히 새로운 스택이기 때문에 SQL에서는 불가능했던 많은 최적화가 가능합니다. 벤치마크 결과 ES|QL은 특히 집계에서 쿼리 API보다 매우 빠른 경우가 많습니다!

SQL과의 차이점
이전 예제에서 보듯이 ES|QL은 SQL과 다소 유사하지만 몇 가지 중요한 차이점이 있습니다. 예를 들어 ES|QL은 파이프 쿼리 언어로, FROM과 같은 소스 명령으로 시작한 다음 모든 후속 명령을 파이프 | 문자로 연결합니다. 이렇게 하면 각 명령이 데이터 테이블을 수신하고 해당 테이블에서 필터링( WHERE), 열 추가( EVAL), 집계 수행( STATS) 등의 작업을 수행하는 방식을 매우 쉽게 이해할 수 있습니다. SELECT 로 시작하여 최종 출력 열을 정의하는 대신 하나 이상의 KEEP 명령이 있을 수 있으며, 마지막 명령은 최종 출력 결과를 지정합니다. 이 구조는 쿼리에 대한 추론을 단순화합니다.
위의 예제에서 WHERE 명령에 초점을 맞추면 PostGIS 예제와 매우 유사하다는 것을 알 수 있습니다:
ES|QL
PostGIS
문자열 따옴표 문자의 차이 외에도 가장 큰 차이점은 문자열을 공간 유형으로 타입 변환하는 방식에 있습니다. PostGIS에서는 ::geometry 접미사를 사용하고, ES|QL에서는 ::geo_shape 접미사를 사용합니다. 이는 ES|QL이 Elasticsearch 내에서 실행되고 유형 캐스팅 연산자 :: 를 사용하여 문자열을 지원되는 ES|QL 유형(이 경우 geo_shape)으로 변환할 수 있기 때문입니다. 또한 Elasticsearch의 geo_shape 및 geo_point 유형은 WGS84로 알려진 공간 좌표계를 의미하며, 더 일반적으로 SRID 번호 4326을 사용하여 참조합니다. PostGIS에서는 명시적이어야 하므로 WKT 문자열에 SRID=4326; 접두사를 사용해야 합니다. 이 접두사를 제거하면 SRID는 0으로 설정되며, 이는 특정 좌표계에 연결되지 않는 Elasticsearch 유형 cartesian_point 및 cartesian_shape 과 유사합니다.
ES|QL과 PostGIS 모두 유형 변환 함수 구문도 제공합니다:
ES|QL
PostGIS
OGC 기능
Elasticsearch 8.14에는 다음과 같은 네 가지 OGC 공간 검색 기능이 도입되었습니다:
| ES|QL | PostGIS | 설명 |
|---|---|---|
| ST_INTERSECTS | ST_Intersects | 두 지오메트리가 교차하면 참을 반환하고 그렇지 않으면 거짓을 반환합니다. |
| ST_DISJOINT | ST_Disjoint | 두 지오메트리가 교차하지 않으면 참을 반환하고, 그렇지 않으면 거짓을 반환합니다. ST_INTERSECTS의 역수입니다. |
| ST_CONTAINS | ST_Contains | 하나의 지오메트리에 다른 지오메트리가 포함되어 있으면 참을 반환하고, 그렇지 않으면 거짓을 반환합니다. |
| ST_WITHIN | ST_Within | 하나의 지오메트리가 다른 지오메트리 안에 있으면 참을 반환하고, 그렇지 않으면 거짓을 반환합니다. ST_CONTAINS의 역수입니다. |
이러한 함수는 PostGIS와 유사하게 작동하며 동일한 방식으로 사용됩니다. 예를 들어 ST_INTERSECTS 은 두 지오메트리가 교차하면 참을 반환하고 그렇지 않으면 거짓을 반환합니다. 위 표의 문서 링크를 따라가 보면 모든 ES|QL 예제는 FROM 절 뒤에 WHERE 절 안에 있는 반면, 모든 PostGIS 예제는 리터럴 지오메트리를 사용하고 있음을 알 수 있습니다. 실제로 두 플랫폼 모두 쿼리의 모든 부분에서 해당 함수를 사용할 수 있도록 지원합니다.
ST_INTERSECTS 에 대한 PostGIS 설명서의 첫 번째 예는 다음과 같습니다:
이에 해당하는 ES|QL은 다음과 같습니다:
PostGIS 예제에서 SRID를 지정하지 않은 점에 유의하세요. 이는 PostGIS에서 geometry 유형을 사용할 때 모든 계산이 평면 좌표계에서 수행되므로 두 도형의 SRID가 같으면 SRID가 무엇이든 상관없기 때문입니다. Elasticsearch에서는 대부분의 함수에서도 마찬가지이지만, 다음 블로그에서 공간 거리 검색에 대해 살펴보겠지만 geo_shape 및 geo_point 에서 구형 계산을 사용하는 예외가 있습니다.
ES|QL 다용도성
위에서 WHERE 절과 ROW 명령에서 공간 함수를 사용하는 예제를 살펴봤습니다. 그 외에는 어디에 의미가 있을까요? 매우 유용한 곳 중 하나는 EVAL 명령어입니다. 이 명령을 사용하면 표현식을 평가하고 결과를 반환할 수 있습니다. 예를 들어 국가 이름으로 그룹화된 모든 공항의 중심이 국가를 나타내는 경계선 내에 있는지 확인해 보겠습니다:
결과는 예상대로 영국 공항의 중심이 영국 경계 내에 있고 아이슬란드 경계 내에 있지 않으며 그 반대의 경우도 마찬가지입니다:
| 중심 | 개수 | in_uk | in_iceland | within_uk | within_iceland |
|---|---|---|---|---|---|
| 포인트 (-21.946634463965893 64.13187285885215) | 1 | false | true | false | true |
| 포인트 (-2.597342072712148 54.33551226578214) | 17 | true | false | true | false |
| POINT (0.04453958108176276 23.74658354606057) | 873 | false | false | false | false |
실제로 이러한 함수는 서명이 의미가 있는 쿼리의 모든 부분에서 사용할 수 있습니다. 이 함수는 모두 리터럴 공간 객체 또는 공간 유형의 필드인 두 개의 인수를 받으며, 모두 부울 값을 반환합니다. 한 가지 중요한 고려 사항은 지오메트리의 좌표 참조 시스템(CRS)이 일치해야 하며, 그렇지 않으면 오류가 반환된다는 것입니다. 즉, 동일한 함수 호출에서 geo_shape 유형과 cartesian_shape 유형을 혼합할 수 없습니다. 그러나 geo_point 유형은 geo_shape 유형의 특수한 경우이며 둘 다 동일한 좌표 참조 시스템을 공유하므로 geo_point 유형과 geo_shape 유형을 혼합하여 사용할 수 있습니다. 위에 정의된 각 함수에 대한 문서에는 지원되는 유형 조합이 나열되어 있습니다.
또한 인수는 공간 리터럴 또는 필드 중 어느 것이든 순서와 상관없이 사용할 수 있습니다. 두 개의 필드, 두 개의 리터럴, 필드와 리터럴 또는 리터럴과 필드를 지정할 수도 있습니다. 유일한 요구 사항은 유형이 호환되어야 한다는 것입니다. 예를 들어, 이 쿼리는 동일한 인덱스에 있는 두 필드를 비교합니다:
이 쿼리는 기본적으로 도시 위치가 도시 경계 내에 있는지 묻는데, 일반적으로는 사실이어야 하지만 항상 예외가 있습니다:
| 카디널리티 | 개수 | in_city |
|---|---|---|
| few | 29 | false |
| 많은 | 740 | true |
훨씬 더 흥미로운 질문은 공항의 위치가 해당 공항이 서비스를 제공하는 도시의 경계 내에 있는지 여부입니다. 그러나 공항 위치는 도시 경계를 포함하는 인덱스와 다른 인덱스에 있습니다. 이를 위해서는 이 두 개의 개별 인덱스에서 데이터를 효과적으로 쿼리하고 상호 연관시킬 수 있는 방법이 필요합니다.
공간 조인
ES|QL은 JOIN 명령을 지원하지 않지만 SQL의 '왼쪽 조인'과 유사하게 작동하는 ENRICH 명령을 사용하여 특수한 경우의 조인을 수행할 수 있습니다. 이 명령은 SQL의 'Left 조인'과 유사하게 작동하며, 두 데이터 집합 간의 공간적 관계를 기반으로 한 인덱스의 결과를 다른 인덱스의 데이터로 보강할 수 있습니다.
예를 들어 공항 위치가 포함된 도시 경계를 찾아서 공항이 취항하는 도시에 대한 추가 정보로 공항 테이블의 결과를 보강한 다음 결과에 대한 몇 가지 통계를 수행해 보겠습니다:
그러면 공항이 가장 많은 상위 5개 지역과 해당 지역과 일치하는 모든 공항의 중심점, 해당 지역 내 도시 경계를 나타내는 WKT의 길이 범위가 반환됩니다:
| 중심 | 개수 | min_wkt | max_wkt | 지역 |
|---|---|---|---|---|
| POINT (-32.56093470960719 32.598117914802714) | 90 | 207 | 207 | null |
| 포인트 (-73.94515332765877 40.70366442203522) | 9 | 438 | 438 | 뉴욕시 |
| 포인트 (-83.10398317873478 42.300230911932886) | 9 | 473 | 473 | 디트로이트 |
| POINT (-156.3020245861262 20.176383580081165) | 5 | 307 | 803 | 하와이 |
| 포인트 (-73.88902732171118 45.57078813901171) | 4 | 837 | 837 | 몬트리올 |
그렇다면 여기서 실제로 무슨 일이 일어났을까요? JOIN 은 어디에서 발생했나요? 쿼리의 핵심은 ENRICH 명령어에 있습니다:
이 명령은 airports 인덱스에서 검색된 결과를 보강하고 원본 인덱스의 city_location 필드와 앞서 몇 가지 예제에서 사용한 airport_city_boundaries 인덱스의 city_boundary 필드 간에 intersects 조인을 수행하도록 Elasticsearch에 지시합니다. 그러나 이 정보 중 일부는 이 쿼리에서 명확하게 표시되지 않습니다. 우리가 볼 수 있는 것은 인라이크 정책의 이름 city_boundaries 이며, 누락된 정보는 해당 정책 정의에 캡슐화되어 있습니다.
여기에서 geo_match 쿼리를 수행하고(intersects 이 기본값), 일치시킬 필드는 city_boundary, enrich_fields 은 원본 문서에 추가하려는 필드임을 알 수 있습니다. 이러한 필드 중 하나인 region 은 실제로 STATS 명령의 그룹화 키로 사용되었는데, 이 '왼쪽 조인' 기능이 없었다면 불가능했을 것입니다. 인라이크 정책에 대한 자세한 내용은 인라이크 문서를 참조하세요. 이러한 문서를 읽다 보면, 수집 파이프라인을 구성하여 인덱스 시점에 데이터를 보강하기 위해 인덱스 보강을 사용하는 방법을 설명하는 것을 볼 수 있습니다. ENRICH 명령은 쿼리 시점에 작동하므로 ES|QL에는 필요하지 않습니다. 필요한 데이터와 보강 정책으로 보강 인덱스를 준비한 다음 ES|QL 쿼리에서 ENRICH 명령을 사용하면 충분합니다.
또한 가장 많이 발견된 지역은 null 입니다. 이것은 무엇을 의미할까요? 이 명령을 SQL의 '왼쪽 조인'에 비유했는데, 이는 공항에 대해 일치하는 도시 경계를 찾을 수 없는 경우 공항이 여전히 반환되지만 airport_city_boundaries 인덱스의 필드에 대한 null 값이 포함된다는 의미입니다. 일치하는 항목이 없는 공항이 89개( city_boundary), 일치하는 항목이 있는 공항이 1개( region 필드 null)인 것으로 나타났습니다. 그 결과 결과에서 region 이 없는 공항이 90곳으로 집계되었습니다. 또 다른 흥미로운 세부 사항은 MV_EXPAND 명령이 필요하다는 것입니다. 이는 ENRICH 명령이 각 입력 행에 대해 여러 개의 결과를 반환할 수 있고 MV_EXPAND 을 사용하면 이러한 결과를 각 결과마다 하나씩 여러 행으로 분리할 수 있기 때문에 필요합니다. 이는 또한 "하와이" 과 min_wkt 및 max_wkt 결과가 다르게 표시되는 이유를 설명합니다. 이름은 같지만 경계가 다른 여러 지역이 존재하기 때문입니다.
Kibana 지도
Kibana는 지도 애플리케이션에서 공간 ES|QL에 대한 지원을 추가했습니다. 즉, 이제 ES|QL을 사용해 Elasticsearch에서 위치 기반 정보 데이터를 검색하고 그 결과를 지도에서 시각화할 수 있습니다.

레이어 추가 메뉴에 "ES|QL" 이라는 새로운 레이어 옵션이 있습니다. 지금까지 설명한 모든 지리공간 기능과 마찬가지로 "기술 미리보기" 에서 확인할 수 있습니다. 이 옵션을 선택하면 ES|QL 쿼리 결과를 기반으로 맵에 레이어를 추가할 수 있습니다. 예를 들어 전 세계의 모든 공항을 표시하는 레이어를 지도에 추가할 수 있습니다.

또는 airport_city_boundaries 인덱스의 다각형을 표시하는 레이어를 추가하거나, 각 지역에 몇 개의 공항이 있는지 통계를 생성하는 위의 복잡한 ENRICH 쿼리를 추가하는 것이 더 좋을 수 있습니다.

그 다음은 무엇일까요?
위의 두 가지 예제에서 또 다른 공간 함수 ST_CENTROID_AGG 를 압축한 것을 보셨을 것입니다. 이것은 STATS 명령에 사용되는 집계 함수이며, ES|QL에 추가할 예정인 많은 공간 분석 기능 중 첫 번째 기능입니다. 더 많은 것을 보여드릴 수 있게 되면 블로그에 포스팅하겠습니다!
그 전에 우리가 작업한 특히 흥미로운 기능에 대해 자세히 알려드리고자 합니다. 바로 Elasticsearch에서 가장 많이 사용되는 공간 검색 기능 중 하나인 공간 거리 검색을 수행하는 기능입니다. 거리 검색의 구문이 어떤 모습일지 상상할 수 있나요? OGC 기능과 비슷하지 않을까요? 이 시리즈의 다음 블로그에서 자세히 알아보세요!
스포일러 경고: Elasticsearch 8.15가 방금 출시되었으며, ES|QL을 사용한 공간 거리 검색이 포함되어 있습니다!




