Amazon Web Services ブログ

Amazon Neptune が TinkerPop 3.4 機能をサポートするようになりました

Amazon Neptune は、Apache TinkerPop 3.4.1 のリリースをサポートするようになりました。この記事では、テキスト述語、valueMap の変更、ネストされた繰り返しステップ、名前付き繰り返しステップ、非数値比較、順序の変更ステップなど、Gremlin クエリとトラバーサル言語の新機能の具体例を紹介します。TinkerPop 3.4 には TinkerPop 3.3 との違いがほとんどないことに注意してください。エンジンリリースの互換性に関する注意事項を必ず確認してください。

エンジンのすべての最新機能と改善点は、Amazon Neptune リリースページに記載されています。

テストクラスターのセットアップ

以下の手順に従って、この記事の例を試すことができます。この記事は、以前に投稿した「Amazon SageMaker Jupyter ノートブックを使用して Amazon Neptune グラフを分析する」と「Let Me Graph That For You – Part 1 – Air Routes」の 2 本の記事に基づいており、航空路データセットを再度利用しています。

この例で使用する航空路データは、GitHub (こちら) で利用できます。

以下に示す例では、Gremlin Python が 3.4 レベル以上である必要があります。以前の投稿の AWS CloudFormation テンプレートを使用してノートブックのセットと Amazon SageMaker インスタンスを生成した場合、ターミナルウィンドウ (ノートブック内) または %% bash のプレフィックス付きのノートブックセルからこのコマンドを実行している Gremlin Python のレベルを更新する必要があります。

/home/ec2-user/anaconda3/bin/python3 -m  pip install --target 
/home/ec2user/anaconda3/envs/python3/lib/python3.6/site-packages/ gremlinpython

以前からインスタンスを保持していた場合にのみ、これを行う必要があります。AWS CloudFormation スクリプトを再度実行する場合、最新の Gremlin Python ライブラリがインストールされます。次に、必要なクラスをインポートして、Neptune クラスターへの接続を確立しましょう。

主な GremlinPython クラスをインポートする

Python コードから Neptune インスタンスに接続する前に、ライブラリからいくつかのクラスをインポートする必要があります。Python には、Gremlin クエリステップと同じ名前の予約語が多数あるため、Gremlin Python を使用する場合、必要に応じてこのステップにアンダースコア文字を後置する必要があります。たとえば、Gremlin in () ステップは in _ () と記述します。

In [34]:from gremlin_python import statics
		from gremlin_python.structure.graph import Graph
		from gremlin_python.process.graph_traversal import __
		from gremlin_python.process.strategies import *
		from gremlin_python.process.traversal import *
		from gremlin_python.driver.driver_remote_connection import DriverRemoteConnection

Neptune インスタンスへのアクセスを確立する

グラフを操作する前に、グラフへの接続を確立する必要があります。これは、Apache TinkerPop で定義され、GremlinPython でサポートされている DriverRemoteConnection 機能を使用して行います。このセルが実行されると、変数 g を使用して、後続のセルの Gremlin クエリでグラフを参照できます。デフォルトでは、Neptune はポート 8182 を使用します。これは以下で接続するものです。独自の Neptune インスタンスを設定する場合、別のポート番号を選択できます。その場合、以下の例のポート 8182 を設定したポートに置き換えます。

 [73]:# エンドポイントへの完全なウェブソケットパスを含む文字列を作成します
		# <your-neptune-instance-name> を Neptune インスタンスの名前と交換します。
		#「myinstance.us-east-1.neptune.amazonaws.com」という形式になります

		#neptune_endpoint = '<your-neptune-instance-name>'
		neptune_gremlin_endpoint = 'wss://' + neptune_endpoint + ':8182/gremlin'

		# リモートエンドポイントのグラフトラバーサルソースオブジェクトを取得します。
		graph = Graph()
		g = graph.traversal()\
		.withRemote(DriverRemoteConnection(neptune_gremlin_endpoint,'g'))

接続をテストする

