GRAPHLOOKUP 명령어

GRAPHLOOKUP 명령어

graphLookup 명령어는 너비 우선 탐색(BFS, breadth-first search) 알고리즘을 사용해 컬렉션에서 재귀적 그래프 탐색을 수행해요. 조직도, 소셜 네트워크, 라우팅 그래프 같은 계층적 데이터에 유용해요.

출처: 문서

본문

참고: 이 기능은 실험적(experimental) 기능이라 프로덕션 환경에서 사용하는 것은 권장하지 않아요. 기능 진행 상황에 대한 업데이트를 받거나 피드백을 남기고 싶다면 OpenSearch 포럼에서 토론에 참여해요.

graphLookup 명령어는 너비 우선 탐색(BFS) 알고리즘을 사용해 컬렉션에서 재귀적 그래프 탐색을 수행해요. 시작 값과 일치하는 문서를 찾은 다음 지정된 필드를 기준으로 문서 간 관계를 재귀적으로 탐색해요. 조직도, 소셜 네트워크, 라우팅 그래프 같은 계층적 데이터에 유용해요.

graphLookup 명령어는 너비 우선 탐색(BFS) 탐색을 수행해요:

양방향 탐색(<->)의 경우 알고리즘은 fromField 값을 추가로 일치시켜 간선을 역방향으로도 따라가요.

구문 (Syntax)

graphLookup 명령어의 구문은 다음과 같아요:

graphLookup <lookupIndex> start=<startField> edge=<fromField><operator><toField> [maxDepth=<maxDepth>] [depthField=<depthField>] [supportArray=(true | false)] [batchMode=(true | false)] [usePIT=(true | false)] [filter=(<condition>)] as <outputField>

다음은 graphLookup 명령어 구문의 예시예요:

source = employees | graphLookup employees start=reportsTo edge=reportsTo-->name as reportingHierarchy
source = employees | graphLookup employees start=reportsTo edge=reportsTo-->name maxDepth=2 as reportingHierarchy
source = employees | graphLookup employees start=reportsTo edge=reportsTo-->name depthField=level as reportingHierarchy
source = employees | graphLookup employees start=reportsTo edge=reportsTo<->name as connections
source = travelers | graphLookup airports start=nearestAirport edge=connects-->airport supportArray=true as reachableAirports
source = airports | graphLookup airports start=airport edge=connects-->airport supportArray=true as reachableAirports
source = employees | graphLookup employees start=reportsTo edge=reportsTo-->name filter=(status = 'active' AND age > 18) as reportingHierarchy

매개변수 (Parameters)

graphLookup 명령어는 다음 매개변수를 지원해요.

매개변수 필수/선택 설명
<lookupIndex> 필수 그래프 탐색을 수행할 인덱스의 이름이에요. 자기 참조 그래프라면 소스 인덱스와 같을 수 있어요.
start=<startField> 필수 재귀 검색을 시작하는 데 사용되는 값이 담긴 소스 문서의 필드예요. 값은 lookup 인덱스의 toField와 일치해요. 단일 값과 배열을 모두 지원해요.
edge=<fromField><operator><toField> 필수 노드 간의 탐색 경로를 정의해 문서가 어떻게 연결되는지와 탐색 방향을 지정해요. Edge 매개변수(Edge parameters)를 참고해요.
maxDepth=<maxDepth> 선택 최대 재귀 깊이(홉 수)예요. 기본값은 0이에요. 값이 0이면 직접 연결만 반환하고, 값이 높을수록 그에 따라 탐색이 확장돼요.
depthField=<depthField> 선택 각 결과 문서에 추가되어 재귀 깊이를 나타내는 필드의 이름이에요. 생략하면 깊이 정보가 추가되지 않아요. 깊이는 첫 번째 레벨에서 0부터 시작해요.
supportArray=(true | false) 선택 true이면 방문한 노드 필터를 OpenSearch로 조기 push-down하지 않아요. 기본값은 false예요. fromField나 toField에 배열 값이 포함되어 있으면 올바른 탐색 동작을 위해 활성화해요. 배열 필드(Array fields)를 참고해요.
batchMode=(true | false) 선택 true이면 모든 시작 값을 수집하고 단일 통합 BFS 탐색을 수행해요. 기본값은 false예요. 출력은 두 개의 배열 [Array<sourceRows>, Array<lookupResults>]이 돼요. 배치 모드(Batch Mode)를 참고해요.
usePIT=(true | false) 선택 true이면 lookup 인덱스에 대해 특정 시점(PIT, Point in Time) 검색을 활성화해 max_result_window 제한을 넘어 완전한 페이지네이션 탐색을 가능하게 해요. 기본값은 false예요. PIT 검색(PIT Search)을 참고해요.
filter=(<condition>) 선택 탐색에 참여하는 lookup 인덱스 문서를 제한하는 필터 조건이에요. BFS 중 일치하는 문서만 고려돼요. 괄호는 필수예요. 예: filter=(status = 'active' AND age > 18)
as <outputField> 필수 탐색 중 발견된 모든 문서를 저장하는 출력 필드의 이름이에요.

