fold()/coalesce()/unfold()를 사용하여 효율적인 Gremlin 업서트 만들기 - Amazon Neptune

기계 번역으로 제공되는 번역입니다. 제공된 번역과 원본 영어의 내용이 상충하는 경우에는 영어 버전이 우선합니다.

fold()/coalesce()/unfold()를 사용하여 효율적인 Gremlin 업서트 만들기

업서트(또는 조건부 삽입)는 버텍스나 엣지가 이미 있는 경우 이를 다시 사용하고 존재하지 않는 경우 이를 생성합니다. 효율적인 업서트는 Gremlin 쿼리의 성능에 상당한 차이를 만들 수 있습니다.

이 페이지에서는 fold()/coalesce()/unfold() Gremlin 패턴을 사용하여 효율적인 업서트를 만드는 방법을 보여줍니다. 그러나 엔진 TinkerPop 버전 1.2.1.0에서 Neptune에 도입된 버전 3.6.x의 릴리즈에서는 대부분의 경우 새 mergeV() 및 단계를 사용하는 것이 좋습니다. mergeE() 여기에 설명된 fold()/coalesce()/unfold() 패턴은 일부 복잡한 상황에서도 여전히 유용할 수 있지만, 일반적으로 Gremlin mergeV() 및 mergeE() 단계를 사용하여 효율적인 업서트 생성에서 설명한 대로 가능한 경우 mergeV()mergeE()를 사용합니다.

업서트를 사용하면 멱등식 삽입 연산을 작성할 수 있습니다. 이러한 연산을 몇 번 실행해도 전체 결과는 동일합니다. 이는 그래프의 동일한 부분을 동시에 수정하면 하나 이상의 트랜잭션이 ConcurrentModificationException와 함께 롤백되어 재시도가 필요한 동시 쓰기 시나리오에서 유용합니다.

예를 들어, 다음 쿼리는 데이터 세트에서 지정된 버텍스를 먼저 찾은 후 해당 결과를 목록으로 접어 버텍스를 업서트합니다. coalesce() 단계에 제공된 첫 번째 순회에서 쿼리는 이 목록을 펼칩니다. 펼친 목록이 비어 있지 않으면 coalesce()에서 결과가 출력됩니다. 그러나 버텍스가 현재 존재하지 않아 unfold()에서 빈 컬렉션을 반환하면 coalesce()는 제공된 두 번째 순회를 평가하기 위해 이동하며, 이 두 번째 순회에서는 쿼리가 누락된 버텍스를 생성합니다.

g.V('v-1').fold() .coalesce( unfold(), addV('Person').property(id, 'v-1') .property('email', 'person-1@example.org') )

업서트용으로 최적화된 coalesce() 형식 사용

Neptune은 업데이트 처리량을 높이기 위해 fold().coalesce(unfold(), ...) 관용구를 최적화할 수 있지만, 이 최적화는 coalesce()의 두 부분이 모두 버텍스 또는 엣지를 반환하고 그 외에는 아무것도 반환하지 않는 경우에만 작동합니다. coalesce()의 일부에서 속성 등의 다른 요소를 반환하려고 하면 Neptune 최적화가 수행되지 않습니다. 쿼리는 성공할 수 있지만, 특히 대규모 데이터 세트의 경우 최적화된 버전만큼 성능이 좋지는 않습니다.

최적화되지 않은 업서트 쿼리는 실행 시간을 늘리고 처리량을 줄이므로, Gremlin explain 엔드포인트를 사용하여 업서트 쿼리가 완전히 최적화되었는지 확인하는 것이 좋습니다. explain 계획을 검토할 때는 + not converted into Neptune stepsWARNING: >>으로 시작하는 라인을 찾아보세요. 예:

+ not converted into Neptune steps: [FoldStep, CoalesceStep([[UnfoldStep], [AddEdgeSte... WARNING: >> FoldStep << is not supported natively yet

이러한 경고를 통해 쿼리를 완전히 최적화하는 데 방해가 되는 부분을 식별할 수 있습니다.

쿼리를 완전히 최적화할 수 없는 경우도 있습니다. 이러한 상황에서는 최적화할 수 없는 단계를 쿼리 끝에 넣어 엔진이 최대한 많은 단계를 최적화할 수 있도록 해야 합니다. 이 기법은 최적화가 불가능할 수 있는 추가 수정을 동일한 버텍스나 엣지에 적용하기 전에 버텍스 또는 엣지 세트에 대해 최적화된 업서트를 모두 수행하는 일부 배치 업서트 예제에서 사용됩니다.

일괄 처리 업서트로 처리량 향상

쓰기 처리량이 높은 시나리오의 경우 업서트 단계를 연결하여 버텍스와 엣지를 일괄적으로 업서트할 수 있습니다. 일괄 처리를 사용하면 많은 수의 버텍스와 엣지를 업서트할 때 발생하는 트랜잭션 오버헤드가 줄어듭니다. 그런 다음 여러 클라이언트를 통해 배치 요청을 병렬로 업서트하여 처리량을 더욱 개선할 수 있습니다.

일반적으로 배치 요청당 약 200개의 레코드를 업서트하는 것이 좋습니다. 레코드는 단일 버텍스, 엣지 레이블 또는 속성입니다. 예를 들어, 레이블이 하나이고 속성이 4개인 버텍스는 5개의 레코드를 생성합니다. 레이블과 단일 속성이 있는 엣지는 레코드 2개를 생성합니다. 각각 레이블이 하나이고 속성이 4개인 버텍스 배치를 업서트하려면 200 / (1 + 4) = 40이므로 배치 크기를 40으로 시작해야 합니다.

배치 크기를 시험해 볼 수 있습니다. 처음에는 배치당 200개의 레코드가 좋지만, 워크로드에 따라 배치 크기가 더 크거나 작을 수 있습니다. 하지만 Neptune에서 요청당 전체 Gremlin 단계 수를 제한할 수 있다는 점에 유의하세요. 이 제한은 문서화되어 있지 않지만, 안전을 위해 요청에 포함된 Gremlin 단계가 1,500개를 넘지 않도록 하는 것이 좋습니다. Neptune은 1,500단계가 넘는 대규모 배치 요청을 거부할 수 있습니다.

처리량을 늘리려면 여러 클라이언트를 사용하여 배치를 병렬로 업서트할 수 있습니다(효율적인 멀티스레드 Gremlin 쓰기 생성 참조). 클라이언트 수는 Neptune writer 인스턴스의 작업자 스레드 수와 같아야 합니다. 이 수는 일반적으로 서버 vCPUs 수의 2배입니다. 예를 들어, r5.8xlarge 인스턴스에는 vCPUs 32개와 64개의 작업자 스레드가 있습니다. r5.8xlarge를 사용하는 처리량이 높은 쓰기 시나리오의 경우 64개의 클라이언트가 Neptune에 배치 업서트를 병렬로 작성하는 것이 좋습니다.

각 클라이언트는 배치 요청을 제출하고 요청이 완료될 때까지 기다렸다가 다른 요청을 제출해야 합니다. 여러 클라이언트가 병렬로 실행되더라도 각 개별 클라이언트는 순차적으로 요청을 제출합니다. 이렇게 하면 서버 측 요청 대기열에 과부하가 걸리지 않고 모든 작업자 스레드를 점유하는 요청 스트림이 서버에 꾸준히 공급됩니다(Neptune DB 클러스터의 DB 인스턴스 크기 조정 참조).

Traverser가 여러 개 생성되는 단계 피하기

Gremlin 단계가 실행되면 들어오는 Traverser를 받아 하나 이상의 출력 Traverser를 내보냅니다. 한 단계에서 내보내는 Traverser 수에 따라 다음 단계가 실행되는 횟수가 결정됩니다.

일반적으로 배치 작업을 수행할 때는 버텍스 A 업서트와 같은 각 작업을 한 번 실행하여 버텍스 A 업서트, 버텍스 B 업서트, 버텍스 C 업서트 식의 작업 순서를 이루도록 하려고 합니다. 단계가 한 요소만 생성하거나 수정하는 경우 하나의 Traverser만 내보내고 다음 작업을 나타내는 단계는 한 번만 실행됩니다. 반면, 작업에서 2개 이상의 요소를 만들거나 수정하면 여러 Traverser가 생성되어 후속 단계가 생성된 Traverser당 한 번씩 여러 번 실행됩니다. 이로 인해 데이터베이스에서 불필요한 추가 작업이 수행될 수 있으며, 경우에 따라 원하지 않는 추가 버텍스, 엣지 또는 속성값이 생성될 수 있습니다.

문제가 발생할 수 있는 예로는 g.V().addV()와 같은 쿼리를 들 수 있습니다. 이 간단한 쿼리는 그래프에 있는 모든 버텍스에 버텍스를 추가합니다. V()는 그래프의 각 버텍스에 대해 Traverser를 내보내고 각 Traverser가 addV()에 대한 호출을 트리거하기 때문입니다.

여러 Traverser를 내보낼 수 있는 연산을 처리하는 방법은 업서트와 삽입 혼합 섹션을 참조하세요.

버텍스 업서트

버텍스 ID를 사용하여 해당하는 버텍스가 존재하는지 확인할 수 있습니다. Neptune은 동시성이 높은 사용 사례에 맞게 Upsert를 최적화하기 때문에 이 방법이 선호됩니다. IDs 예를 들어, 다음 쿼리는 지정된 버텍스 ID가 없는 경우 버텍스를 생성하고 존재하는 경우 이를 재사용합니다.

g.V('v-1') .fold() .coalesce(unfold(), addV('Person').property(id, 'v-1') .property('email', 'person-1@example.org')) .id()

참고로 이 쿼리는 id() 단계로 끝납니다. 버텍스 업서트를 위해 반드시 필요하지는 않지만, 업서트 쿼리의 끝에 id() 단계를 추가하면 서버가 모든 버텍스 속성을 클라이언트로 다시 직렬화하지 않도록 보장하므로 쿼리의 잠금 비용을 줄이는 데 도움이 됩니다.

또는 버텍스 속성을 사용하여 버텍스가 존재하는지 확인할 수 있습니다.

g.V() .hasLabel('Person') .has('email', 'person-1@example.org') .fold() .coalesce(unfold(), addV('Person').property('email', 'person-1@example.org')) .id()

가능하면 사용자가 제공한 자체 버텍스를 사용하여 버텍스를 만들고 IDs 이를 사용하여 업서트 작업 중에 버텍스가 IDs 존재하는지 확인하세요. 이를 통해 Neptune은 주변의 업서트를 최적화할 수 있습니다. IDs 동시 수정이 빈번하게 발생하는 경우 ID 기반 업서트가 속성 기반 업서트보다 훨씬 더 효율적일 수 있습니다.

버텍스 업서트 연결

버텍스 업서트를 체인으로 연결하여 일괄적으로 삽입할 수 있습니다.

g.V('v-1') .fold() .coalesce(unfold(), addV('Person').property(id, 'v-1') .property('email', 'person-1@example.org')) .V('v-2') .fold() .coalesce(unfold(), addV('Person').property(id, 'v-2') .property('email', 'person-2@example.org')) .V('v-3') .fold() .coalesce(unfold(), addV('Person').property(id, 'v-3') .property('email', 'person-3@example.org')) .id()

엣지 업서트

커스텀 버텍스를 사용하여 IDs 버텍스를 업서트하는 것과 같은 방식으로 엣지를 사용하여 에지를 업서트할 수 있습니다. IDs 다시 말하지만, 이 방법을 사용하면 Neptune에서 쿼리를 최적화할 수 있어 선호됩니다. 예를 들어, 다음 쿼리는 엣지가 아직 없으면 엣지 ID를 기반으로 엣지를 생성하고 존재하면 재사용합니다. 또한 쿼리는 새 가장자리를 만들어야 하는 경우 from 및 IDs to 꼭지점의 a를 사용합니다.

g.E('e-1') .fold() .coalesce(unfold(), addE('KNOWS').from(V('v-1')) .to(V('v-2')) .property(id, 'e-1')) .id()

많은 애플리케이션이 커스텀 버텍스를 사용하지만 엣지를 IDs 생성하는 데는 Neptune을 그대로 둡니다. IDs 가장자리의 ID는 모르지만 fromto IDs 꼭지점은 알고 있다면 다음 공식을 사용하여 가장자리를 표시할 수 있습니다.

g.V('v-1') .outE('KNOWS') .where(inV().hasId('v-2')) .fold() .coalesce(unfold(), addE('KNOWS').from(V('v-1')) .to(V('v-2'))) .id()

단, where() 절의 버텍스 단계는 otherV()가 아닌 inV()(또는 엣지를 찾는 데 inE()를 사용한 적이 있다면 outV())여야 합니다. 여기서는 otherV()를 사용하지 마세요. 사용하면 쿼리가 최적화되지 않아 성능이 저하될 수 있습니다. 예를 들어, Neptune은 다음 쿼리를 최적화하지 않습니다.

// Unoptimized upsert, because of otherV() g.V('v-1') .outE('KNOWS') .where(otherV().hasId('v-2')) .fold() .coalesce(unfold(), addE('KNOWS').from(V('v-1')) .to(V('v-2'))) .id()

가장자리나 꼭지점을 정확히 모르는 경우 꼭짓점 속성을 IDs 사용하여 상향 조정할 수 있습니다.

g.V() .hasLabel('Person') .has('name', 'person-1') .outE('LIVES_IN') .where(inV().hasLabel('City').has('name', 'city-1')) .fold() .coalesce(unfold(), addE('LIVES_IN').from(V().hasLabel('Person') .has('name', 'person-1')) .to(V().hasLabel('City') .has('name', 'city-1'))) .id()

버텍스 업서트와 마찬가지로, Neptune이 업서트를 완전히 최적화할 수 있도록 속성 기반 IDs 업서트보다는 엣지 ID 또는 fromto 버텍스를 사용하는 ID 기반 엣지 업서트를 사용하는 것이 좋습니다.

fromto 버텍스 존재 여부 확인

addE().from().to()와 같이 새 엣지를 만드는 단계의 구성을 참고하세요. 이 구성을 통해 쿼리는 fromto 버텍스의 존재 여부를 모두 확인할 수 있습니다. 둘 중 하나가 존재하지 않는 경우 쿼리는 다음과 같은 오류를 반환합니다.

{ "detailedMessage": "Encountered a traverser that does not map to a value for child... "code": "IllegalArgumentException", "requestId": "..." }

from 또는 to 버텍스가 존재하지 않을 가능성이 있는 경우 버텍스 사이의 엣지를 업서트하기 전에 버텍스를 업서트해야 합니다. 버텍스 업서트와 엣지 업서트의 결합을 참조하세요.

V().addE().to()와 같이 사용하지 말아야 할 엣지를 만들 수 있는 다른 방법이 있습니다. from 버텍스가 있는 경우에만 엣지를 추가합니다. to 버텍스가 존재하지 않는 경우 앞에서 설명한 것처럼 쿼리에서 오류가 발생하지만, from 버텍스가 없으면 오류가 발생하지 않고 엣지 삽입이 자동으로 실패합니다. 예를 들어, 다음 업서트는 from 버텍스가 없는 경우 엣지를 업서트하지 않고 완료합니다.

// Will not insert edge if from vertex does not exist g.V('v-1') .outE('KNOWS') .where(inV().hasId('v-2')) .fold() .coalesce(unfold(), V('v-1').addE('KNOWS') .to(V('v-2'))) .id()

엣지 업서트 연결

엣지 업서트를 연결하여 배치 요청을 만들려면 이미 에지를 알고 있더라도 각 업서트를 버텍스 검색으로 시작해야 합니다. IDs

업서트하려는 from 간선과 IDs to 꼭지점의 위치를 이미 알고 있다면 다음 공식을 사용할 IDs 수 있습니다.

g.V('v-1') .outE('KNOWS') .hasId('e-1') .fold() .coalesce(unfold(), V('v-1').addE('KNOWS') .to(V('v-2')) .property(id, 'e-1')) .V('v-3') .outE('KNOWS') .hasId('e-2').fold() .coalesce(unfold(), V('v-3').addE('KNOWS') .to(V('v-4')) .property(id, 'e-2')) .V('v-5') .outE('KNOWS') .hasId('e-3') .fold() .coalesce(unfold(), V('v-5').addE('KNOWS') .to(V('v-6')) .property(id, 'e-3')) .id()

아마도 가장 일반적인 배치 엣지 업서트 시나리오는 fromto IDs 꼭짓점은 알지만 업서트하려는 엣지는 모르는 경우일 것입니다. IDs 이 경우 다음 공식을 사용하세요.

g.V('v-1') .outE('KNOWS') .where(inV().hasId('v-2')) .fold() .coalesce(unfold(), V('v-1').addE('KNOWS') .to(V('v-2'))) .V('v-3') .outE('KNOWS') .where(inV().hasId('v-4')) .fold() .coalesce(unfold(), V('v-3').addE('KNOWS') .to(V('v-4'))) .V('v-5') .outE('KNOWS') .where(inV().hasId('v-6')) .fold() .coalesce(unfold(), V('v-5').addE('KNOWS').to(V('v-6'))) .id()

삽입하고 싶은 모서리는 IDs 알지만 from to 꼭지점과 꼭짓점을 모르는 경우 (일반적이지 않음) 다음 공식을 사용할 수 있습니다. IDs

g.V() .hasLabel('Person') .has('email', 'person-1@example.org') .outE('KNOWS') .hasId('e-1') .fold() .coalesce(unfold(), V().hasLabel('Person') .has('email', 'person-1@example.org') .addE('KNOWS') .to(V().hasLabel('Person') .has('email', 'person-2@example.org')) .property(id, 'e-1')) .V() .hasLabel('Person') .has('email', 'person-3@example.org') .outE('KNOWS') .hasId('e-2') .fold() .coalesce(unfold(), V().hasLabel('Person') .has('email', 'person-3@example.org') .addE('KNOWS') .to(V().hasLabel('Person') .has('email', 'person-4@example.org')) .property(id, 'e-2')) .V() .hasLabel('Person') .has('email', 'person-5@example.org') .outE('KNOWS') .hasId('e-1') .fold() .coalesce(unfold(), V().hasLabel('Person') .has('email', 'person-5@example.org') .addE('KNOWS') .to(V().hasLabel('Person') .has('email', 'person-6@example.org')) .property(id, 'e-3')) .id()

버텍스 업서트와 엣지 업서트의 결합

버텍스와 버텍스를 연결하는 엣지를 모두 업서트해야 하는 경우가 있을 수 있습니다. 여기에 제시된 배치 예제를 혼합하여 사용할 수 있습니다. 다음 예제에서는 버텍스 3개와 엣지 2개를 업서트합니다.

g.V('p-1') .fold() .coalesce(unfold(), addV('Person').property(id, 'p-1') .property('email', 'person-1@example.org')) .V('p-2') .fold() .coalesce(unfold(), addV('Person').property(id, 'p-2') .property('name', 'person-2@example.org')) .V('c-1') .fold() .coalesce(unfold(), addV('City').property(id, 'c-1') .property('name', 'city-1')) .V('p-1') .outE('LIVES_IN') .where(inV().hasId('c-1')) .fold() .coalesce(unfold(), V('p-1').addE('LIVES_IN') .to(V('c-1'))) .V('p-2') .outE('LIVES_IN') .where(inV().hasId('c-1')) .fold() .coalesce(unfold(), V('p-2').addE('LIVES_IN') .to(V('c-1'))) .id()

업서트와 삽입 혼합

버텍스와 버텍스를 연결하는 엣지를 모두 업서트해야 하는 경우가 있을 수 있습니다. 여기에 제시된 배치 예제를 혼합하여 사용할 수 있습니다. 다음 예제에서는 버텍스 3개와 엣지 2개를 업서트합니다.

업서트는 일반적으로 한 번에 한 요소씩 진행됩니다. 여기에 제시된 업서트 패턴을 고수하면 각 업서트 작업에서 Traverser가 하나만 생성되어 후속 작업이 한 번만 실행됩니다.

하지만 업서트와 삽입을 섞어서 사용해야 하는 경우도 있습니다. 예를 들어, 엣지를 사용하여 동작이나 이벤트의 인스턴스를 나타내는 경우가 이에 해당합니다. 요청에서는 업서트를 사용하여 필요한 버텍스가 모두 존재하는지 확인한 다음, 삽입을 통해 엣지를 추가할 수 있습니다. 이런 요청 유형의 경우 각 작업에서 발생할 수 있는 잠재적 Traverser 수에 유의하세요.

업서트와 삽입을 혼합하여 이벤트를 나타내는 엣지를 그래프에 추가하는 다음 예제를 생각해 보세요.

// Fully optimized, but inserts too many edges g.V('p-1') .fold() .coalesce(unfold(), addV('Person').property(id, 'p-1') .property('email', 'person-1@example.org')) .V('p-2') .fold() .coalesce(unfold(), addV('Person').property(id, 'p-2') .property('name', 'person-2@example.org')) .V('p-3') .fold() .coalesce(unfold(), addV('Person').property(id, 'p-3') .property('name', 'person-3@example.org')) .V('c-1') .fold() .coalesce(unfold(), addV('City').property(id, 'c-1') .property('name', 'city-1')) .V('p-1', 'p-2') .addE('FOLLOWED') .to(V('p-1')) .V('p-1', 'p-2', 'p-3') .addE('VISITED') .to(V('c-1')) .id()

쿼리는 5개의 엣지 (엣지 2개FOLLOWED, 엣지 3개) 를 삽입해야 합니다. VISITED 그러나 작성된 쿼리는 8개의 모서리 (FOLLOWED2와 6) 를 VISITED 삽입합니다. 그 이유는 두 개의 모서리를 삽입하는 작업에서 두 개의 트래버서가 발생하여 세 개의 FOLLOWED 가장자리를 삽입하는 후속 삽입 작업이 두 번 실행되기 때문입니다.

잠재적으로 2개 이상의 Traverser를 내보낼 수 있는 각 작업 뒤에 fold() 단계를 추가하면 문제를 해결할 수 있습니다.

g.V('p-1') .fold() .coalesce(unfold(), addV('Person').property(id, 'p-1') .property('email', 'person-1@example.org')) .V('p-2') .fold() .coalesce(unfold(), addV('Person').property(id, 'p-2'). .property('name', 'person-2@example.org')) .V('p-3') .fold() .coalesce(unfold(), addV('Person').property(id, 'p-3'). .property('name', 'person-3@example.org')) .V('c-1') .fold(). .coalesce(unfold(), addV('City').property(id, 'c-1'). .property('name', 'city-1')) .V('p-1', 'p-2') .addE('FOLLOWED') .to(V('p-1')) .fold() .V('p-1', 'p-2', 'p-3') .addE('VISITED') .to(V('c-1')). .id()

여기서는 모서리를 삽입하는 작업 뒤에 fold() 단계를 삽입했습니다. FOLLOWED 그 결과 단일 Traverser가 생성되어 후속 작업이 한 번만 실행됩니다.

이 접근 방식의 단점은 fold()가 최적화되지 않아 쿼리가 완전히 최적화되지 않았다는 것입니다. fold() 이후 발생하는 삽입 작업은 이제 최적화되지 않습니다.

후속 단계를 대신하여 Traverser 수를 줄이는 데 fold()를 사용해야 하는 경우 가장 비용이 적게 드는 작업이 쿼리에서 최적화되지 않은 부분을 차지하도록 작업을 정렬해 보세요.

기존 버텍스와 엣지를 수정하는 업서트

버텍스나 엣지가 없는 경우 새것인지 기존에 있던 것인지 관계없이 버텍스나 엣지를 새로 만들어 속성을 추가하거나 업데이트하고 싶을 때가 있습니다.

속성을 추가 또는 수정하려면 property() 단계를 사용하세요. 이 단계는 coalesce() 단계 외부에서 사용하세요. coalesce() 단계 내 기존 버텍스 또는 엣지의 속성을 수정하려고 하면 Neptune 쿼리 엔진에서 쿼리를 최적화하지 못할 수 있습니다.

다음 쿼리는 업서트된 각 버텍스의 카운터 속성을 추가하거나 업데이트합니다. 각 property() 단계에는 단일 카디널리티가 적용되므로, 새 값이 기존 값 세트에 추가되지 않고 기존 값을 모두 대체합니다.

g.V('v-1') .fold() .coalesce(unfold(), addV('Person').property(id, 'v-1') .property('email', 'person-1@example.org')) .property(single, 'counter', 1) .V('v-2') .fold() .coalesce(unfold(), addV('Person').property(id, 'v-2') .property('email', 'person-2@example.org')) .property(single, 'counter', 2) .V('v-3') .fold() .coalesce(unfold(), addV('Person').property(id, 'v-3') .property('email', 'person-3@example.org')) .property(single, 'counter', 3) .id()

업서트된 모든 요소에 적용되는 속성값(예: lastUpdated 타임스탬프 값)이 있는 경우 쿼리가 끝날 때 이를 추가하거나 업데이트할 수 있습니다.

g.V('v-1') .fold() .coalesce(unfold(), addV('Person').property(id, 'v-1') .property('email', 'person-1@example.org')) .V('v-2'). .fold(). .coalesce(unfold(), addV('Person').property(id, 'v-2') .property('email', 'person-2@example.org')) .V('v-3') .fold() .coalesce(unfold(), addV('Person').property(id, 'v-3') .property('email', 'person-3@example.org')) .V('v-1', 'v-2', 'v-3') .property(single, 'lastUpdated', datetime('2020-02-08')) .id()

버텍스나 엣지를 추가로 수정해야 하는지 여부를 결정하는 추가 조건이 있는 경우 has() 단계를 사용하여 수정이 적용될 요소를 필터링할 수 있습니다. 다음 예제에서는 has() 단계를 사용하여 version 속성값을 기준으로 업서트된 버텍스를 필터링합니다. 그런 다음 쿼리는 version이 3보다 작은 모든 버텍스의 version을 3으로 업데이트합니다.

g.V('v-1') .fold() .coalesce(unfold(), addV('Person').property(id, 'v-1') .property('email', 'person-1@example.org') .property('version', 3)) .V('v-2') .fold() .coalesce(unfold(), addV('Person').property(id, 'v-2') .property('email', 'person-2@example.org') .property('version', 3)) .V('v-3') .fold() .coalesce(unfold(), addV('Person').property(id, 'v-3') .property('email', 'person-3@example.org') .property('version', 3)) .V('v-1', 'v-2', 'v-3') .has('version', lt(3)) .property(single, 'version', 3) .id()