Neptune への接続が動作するかを確認するために、簡単なクエリから始めましょう。以下のクエリは、グラフ内のすべての頂点とエッジを調べ、グラフの集約を示す 2 つのマップを作成します。航空路データセットを使用しているため、返された値は空港とルートに関連しています。これらの値を使用して、この記事に含まれる他の例の結果を確認できます。

In [73]:vertices = g.V().groupCount().by(T.label).toList()
		edges = g.E().groupCount().by(T.label).toList()
		print(vertices)
		print(edges)

		[{'continent': 7, 'country': 237, 'test999': 1, 'version': 1, 'airport': 3442}]
		[{'contains': 6884, 'route': 49879}]

テキスト述語

多くのお客様が、より集中的なテキスト検索を可能にする TinkerPop 3.4 の新しい「述語」機能に興奮しています。これは、アプリケーションを構築していて、トラバーサルがプロパティのテキスト値を利用するようにしたい場合にうまく機能します。たとえば、「Dal」で始まるすべての都市を見つけてください。合計で、6 つの新しい述語が追加されました。

  • startingWith
  • endingWith
  • containing
  • notStartingWith
  • notEndingWith
  • notContaining

これらの新しい述語はすべて大文字と小文字が区別されます。

startingWith

以下の例では、「dal」で始まる名前の都市を検索します。dedup ステップは、重複する名前を取り除くために使用します。

In [4]:g.V().hasLabel('airport')\
		.has('city',TextP.startingWith('Dal'))\
		.values('city').dedup().toList()
Out[4]:['Dalat', 'Dallas', 'Dalcahue', 'Dalaman', 'Dalian', 'Dalanzadgad']

テキストの述語では大文字と小文字が区別されるため、「Dal」で始まる名前の都市を検索しても、何も見つかりません。

In [5]:g.V().hasLabel('airport').has('city',startingWith('dal')).count().next()
Out[5]:0

「Dal」または「dal」の両方を確認する場合は、以下に示すように or ステップと 2 つの has ステップを使用して確認できます。

In [16]:g.V().hasLabel('airport')\
		.or_(__.has('city',startingWith('dal')),\
		__.has('city',startingWith('Dal')))\
		.dedup().by('city')\
		.count()\
		.next()
Out[16]:6

notStartingWith

各テキスト述語には逆のステップがあります。notStartingWith ステップを使用して、「Dal」で始まらない都市名を検索できます。

In [7]:g.V().hasLabel('airport').has('city',notStartingWith('Dal')).count().next()
Out[7]:3434

上記の例は、以下に示すように startingWith ステップを無効にする場合と同じ結果を返します。

In [8]:g.V().hasLabel('airport').not_(__.has('city',startingWith('Dal'))).count().nex t()
Out[8]:3434

endingWith

次の例では、「zhi」という文字で終わる都市名を検索します。

In [9]:g.V().hasLabel('airport').has('city',endingWith('zhi')).values('city').toList ()
Out[9]:['Changzhi']

notEndingWith

notEndingWith を使用すると、名前が「zhi」で終わっていない都市を簡単に見つけることができます。

In [10]:g.V().hasLabel('airport').has('city',notEndingWith('zhi')).count().next()
Out[10]:3440

containing

名前に特定の文字列が含まれる都市を検索することもできます。以下の例では、名前に文字列「gzh」が含まれる都市を検索します。

In [11]:g.V().hasLabel('airport').has('city',containing('gzh'))\
		.values('city').toList()
Out[11]:['Zhengzhou',
		'Guangzhou',
		'Yongzhou',
		'Hangzhou',
		'Changzhou',
		'Yangzho',
		'Changzhi']

notContaining

以下の例は、英語で一般的に使用される基本的な小文字の母音を含まない名前を持つ都市を見つけるために、notContaining 述語を使用していくつかの has ステップをつなぎ合わせています

In [11]:g.V().hasLabel('airport')\
		.has('city',notContaining('e'))\
		.has('city',notContaining('a'))\
		.has('city',notContaining('i'))\
		.has('city',notContaining('u'))\
		.has('city',notContaining('o'))\
		.values('city')\
		.dedup()\
		.toList()