Edge 매개변수

edge 매개변수는 edge=<fromField><operator><toField> 구문을 사용하며 다음 구성 요소로 이루어져 있어요.

구성 요소 설명
fromField 탐색의 원본으로 사용되는 lookup 인덱스 문서의 필드예요. 문서가 일치하면 이 필드의 값을 사용해 다음 연결 문서 집합을 찾아요. 단일 값과 배열을 모두 지원해요.
toField 일치에 사용되는 lookup 인덱스 문서의 필드예요. toField가 현재 탐색 값과 같은 문서가 결과에 포함돼요.
operator 탐색 방향을 지정해요: - -->는 fromField에서 toField로만 단방향 탐색을 수행해요 (예: edge=reportsTo-->name은 reportsTo에서 name으로 한 방향으로만 탐색해요). - <->는 fromField와 toField 사이를 양방향으로 탐색해요 (예: edge=reportsTo<->name은 reportsTo와 name 사이를 양방향으로 탐색해요).

예제 1: 직원 계층 구조 탐색하기

다음 문서를 포함하는 employees 인덱스를 생각해 보세요.

id name reportsTo
1 Dev Eliot
2 Eliot Ron
3 Ron Andrew
4 Andrew null
5 Asya Ron
6 Dan Andrew

다음 쿼리는 각 직원의 보고 체인을 찾아요:

source = employees
  | graphLookup employees
    start=reportsTo
    edge=reportsTo-->name
    as reportingHierarchy

쿼리는 다음과 같은 결과를 반환해요:

name reportsTo id reportingHierarchy
Dev Eliot 1 [{name:Eliot, reportsTo:Ron, id:2}]
Eliot Ron 2 [{name:Ron, reportsTo:Andrew, id:3}]
Ron Andrew 3 [{name:Andrew, reportsTo:null, id:4}]
Andrew null 4 []
Asya Ron 5 [{name:Ron, reportsTo:Andrew, id:3}]
Dan Andrew 6 [{name:Andrew, reportsTo:null, id:4}]

reportingHierarchy 배열의 각 요소는 lookup 인덱스의 명명된 필드를 포함하는 struct예요. Dev라는 직원의 경우 탐색은 reportsTo="Eliot"로 시작해 Eliot의 레코드를 찾고 reportingHierarchy 배열에 포함해요.

예제 2: 깊이 추적 추가하기

다음 쿼리는 level이라는 depthField를 추가해 각 관리자가 직원으로부터 몇 레벨 떨어져 있는지 추적해요:

source = employees
  | graphLookup employees
    start=reportsTo
    edge=reportsTo-->name
    depthField=level
    as reportingHierarchy

쿼리는 다음과 같은 결과를 반환해요:

name reportsTo id reportingHierarchy
Dev Eliot 1 [{name:Eliot, reportsTo:Ron, id:2, level:0}]
Eliot Ron 2 [{name:Ron, reportsTo:Andrew, id:3, level:0}]
Ron Andrew 3 [{name:Andrew, reportsTo:null, id:4, level:0}]
Andrew null 4 []
Asya Ron 5 [{name:Ron, reportsTo:Andrew, id:3, level:0}]
Dan Andrew 6 [{name:Andrew, reportsTo:null, id:4, level:0}]

level 필드는 결과 배열의 각 struct에 추가돼요. 값 0은 첫 번째 일치 레벨을 나타내요.

