Elasticsearch를 직접 체험하려면 당사의 샘플 노트북을 살펴보거나, 무료 클라우드 체험판을 시작하거나, 지금 바로 로컬 기기에서 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을 사용한 공간 거리 검색이 포함되어 있습니다!