Out[11]:['Orsk',
		'Łódź',
		'Røst',
		'Iğdır',
		'Osh',
		'Kyzyl',
		'Omsk',
		'Växjö',
		'Brønnøy',
		'Mörön',
		'Årø']

valueMap の変更

Apache TinkerPop 3.4 では、valueMap ステップの使用方法に変更が加えられ、新しい機能が導入されました。一般的に、valueMap ステップは、以下に示すようにキーと値のペアのセットを返します。デフォルトでは、すべての値はリストのメンバーとして表示されます。これは、TinkerPop の以前のバージョンで見られたのと同じ動作です。

In [13]:g.V().has('code','AUS').valueMap().next()
Out[13]:{'country': ['US'],
		'code': ['AUS'],
		'longest': [12250],
		'city': ['Austin'],
		'elev': [542],
		'icao': ['KAUS'],
		'lon': [-97.6698989868164],
		'runways': [2],
		'type': ['airport'],
		'region': ['US-TX'],
		'lat': [30.1944999694824],
		'desc': ['Austin Bergstrom International Airport']}

TinkerPop 3.4 では、新しい述語を使用して、リストに値を表示せずに valueMap ステップの結果を返す機能を追加しました。

In [14]:g.V().has('code','AUS').valueMap().by(__.unfold()).next()
Out[14]:{'code': 'AUS',
		'type': 'airport',
		'desc': 'Austin Bergstrom International Airport',
		'country': 'US',
		'longest': 12250,
		'city': 'Austin',
		'lon': -97.6698989868164,
		'elev': 542,
		'icao': 'KAUS',
		'region': 'US-TX',
		'runways': 2,
		'lat': 30.1944999694824}

以前のリリースと同様に、関心のあるプロパティキーをより具体的にし、結果を展開 (unfold) することができます。これは、トラバーサルで最高のパフォーマンスを得るためのベストプラクティスです。

In [15]:g.V().has('code','AUS').valueMap('icao').by(__.unfold()).next()
Out[15]:{'icao': 'KAUS'}

特定のキーを選択 (select) して、キー名が関連付けられていない値のみを返すこともできます。

In [16]:g.V().has('code','AUS').valueMap().by(__.unfold()).select('icao').next()
Out[16]:'KAUS'

Apache TInkerPop 3.4 以前では、頂点またはエッジの ID とラベルを valueMap の結果に含めるには、以下に示すように valueMap (true) 構造を使用します。

In [17]:g.V().has('code','AUS').valueMap(True).toList()
Out[17]:[{'country': ['US'],
		'code': ['AUS'],
		'longest': [12250],
		'city': ['Austin'],
		<T.label: 3>: 'airport',
		<T.id: 1>: '3',
		'lon': [-97.6698989868164],
		'type': ['airport'],
		'elev': [542],
		'icao': ['KAUS'],
		'runways': [2],
		'region': ['US-TX'],
		'lat': [30.1944999694824],
		'desc': ['Austin Bergstrom International Airport']}]

valueMap (true) の使用は非推奨になりました。代わりに、新しい with ステップでは、WithOptions 列挙を使用して、返されるものを指定できます。

In [18]:g.V().has('code','AUS').valueMap().with_(WithOptions.tokens).toList()
Out[18]:[{'country': ['US'],
		'code': ['AUS'],
		'longest': [12250],
		'city': ['Austin'],
		<T.label: 3>: 'airport',
		<T.id: 1>: '3',
		'lon': [-97.6698989868164],
		'type': ['airport'],
		'elev': [542],
		'icao': ['KAUS'],
		'runways': [2],
		'region': ['US-TX'],
		'lat': [30.1944999694824],
		'desc': ['Austin Bergstrom International Airport']}]

結果は、前の例のように展開できます。

In [19]:g.V().has('code','AUS').valueMap().by(__.unfold())\
		.with_(WithOptions.tokens).toList()