예제 3: 탐색 깊이 제한하기

다음 쿼리는 maxDepth=1(깊이 0과 1)을 사용해 탐색을 두 레벨로 제한해요:

source = employees
  | graphLookup employees
    start=reportsTo
    edge=reportsTo-->name
    maxDepth=1
    as reportingHierarchy

쿼리는 다음과 같은 결과를 반환해요:

name reportsTo id reportingHierarchy
Dev Eliot 1 [{name:Eliot, reportsTo:Ron, id:2}, {name:Ron, reportsTo:Andrew, id:3}]
Eliot Ron 2 [{name:Ron, reportsTo:Andrew, id:3}, {name:Andrew, reportsTo:null, id:4}]
Ron Andrew 3 [{name:Andrew, reportsTo:null, id:4}]
Andrew null 4 []
Asya Ron 5 [{name:Ron, reportsTo:Andrew, id:3}, {name:Andrew, reportsTo:null, id:4}]
Dan Andrew 6 [{name:Andrew, reportsTo:null, id:4}]

예제 4: 도달 가능한 공항 찾기

다음 문서를 포함하는 airports 인덱스를 생각해 보세요.

airport connects
JFK [BOS, ORD]
BOS [JFK, PWM]
ORD [JFK]
PWM [BOS, LHR]
LHR [PWM]

다음 쿼리는 각 공항에서 도달 가능한 모든 공항을 찾아요:

source = airports
  | graphLookup airports
    start=airport
    edge=connects-->airport
    as reachableAirports

쿼리는 다음과 같은 결과를 반환해요:

airport connects reachableAirports
JFK [BOS, ORD] [{airport:JFK, connects:[BOS, ORD]}]
BOS [JFK, PWM] [{airport:BOS, connects:[JFK, PWM]}]
ORD [JFK] [{airport:ORD, connects:[JFK]}]
PWM [BOS, LHR] [{airport:PWM, connects:[BOS, LHR]}]
LHR [PWM] [{airport:LHR, connects:[PWM]}]

예제 5: 서로 다른 소스와 lookup 인덱스 사용하기

graphLookup 명령어는 서로 다른 소스와 lookup 인덱스를 사용할 수 있어요.

다음 문서를 포함하는 travelers 인덱스를 생각해 보세요.

name nearestAirport
Dev JFK
Eliot JFK
Jeff BOS

다음 쿼리는 각 여행자에 대한 도달 가능한 공항을 찾아요:

source = travelers
  | graphLookup airports
    start=nearestAirport
    edge=connects-->airport
    as reachableAirports

쿼리는 다음과 같은 결과를 반환해요:

name nearestAirport reachableAirports
Dev JFK [{airport:JFK, connects:[BOS, ORD]}]
Eliot JFK [{airport:JFK, connects:[BOS, ORD]}]
Jeff BOS [{airport:BOS, connects:[JFK, PWM]}]

예제 6: 그래프를 양방향으로 탐색하기

다음 쿼리는 양방향 탐색을 수행해 관리자와 같은 관리자를 공유하는 동료를 모두 찾아요:

source = employees
  | where name = 'Ron'
  | graphLookup employees
    start=reportsTo
    edge=reportsTo<->name
    as connections

쿼리는 다음과 같은 결과를 반환해요:

name reportsTo id connections
Ron Andrew 3 [{name:Ron, reportsTo:Andrew, id:3}, {name:Andrew, reportsTo:null, id:4}, {name:Dan, reportsTo:Andrew, id:6}]

양방향 탐색에서 Ron의 연결에는 다음 레코드가 포함돼요:

  • Ron 자신의 레코드 (Ron은 Andrew에게 보고함).
  • Ron의 관리자 (Andrew).
  • Ron의 동료 (Andrew에게도 보고하는 Dan).

배치 모드 (Batch mode)

batchMode=true이면 graphLookup 명령어는 모든 소스 행에서 시작 값을 수집하고, 각 행을 개별적으로 탐색하는 대신 단일 통합 BFS 탐색을 수행해요.

다음과 같은 경우 batchMode=true를 사용해요:

  • 소스 시작 값 중 하나에서 도달 가능한 모든 노드를 찾고 싶을 때.
  • 여러 시작점에서 그래프 연결성의 전역적 관점이 필요할 때.
  • 여러 소스 행이 겹치는 경로를 공유할 때 중복 탐색을 피하고 싶을 때.

