저자: Elastic Craig Taverner에서
Elasticsearch는 수년간 강력한 지리공간 검색 및 분석 기능을 제공해 왔지만 해당 API는 일반적인 GIS 사용자에게 익숙한 API와 매우 다릅니다. 작년에 우리는 SQL만큼 간단하거나 더 간단하지 않은 파이프라인 쿼리 언어인 ES|QL 쿼리 언어를 추가했습니다 . 특히 Elastic이 탁월한 검색, 보안, 관측 가능성 사용 사례에 적합합니다. 또한 ES|QL에 지리공간 검색 및 분석에 대한 지원을 추가하여 특히 SQL 또는 GIS 커뮤니티 사용자의 사용을 더욱 쉽게 만들었습니다 .
Elasticsearch 8.12 및 8.13은 ES|QL에서 지리공간 유형에 대한 기본 지원을 제공합니다. 이 기능은 8.14에 지리공간 검색 기능이 추가되면서 크게 향상되었습니다. 더 중요한 것은 이 지원이 PostGIS와 같은 다른 공간 데이터베이스에서 사용되는 OGC(Open Geospatial Consortium ) OGC(Simple Feature Access ) 표준 과 밀접하게 일치하도록 설계되어 이러한 표준에 익숙한 GIS 전문가가 더 쉽게 사용할 수 있다는 것입니다.
이 블로그 게시물에서는 ES|QL을 사용하여 지리공간 검색을 수행하는 방법과 이를 SQL 및 쿼리 DSL과 비교하는 방법을 보여 드리겠습니다. 또한 ES|QL을 사용하여 공간 조인을 수행하고 Kibana 지도에서 결과를 시각화하는 방법도 보여 드리겠습니다. 여기에 설명된 모든 기능은 "기술 미리 보기" 상태에 있으며 이러한 기능을 개선하는 방법에 대한 피드백을 듣고 싶습니다.
데이터 준비
이 튜토리얼에 사용된 데이터는 다음 위치에서 다운로드할 수 있습니다.
git clone https://github.com/liu-xiao-guo/esql
우리가 사용하는 문서는 메인 · liu-xiao-guo/esql · GitHub의 esql/airport_city_boundaries.csv 입니다.
그런 다음 Kibana를 엽니다.
위에서는 인덱스 이름을 Airport_city_boundaries로 설정했습니다.
위의 매핑을 다음과 같이 수정합니다.
"properties": {
"abbrev": {
"type": "keyword"
},
"airport": {
"type": "text"
},
"city": {
"type": "keyword"
},
"city_boundary": {
"type": "geo_shape"
},
"city_location": {
"type": "geo_point"
},
"region": {
"type": "text"
}
}
}
위와 같이 769개의 문서를 성공적으로 작성했습니다.
공간정보 검색
예제 쿼리부터 시작해 보겠습니다.
FROM airport_city_boundaries
| WHERE ST_INTERSECTS(
city_boundary,
"POLYGON((109.4 18.1, 109.6 18.1, 109.6 18.3, 109.4 18.3, 109.4 18.1))"::geo_shape
)
| KEEP abbrev, airport, region, city, city_location
그러면 싼야 봉황 국제공항(SYX) 주변의 직사각형 검색 다각형과 교차하는 모든 도시 경계 다각형이 검색됩니다.
공항, 도시 및 도시 경계의 샘플 데이터세트에서 이 검색은 교차하는 다각형을 찾고 일치하는 문서에서 필수 필드를 반환합니다.
약어 | 공항 | 지역 | 도시 | 도시 위치 |
---|---|---|---|---|
SYX | 싼야 피닉스 국제공항 | 톈야구 | 그것을 배치 | 포인트(109.5036 18.2533) |
쉽습니다! 이제 이를 동일한 쿼리에 대한 클래식 Elasticsearch 쿼리 DSL과 비교해 보세요.
GET /airport_city_boundaries/_search
{
"_source": ["abbrev", "airport", "region", "city", "city_location"],
"query": {
"geo_shape": {
"city_boundary": {
"shape": {
"type": "polygon",
"coordinates" : [[
[109.4, 18.1],
[109.6, 18.1],
[109.6, 18.3],
[109.4, 18.3],
[109.4, 18.1]
]]
}
}
}
}
}
두 쿼리의 목적은 상당히 명확하지만 ES|QL 쿼리는 SQL과 매우 유사합니다. PostGIS의 동일한 쿼리는 다음과 같습니다:
SELECT abbrev, airport, region, city, city_location
FROM airport_city_boundaries
WHERE ST_INTERSECTS(
city_boundary,
'SRID=4326;POLYGON((109.4 18.1, 109.6 18.1, 109.6 18.3, 109.4 18.3, 109.4 18.1))'::geometry
);
ES|QL 예제를 검토해 보겠습니다. 꽤 비슷하죠?
FROM airport_city_boundaries
| WHERE ST_INTERSECTS(
city_boundary,
"POLYGON((109.4 18.1, 109.6 18.1, 109.6 18.3, 109.4 18.3, 109.4 18.1))"::geo_shape
)
| KEEP abbrev, airport, region, city, city_location
Elasticsearch API의 기존 사용자는 ES|QL을 사용하기가 더 쉽다는 것을 확인했습니다. 이제 기존 SQL 사용자(특히 Spatial 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 예제와 매우 유사하다는 것을 알 수 있습니다.
EN|QL
WHERE ST_INTERSECTS(
city_boundary,
"POLYGON((109.4 18.1, 109.6 18.1, 109.6 18.3, 109.4 18.3, 109.4 18.1))"::geo_shape
)
PostGIS
WHERE ST_INTERSECTS(
city_boundary,
'SRID=4326;POLYGON((109.4 18.1, 109.6 18.1, 109.6 18.3, 109.4 18.3, 109.4 18.1))'::geometry
)
문자열 인용 문자의 차이점 외에도 가장 큰 차이점은 문자열을 공백 유형으로 변환하는 방법입니다. PostGIS에서는 ::geometry 접미사를 사용하고 ES|QL에서는 ::geo_shape 접미사를 사용합니다. 이는 ES|QL이 Elasticsearch에서 실행되고 유형 변환 연산자::를 사용하여 문자열을 지원되는 모든 ES|QL 유형(이 경우 geo_shape)으로 변환할 수 있기 때문입니다. 또한 Elasticsearch의 geo_shape 및 geo_point 유형은 일반적으로 SRID 번호 4326으로 표시되는 WGS84라는 공간 좌표계를 의미합니다. PostGIS에서는 이를 명시적으로 명시해야 하므로 WKT 문자열 앞에 SRID=4326;이 붙습니다. 접두사를 제거하면 SRID는 0으로 설정됩니다. 이는 특정 좌표계에 연결되지 않는 Elasticsearch 유형 cartesian_point 및 cartesian_shape와 더 유사합니다.
ES|QL과 PostGIS는 모두 유형 변환 함수 구문을 제공합니다.
EN|QL
WHERE ST_INTERSECTS(
city_boundary,
TO_GEOSHAPE("POLYGON((109.4 18.1, 109.6 18.1, 109.6 18.3, 109.4 18.3, 109.4 18.1))")
)
PostGIS
WHERE ST_INTERSECTS(
city_boundary,
ST_SetSRID(
ST_GeomFromText('POLYGON((109.4 18.1, 109.6 18.1, 109.6 18.3, 109.4 18.3, 109.4 18.1))'),
4326
)
)
OGC 기능
Elasticsearch 8.14에는 다음과 같은 네 가지 OGC 공간 검색 기능이 도입되었습니다.
EN|QL | PostGIS | 설명 |
---|---|---|
ST_교차점 | ST_교차 | 두 도형이 교차하면 true를 반환하고, 그렇지 않으면 false를 반환합니다. |
ST_분리 | ST_분리형 | 두 도형이 교차하지 않으면 true를 반환하고, 그렇지 않으면 false를 반환합니다. ST_INTERSECTS의 반대입니다. |
ST_포함 | ST_포함 | 하나의 도형에 다른 도형이 포함되어 있으면 true를 반환하고, 그렇지 않으면 false를 반환합니다. |
ST_WITHIN | ST_내부 | 하나의 도형이 다른 도형 내에 있으면 true를 반환하고, 그렇지 않으면 false를 반환합니다. ST_CONTAINS의 역연산. |
이러한 기능은 PostGIS 기능과 유사하게 동작하며 동일한 방식으로 사용할 수 있습니다. 예를 들어, ST_INTERSECTS는 두 기하학이 교차하는 경우 true를 리턴하고 그렇지 않으면 false를 리턴합니다. 위 표의 문서 링크를 클릭하면 모든 ES|QL 예제가 FROM 절 뒤의 WHERE 절에 있는 반면 모든 PostGIS 예제는 리터럴 기하학을 사용한다는 것을 알 수 있습니다. 실제로 두 플랫폼 모두 쿼리의 모든 부분에서 이러한 기능의 사용을 지원합니다.
PostGIS 문서에서 ST_INTERSECTS의 첫 번째 예는 다음과 같습니다:
SELECT ST_Intersects(
'POINT(0 0)'::geometry,
'LINESTRING ( 2 0, 0 2 )'::geometry
);
ES|QL의 해당 버전은 다음과 같습니다.
ROW ST_INTERSECTS(
"POINT(0 0)"::geo_point,
"LINESTRING ( 2 0, 0 2 )"::geo_shape
)
PostGIS 예제에서는 SRID를 지정하지 않았습니다. 이는 PostGIS에서 도형 유형을 사용할 때 모든 계산이 평면 좌표계에서 수행되므로 두 도형의 SRID가 동일하면 SRID가 무엇인지는 중요하지 않기 때문입니다. Elasticsearch에서도 이는 대부분의 함수에 해당됩니다. 그러나 공간 거리 검색에 대한 다음 블로그에서 볼 수 있듯이 geo_shape 및 geo_point가 구형 계산을 사용하는 예외가 있습니다.
ES|QL 다양성
이상으로 WHERE 절과 ROW 명령에서 공간 함수를 사용하는 예를 살펴보았습니다. 또 어디에 유용할 수 있나요? 매우 유용한 곳은 EVAL 명령입니다. 이 명령을 사용하면 표현식을 평가하고 결과를 반환할 수 있습니다. 예를 들어, 국가 이름별로 그룹화된 모든 공항의 중심이 국가 개요 경계 내에 있는지 확인하겠습니다.
FROM airports
| EVAL in_uk = ST_INTERSECTS(location, TO_GEOSHAPE("POLYGON((1.2305 60.8449, -1.582 61.6899, -10.7227 58.4017, -7.1191 55.3291, -7.9102 54.2139, -5.4492 54.0078, -5.2734 52.3756, -7.8223 49.6676, -5.0977 49.2678, 0.9668 50.5134, 2.5488 52.1065, 2.6367 54.0078, -0.9668 56.4625, 1.2305 60.8449))"))
| EVAL in_iceland = ST_INTERSECTS(location, TO_GEOSHAPE("POLYGON ((-25.4883 65.5312, -23.4668 66.7746, -18.4131 67.4749, -13.0957 66.2669, -12.3926 64.4159, -20.1270 62.7346, -24.7852 63.3718, -25.4883 65.5312))"))
| EVAL within_uk = ST_WITHIN(location, TO_GEOSHAPE("POLYGON((1.2305 60.8449, -1.582 61.6899, -10.7227 58.4017, -7.1191 55.3291, -7.9102 54.2139, -5.4492 54.0078, -5.2734 52.3756, -7.8223 49.6676, -5.0977 49.2678, 0.9668 50.5134, 2.5488 52.1065, 2.6367 54.0078, -0.9668 56.4625, 1.2305 60.8449))"))
| EVAL within_iceland = ST_WITHIN(location, TO_GEOSHAPE("POLYGON ((-25.4883 65.5312, -23.4668 66.7746, -18.4131 67.4749, -13.0957 66.2669, -12.3926 64.4159, -20.1270 62.7346, -24.7852 63.3718, -25.4883 65.5312))"))
| STATS centroid = ST_CENTROID_AGG(location), count=COUNT() BY in_uk, in_iceland, within_uk, within_iceland
| SORT count ASC
결과는 예상한 대로 영국 공항의 중심이 아이슬란드 국경이 아닌 영국 국경 내에 있고 그 반대의 경우도 마찬가지입니다.
중심 | 세다 | 영국에서 | 아이슬란드에서 | 영국 내 | 아이슬란드 내 |
---|---|---|---|---|---|
포인트 (-21.946634463965893 64.13187285885215) | 1 | 거짓 | 진실 | 거짓 | 진실 |
포인트 (-2.597342072712148 54.33551226578214) | 17 | 진실 | 거짓 | 진실 | 거짓 |
포인트(0.04453958108176276 23.74658354606057) | 873 | 거짓 | 거짓 | 거짓 | 거짓 |
실제로 이러한 함수는 해당 서명이 의미가 있는 한 쿼리의 모든 부분에서 사용할 수 있습니다. 둘 다 리터럴 공간 객체 또는 공간 유형의 필드라는 두 개의 인수를 허용하며 둘 다 부울 값을 반환합니다. 중요한 고려 사항은 형상의 좌표 참조 시스템(CRS)이 일치해야 한다는 것입니다. 그렇지 않으면 오류가 반환됩니다. 이는 동일한 함수 호출에서 geo_shape 및 cartesian_shape 유형을 혼합할 수 없음을 의미합니다. 그러나 geo_point 유형은 geo_shape 유형의 특별한 경우이고 둘 다 동일한 좌표 참조 시스템을 공유하므로 geo_point 및 geo_shape 유형을 혼합할 수 있습니다. 위에 정의된 각 함수에 대한 문서에는 지원되는 유형 조합이 나열되어 있습니다.
또한 두 매개변수 중 하나는 순서에 관계없이 공백 리터럴이거나 필드일 수 있습니다. 두 개의 필드, 두 개의 텍스트, 필드와 텍스트 또는 텍스트와 필드를 지정할 수도 있습니다. 유일한 요구 사항은 유형 호환성입니다. 예를 들어 다음 쿼리는 동일한 인덱스에 있는 두 필드를 비교합니다.
FROM airport_city_boundaries
| EVAL in_city = ST_INTERSECTS(city_location, city_boundary)
| STATS count=COUNT(*) BY in_city
| SORT count ASC
| EVAL cardinality = CASE(count < 10, "very few", count < 100, "few", "many")
| KEEP cardinality, count, in_city
쿼리는 기본적으로 도시 위치가 도시 경계 내에 있는지 묻습니다. 이는 일반적으로 정확해야 하지만 항상 예외가 있습니다.
기수 | 세다 | in_city |
---|---|---|
약간의 | 29 | 거짓 |
많은 | 740 | 진실 |
더 흥미로운 질문은 공항 위치가 공항이 서비스를 제공하는 도시 경계 내에 있는지 여부입니다. 그러나 공항 위치는 도시 경계를 포함하는 위치와 다른 색인에 있습니다. 이를 위해서는 두 개의 독립 인덱스에 있는 데이터를 효율적으로 쿼리하고 상관시키는 방법이 필요합니다.
공간 조인
ES|QL은 JOIN 명령을 지원하지 않지만 ENRICH 명령을 사용하여 SQL의 "왼쪽 조인"처럼 동작하는 특별한 조인 사례를 구현할 수 있습니다. 이 명령은 SQL의 "왼쪽 조인"처럼 작동하므로 두 데이터 세트 간의 공간 관계를 기반으로 한 인덱스의 결과를 다른 인덱스의 데이터로 보강할 수 있습니다.
예를 들어, 공항이 서비스를 제공하는 도시에 대한 추가 정보와 함께 공항 위치가 포함된 도시 경계를 찾아 공항 테이블의 결과를 강화한 다음 결과에 대한 일부 통계를 실행해 보겠습니다.
FROM airports
| ENRICH city_boundaries ON city_location WITH airport, region, city_boundary
| MV_EXPAND city_boundary
| EVAL boundary_wkt_length = LENGTH(TO_STRING(city_boundary))
| STATS centroid = ST_CENTROID_AGG(location), count = COUNT(city_location), min_wkt = MIN(boundary_wkt_length), max_wkt = MAX(boundary_wkt_length) BY region
| SORT count DESC
| LIMIT 5
그러면 공항이 가장 많은 상위 5개 지역과 일치하는 지역이 있는 모든 공항의 중심, 해당 지역 내 도시 경계의 WKT 표현 길이 범위가 반환됩니다.
중심 | 세다 | 내_주 | 최대_길이 | 지역 |
---|---|---|---|---|
포인트 (-32.56093470960719 32.598117914802714) | 90 | 207 | 207 | 널 |
포인트 (-73.94515332765877 40.70366442203522) | 9 | 438 | 438 | 뉴욕시 |
포인트 (-83.10398317873478 42.300230911932886) | 9 | 473 | 473 | 디트로이트 |
포인트 (-156.3020245861262 20.176383580081165) | 5 | 307 | 803 | 하와이 |
포인트 (-73.88902732171118 45.57078813901171) | 4 | 837 | 837 | 몬트리올 |
그렇다면 여기서 정확히 무슨 일이 일어나고 있는 걸까요? 소위 JOIN은 어디에서 발생합니까? 쿼리의 핵심은 ENRICH 명령에 있습니다.
FROM airports
| ENRICH city_boundaries ON city_location WITH airport, region, city_boundary
이 명령은 Elasticsearch에게 Airports 인덱스에서 검색된 결과를 강화하고 원래 인덱스의 city_location 필드와 이전 몇 가지 예에서 사용한 Airport_city_boundaries 인덱스의 city_boundary 필드 사이에 교차 조인을 수행하도록 지시합니다. 하지만 이 정보 중 일부는 이 쿼리에서 명확하게 표시되지 않습니다. 우리가 보는 것은 강화된 정책 city_boundaries의 이름이며, 누락된 정보는 정책 정의에 캡슐화되어 있습니다.
{
"geo_match": {
"indices": "airport_city_boundaries",
"match_field": "city_boundary",
"enrich_fields": ["city", "airport", "region", "city_boundary"]
}
}
여기서는 geo_match 쿼리(기본적으로 교차)를 수행하고 일치할 필드는 city_boundary이며 rich_fields는 원본 문서에 추가하려는 필드임을 알 수 있습니다. 필드 중 하나인 영역은 실제로 STATS 명령의 그룹화 키로 사용되는데, 이 "왼쪽 조인" 기능이 없으면 이를 수행할 수 없습니다. 강화 전략에 대한 자세한 내용은 강화 문서를 참조하세요 . 이 문서를 읽어보면 풍부한 인덱스를 사용하여 수집 파이프라인을 구성하여 인덱스 시 데이터를 풍부하게 하는 방법이 설명되어 있음을 알 수 있습니다. ENRICH 명령은 쿼리 시 작동하기 때문에 ES|QL에는 필요하지 않습니다. 필요한 데이터와 강화 전략으로 풍부한 인덱스를 준비한 다음 ES|QL 쿼리에서 ENRICH 명령을 사용하면 충분합니다.
또한 가장 일반적인 필드가 null이라는 것을 알 수 있습니다. 그게 무슨 뜻이야? 이 명령을 SQL의 "왼쪽 조인"과 비교했습니다. 즉, 공항에 대해 일치하는 도시 경계가 없으면 공항은 여전히 반환되지만 Airport_city_boundaries 인덱스의 필드 값은 null입니다. 89개 공항에는 일치하는 도시 경계가 없었고, 한 공항에는 null 일치 지역 필드가 있는 것으로 나타났습니다. 결과에는 지역이 없는 90개의 공항이 포함되었습니다. 또 다른 흥미로운 세부 사항은 MV_EXPAND 명령이 필요하다는 것입니다. 이는 ENRICH 명령이 각 입력 행에 대해 여러 결과를 반환할 수 있고 MV_EXPAND가 이러한 결과를 각 결과에 대해 하나씩 여러 행으로 나누는 데 도움이 되기 때문에 필요합니다. 이는 또한 "하와이"가 서로 다른 min_wkt 및 max_wkt 결과를 표시하는 이유를 설명합니다. 이름은 같지만 경계가 다른 여러 지역이 있습니다.
키바나 지도
Kibana는 지도 애플리케이션에 Spatial ES|QL에 대한 지원을 추가합니다. 이는 이제 ES|QL을 사용하여 Elasticsearch에서 지리공간 데이터를 검색하고 결과를 지도에 시각화할 수 있다는 의미입니다.
Add Layer 메뉴에는 "ES|QL"이라는 새로운 레이어 옵션이 있습니다. 지금까지 설명한 모든 지리공간 기능과 마찬가지로 이 옵션도 "기술 미리 보기" 상태입니다. 이 옵션을 선택하면 ES|QL 쿼리 결과를 기반으로 지도에 레이어를 추가할 수 있습니다. 예를 들어, 세계의 모든 공항을 표시하는 레이어를 지도에 추가할 수 있습니다.
또는 Airport_city_boundaries 인덱스에 다각형을 표시하는 레이어를 추가할 수 있습니다. 또는 더 나은 방법은 위의 복잡한 ENRICH 쿼리가 각 지역에 공항이 몇 개 있는지에 대한 통계를 어떻게 생성합니까?
다음은 무엇입니까?
위의 두 예에서 다른 공간 함수 ST_CENTROID_AGG를 압축했다는 것을 눈치챘을 것입니다. 이는 STATS 명령에 사용되는 집계 함수이며 ES|QL에 추가할 많은 공간 분석 기능 중 첫 번째 기능입니다. 더 보여줄 것이 있으면 블로그에 올리겠습니다!
그에 앞서, 우리가 개발한 특히 흥미로운 기능, 즉 Elasticsearch에서 가장 일반적으로 사용되는 공간 검색 기능 중 하나인 공간 거리 검색을 수행하는 기능에 대해 조금 더 말씀드리고 싶습니다. 거리 검색의 구문이 어떤 모습일지 상상할 수 있나요? 어쩌면 OGC 기능과 같은 것이 있을까요? 자세한 내용은 이 시리즈의 다음 블로그를 계속 지켜봐 주시기 바랍니다!
스포일러 경고: Elasticsearch 8.15가 방금 출시되었으며 ES|QL을 사용한 공간 거리 검색이 포함되어 있습니다!
직접 시도해 볼 준비가 되셨나요? 무료 평가판을 시작하세요 .
Elastic 인증을 받고 싶으신가요? 다음 Elasticsearch 엔지니어 교육이 언제 시작되는지 알아보세요!
원본: Elasticsearch ES|QL을 사용한 Geospatial 검색 — Search Labs
Google: Rust로의 전환으로 Android 취약점이 크게 감소했습니다. Huawei는 공개 클래식 음악 플레이어인 Winamp가 2024.2.3에 공식적으로 오픈 소스로 출시되었다고 발표 했습니다 . 오라클 상표가 되었나요? Open Source Daily | PostgreSQL 17; 중국 AI 기업이 미국 칩 금지를 우회하는 방법; AI 개발자의 갈증을 해소할 수 있는 사람은 누구입니까? "Zhihuijun" 스타트업 회사는 현대 로봇 분야의 런타임 개발 프레임워크인 AimRT를 오픈 소스로 공개했으며, Tcl/Tk 9.0은 Meta를 출시하고 Llama 3.2 다중 모드 AI 모델을 출시했습니다.