Out[19]:[{<T.id: 1>: '3',
		<T.label: 3>: 'airport',
		'code': 'AUS',
		'type': 'airport',
		'desc': 'Austin Bergstrom International Airport',
		'country': 'US',
		'longest': 12250,
		'city': 'Austin',
		'lon': -97.6698989868164,
		'elev': 542,
		'icao': 'KAUS',
		'region': 'US-TX',
		'runways': 2,
		'lat': 30.1944999694824}]

コレクションに数値インデックスを追加する

新しい index ステップにより、fold ステップの結果など、コレクションであるものはすべて、コレクション内の各エントリに関連付けられた数字のインデックス値を持つことができます。最初のインデックス値は常にゼロで、増分は常に 1 です。

In [18]:g.V().has('airport','region','US-TX').limit(5)\
		.values('code')\
		.fold().index()\
		.next()
Out[18]:[['AFW', 0], ['DRT', 1], ['AUS', 2], ['DFW', 3], ['IAH', 4]]

with ステップを使用して、作成されるインデックスのタイプを制御できます。デフォルトは list ですが、インデックス付きの値を map として返すように要求することもできます。インデックスはマップのキーであり、元の値はそのキーに対してマップされます。

In [19]:g.V().has('airport','region','US-TX').limit(5)\
		.values('code')\
		.fold().index().with_(WithOptions.indexer,WithOptions.map)\
		.next()
Out[19]:{0: 'AFW', 1: 'DRT', 2: 'AUS', 3: 'DFW', 4: 'IAH'}

list はデフォルトのインデックスモードですが、with ステップを使用して明示的に要求できます。

In [20]:g.V().has('airport','region','US-TX').limit(5)\
		.values('code')\
		.fold().index().with_(WithOptions.indexer,WithOptions.list)\
		.next()
Out[20]:[['AFW', 0], ['DRT', 1], ['AUS', 2], ['DFW', 3], ['IAH', 4]]

インデックス値はクエリからアクセスできます。次の例では、インデックス値を使用して、結果を逆の順序で返します。

In [24]:g.V().has('airport','region','US-TX').limit(5)\
		.values('code')\
		.fold().index()\
		.unfold().order().by(__.tail(Scope.local,1),Order.desc)\
		.toList()
Out[24]:[['IAH', 4], ['DFW', 3], ['AUS', 2], ['DRT', 1], ['AFW', 0]]

以下の例は、group ステップによって生成された結果にインデックスステップを適用します。

In [23]:g.V().has('region',P.within('US-NM','US-OK','US-AR'))\
		.group().by('region').by('city')\
		.index()\
		.next()
Out[23]:[[{'US-AR': ['Harrison',
		'Little Rock',
		'Texarkana',
		'Fort Smith',
		'El Dorado',
		'Hot Springs',
		'Jonesboro',
		'Fayetteville/Springdale/']},
		0],
		[{'US-NM': ['Clovis',
		'Santa Fe',
		'Albuquerque',
		'Carlsbad',
		'Los Alamos',
		'Roswell',
		'Farmington',
		'Hobbs',
		'Silver City']},
		1],
		[{'US-OK': ['Oaklahoma City', 'Tulsa', 'Stillwater', 'Lawton']}, 2]]

ネストされた繰り返し手順

Gremlin の repeat ステップは、他の repeat ステップ内または emit および until ステップ内にネストできるようになりました。以下の例は、オースティン空港から始まり、1 回トラバースし、見つかった各空港について、深さ 2 までの到着ルートを調べます。

In [59]:paths = (g.V().has('code','AUS').
		repeat(__.out('route').
		repeat(__.in_('route')).times(2)).
		times(1).
		path().by('code').
		limit(10).
		toList())
		for p in paths:
		str = '{} -> {} <- {} <- {}'.format(p[0],p[1],p[2],p[3])
		print(str)
	
		AUS -> ATL <- SLC <- LAS
		AUS -> ATL <- SLC <- DEN
		AUS -> ATL <- SLC <- SAT
		AUS -> ATL <- SLC <- MSY
		AUS -> ATL <- SLC <- CDG
		AUS -> ATL <- SLC <- RSW
		AUS -> ATL <- SLC <- MKE
		AUS -> ATL <- SLC <- MDW
		AUS -> ATL <- SLC <- OMA
		AUS -> ATL <- SLC <- TUL