배치 모드에서는 출력이 두 배열을 포함하는 단일 행이에요:

  1. 수집된 모든 소스 행.
  2. 통합 BFS 탐색의 모든 lookup 결과.

다음 쿼리는 각 여행자의 가장 가까운 공항에서 도달 가능한 모든 공항을 찾아요:

source = travelers
  | graphLookup airports
    start=nearestAirport
    edge=connects-->airport
    batchMode=true
    maxDepth=2
    as reachableAirports

표준 모드(기본값): 각 여행자에게 도달 가능한 공항 목록이 할당돼요:

| name  | nearestAirport | reachableAirports                    |
|-------|----------------|--------------------------------------|
| Dev   | JFK            | [{airport:JFK, connects:[BOS, ORD]}] |
| Jeff  | BOS            | [{airport:BOS, connects:[JFK, PWM]}] |

배치 모드: 모든 여행자와 모든 도달 가능한 공항이 단일 결과로 결합돼요:

| travelers                                                          | reachableAirports                                           |
|--------------------------------------------------------------------|-------------------------------------------------------------|
| [{name:Dev, nearestAirport:JFK}, {name:Jeff, nearestAirport:BOS}] | [{airport:JFK, connects:[BOS, ORD]}, {airport:BOS, ...}]   |

배열 필드 (Array fields)

fromField나 toField에 배열 값이 포함되어 있으면 올바른 탐색 동작을 위해 supportArray=true를 설정해요.

기본적으로 BFS 탐색의 각 레벨은 반환되는 문서 수를 lookup 인덱스의 max_result_window 설정(일반적으로 10,000)으로 제한해요. 이렇게 하면 특정 시점(PIT) 검색의 오버헤드를 피하지만, 단일 탐색 레벨이 제한보다 많은 문서와 일치하면 불완전한 결과를 반환할 수 있어요.

usePIT=true이면 이 제한이 제거되고 lookup 테이블은 PIT 기반 페이지네이션을 사용해 각 탐색 레벨에서 모든 일치하는 문서가 검색되도록 보장해요. 추가 검색 오버헤드를 대가로 완전하고 정확한 결과를 제공해요.

다음과 같은 경우 usePIT=true를 사용해요:

  • 그래프에 단일 탐색 레벨이 max_result_window보다 많은 문서를 반환할 수 있는 고차(high-degree) 노드가 포함되어 있을 때.
  • 쿼리 성능보다 결과 완전성이 더 중요할 때.
  • 기본 설정으로 불완전하거나 누락된 결과가 관찰될 때.

다음 쿼리는 완전한 탐색 결과를 보장하기 위해 PIT 검색을 활성화해요:

source = employees
  | graphLookup employees
    start=reportsTo
    edge=reportsTo-->name
    usePIT=true
    as reportingHierarchy

필터링된 그래프 탐색 (Filtered graph traversal)

filter 매개변수는 BFS 탐색 중 고려되는 lookup 인덱스의 문서를 제한해요. 필터 조건과 일치하는 문서만 각 탐색 레벨의 후보로 포함돼요.

다음 쿼리는 보고 계층에서 활성 직원만 탐색해요:

source = employees
  | graphLookup employees
    start=reportsTo
    edge=reportsTo-->name
    filter=(status = 'active')
    as reportingHierarchy

필터는 OpenSearch 쿼리 레벨에서 적용되므로 BFS 탐색 쿼리와 효율적으로 결합돼요. 각 BFS 레벨에서 OpenSearch로 보내는 쿼리는 bool { filter: [user_filter, bfs_terms_query] } 형태예요.

제한 사항 (Limitations)

graphLookup 명령어의 다음 제한 사항을 참고해요:

  • 탐색의 시작점을 제공하는 소스 입력은 성능 문제를 피하기 위해 100개 문서로 제한돼요.
  • usePIT=false(기본값)이면 각 탐색 레벨은 lookup 인덱스의 max_result_window까지 반환하며 불완전한 결과가 발생할 수 있어요. 완전한 결과를 얻으려면 usePIT=true를 설정해요.

더 알아보기 (Learn more)