名前付き繰り返し手順

ネストするだけでなく、各 repeat ステップにオプションの名前を付けることができるようになりました。これにより、後で loops ステップ内で参照できます。以下の例は、名前付きの repeat ステップが使用されていることを示しています。この特定のケースでは、命名は省略できますが、これは機能を示しています。repeat ステップに名前を付ける機能は、そのステップもネストされている場合を対象としています。

In [59]:paths = (g.V('3').repeat('r1',__.out().simplePath()).
		until(__.loops('r1').is_(3)).
		path().by('code').
		limit(3).
		toList())
		for p in paths:
		str = '{} -> {} -> {}'.format(p[0],p[1],p[2])
		print(str)

		AUS -> ATL -> BNA
		AUS -> ATL -> BNA
		AUS -> ATL -> BNA

非数値比較

TinkerPop 3.4 より前は、min および max ステップは数値にのみ適用できました。これらのステップは、テキスト文字列など、「同等」と見なされるあらゆるものに適用できるようになりました。これは、結果セットを順序付けて最初または最後の値を選択するよりも少し簡単です。

In [67]:g.V().hasLabel('continent').values('desc').order().toList()
Out[67]:['Africa',
		'Antarctica',
		'Asia',
		'Europe',
		'North America',
		'Oceania',
		'South America']
In [69]:g.V().hasLabel('continent').values('desc').order().limit(1).next()
Out[69]:'Africa'
In [70]:g.V().hasLabel('continent').values('desc').order().tail(1).next()
Out[70]:'South America'
In [71]:g.V().hasLabel('continent').values('desc').min().next()
Out[71]:'Africa'
In [72]:g.V().hasLabel('continent').values('desc').max().next()
Out[72]:'South America'

順序の変更

以前の Order.incr および Order.decr 列挙は、Order.asc および Order.desc を優先させて廃止されました。この変更により、Gremlin の用語は他のデータベースのクエリ言語との一貫性が高まりました。この変更は TinkerPop 3.4 より前にリリースされましたが、現在 Amazon Neptune でもサポートされています。

In [90]:g.V().has('region','US-NM').values('city').order().by(Order.asc).toList()
Out[90]:['Albuquerque',
		'Carlsbad',
		'Clovis',
		'Farmington',
		'Hobbs',
		'Los Alamos',
		'Roswell',
		'Santa Fe',
		'Silver City']
In [68]:g.V().has('region','US-NM').values('city').order().by(Order.desc).toList()
Out[68]:['Silver City',
		'Santa Fe',
		'Roswell',
		'Los Alamos',
		'Hobbs',
		'Farmington',
		'Clovis',
		'Carlsbad',
		'Albuquerque']

bulkSet の変更

TinkerPop 3.4 は、bulkSet を List タイプに強制するのではなく、GraphSON タイプとして追加します。以前は、TinkerPop3.4 ではクエリ結果がフラット化されたリストとしてシリアル化されていました。古い Gremlin クライアントは、変更を認識できない場合があります。TinkerPop 3.4 BulkSet documentation も詳細を呼び出しています。

まとめ

Amazon Neptune で Apache TinkerPop 3.4 リリースをサポートできることを嬉しく思います。上記の手順で説明したようにクラスターを作成し、ぜひ具体例を実行してみてください。この記事にコメントを残すことで、または Amazon Neptune の Discussion Forum からご意見をお聞かせください。

 

 


著者について

Kelvin Lawrence は、Amazon Neptune および他の多くの関連サービスに重点を置く、データベースサービス顧客顧問チームのプリンシパルデータアーキテクトです。彼は長年にわたりグラフデータベースを扱っており、本書 Practical Gremlin の著者であり、Apache TinkerPop プロジェクトのコミッターです。