以前のブログ記事で述べた通り、CG制作はグラフ構造として表現できます。映画はショットで構成されており、それらはシーンファイルから生成されます。そしてそのシーンファイルは、関係によって結び付けられた要素から成り立っています。それにもかかわらず、制作データをデータベースに保存するときには、データをフラットな形式で記述することが多いです。そしてデータベースを選ぶ段になると、最も一般的な選択肢はリレーショナルデータベースに頼ることです。
リレーショナルデータベースを使うのは良い選択です。安全で、きちんと目的を果たしてくれます。しかし現在では、いくつかのデータベース技術が、データをグラフとして直接フォーマットされた形で保存できるよう提案しています。最初は主にソーシャルネットワークや銀行のユースケースに使われていました。ですが、多くのCGスタジオのテクニカルディレクターや開発者の注目を集めたのは、驚くべきことではありません。グラフデータベースへの関心が高まっているため、私たちはそれらをより詳しく見ていくことにしました。
グラフの情報は、あなたをより機敏にします。グラフストレージによって、すべてのアセットの依存関係を保存し、ショットでキャストされる要素のバージョンを設定できます。そして、保存されたグラフは有向であるため、シーンの要素を構築・再構築するための一連の操作を簡単に計算できます。つまり、監督が新しいことを試したいときに、より高いリアクタビリティが得られます。
いまやグラフデータベースを使う十分な動機があります。そこで、市場で利用可能な主要なオープンソースのグラフデータベースを見ていきます。
使用例
これらのデータベースを探索するために、以前の記事 CG production as a Graph で説明した小道具アニメーションのデータグラフを実装することを提案します。方針は、小道具を作るために必要なステップを保存し、それを特定のショットに組み込むことです。
グラフで最もよくやりたいことは、ある要素に対する変更がもたらす影響をすべて取得することです。これを説明するために、小道具のメッシュに対する変更によって影響を受ける要素を取得するクエリを実行します。
各データベースの使い方を示すPythonスニペットを用意します。その後、簡単なベンチマークを実行します。i7–6700 CPU @ 3.40GHz 上で、サンプルクエリを1万回実行するのにどれくらい時間がかかるかを比較します。なお、このベンチマークにはPythonクライアントが含まれます。つまり、あなたがデータベースをクライアント経由でしか使わないと考え、そのため計測に含めています。
主要データベース
私たちが調べる主要なデータベースは以下の通りです:
Cayley
CayleyはGoで書かれ、Googleによって配布されているグラフデータベースです。多くの点で有望です(設定可能なバックエンド、コミュニティ主導など)。ただし現時点ではドキュメントがほとんど存在しません。とはいえ、何ができるのか見ていきましょう。
まず、あなたのプラットフォームに関連するバイナリをダウンロードし、データベースを初期化して、クエリを実行するのに使えるHTTPサーバーを起動します。データベースの初期化は、データを渡す必要があるという意味ではありません。単にデータベースファイルを作るだけです。../cayley init -db bolt -dbpath /tmp/testdb
./cayley http --dbpath=/tmp/testdb --host 0.0.0.0 --port 64210
ここで別のDB技術(Bolt)が関わっていることに気づくでしょう。Cayleyは既存のデータベースの上に構築されたレイヤーだからです。バックエンドとして、従来のキー・バリュー・ストアを使うことも、リレーショナルデータベースを使うこともできます。
ではPythonクライアントのコードに進みます。私たちは、すべてのアセット、シーン、ショット、そしてそれらの関係を保存したいのです。そのためには、Pythonドライバをインストールする必要があります:pip install pyley
Cayleyはトリプレット(3つ組)という概念に基づいています。すべてが別の頂点と結び付けられた頂点です。トリプレットは3つの頂点から構成されます。つまり、リンクしたい2つの要素と、リンクのための頂点(ある種のエッジ)です。各トリプレットにラベルを追加できるため、Cayleyではこのデータ構造の用語は「quads(クアッズ)」になります。
残念ながらPythonクライアントは完成しておらず、Quadの作成をサポートしていません。そこで、標準的なPython HTTPクライアント(CayleyはREST APIを提供しています)を使ってクアッドを作成する必要があります:def create_quad(quad):
path = “http://localhost:64210/api/v1/write"
return requests.post(path, json=[quad])
ではクアッドの作成に進みます:quads = [
{
“subject”: “props1-concept”,
“predicate”: “dependencyof”,
“object”: “props1-texture”
},
{
“subject”: “props1-concept”,
“predicate”: “dependencyof”,
“object”: “props1-mesh”
},
{
“subject”: “props1-texture”,
“predicate”: “dependencyof”,
“object”: “props1-model”
},
{
“subject”: “props1-mesh”,
“predicate”: “dependencyof”,
“object”: “props1-model”
},
{
“subject”: “props1-mesh”,
“predicate”: “dependencyof”,
“object”: “props1-rig”
},
{
“subject”: “props1-mesh”,
“predicate”: “dependencyof”,
“object”: “props1-keys”
}
{
“subject”: “props1-rig”,
“predicate”: “dependencyof”,
“object”: “props1-keys”
},
{
“subject”: “props1-model”,
“predicate”: “dependencyof”,
“object”: “shot1-image-sequence”
},
{
“subject”: “props1-keys”,
“predicate”: “dependencyof”,
“object”: “shot1-image-sequence”
}
]for quad in quads:
create_quad(quad)
以上です。ご覧の通り、すべてのデータはすでに保存され、互いの間の関係も設定されています。同様のクアッドをもう一度作成しても何も変わらず、重複も発生しません。
それでは、制作におけるリグ変更の影響に関するクエリを実行しましょう:from pyley import CayleyClient, GraphObject
client = CayleyClient("http://localhost:64210", "v1")graph = GraphObject()
query = graph.V(“props1-mesh”)
.Out()
.All()
目的のデータを得るために、どの頂点(ここではテクスチャ)の影響を調べたいのかを指定する必要がありました。次に、テクスチャが属している要素である外側の頂点を求めただけです。影響の深さに応じて呼び出しを連結できます。再帰的なトラバーサルも利用可能ですが、Pythonクライアントはまだそれを実装していません。最後に、パフォーマンステストを行いました。このクエリを1万回実行するのに50秒かかりました。
可視化のUIはうまく動かず、使い勝手もあまり良くありません。これは残念です。Neo4jとArangoには、グラフを表示できる動作するUIがあります。
Cayleyはとてもシンプルなデータベースです。クアッド表現という1つの概念で、データを表現できます。クエリも非常に簡単で、Gremlin などの標準的なグラフクエリ言語に基づいています(お気に入りのクエリ言語を選べます)。しかし、プロジェクトのドキュメントはまだ貧弱で、Pythonクライアントも不完全です。だからこそ、クリーンでシンプルな設計にもかかわらず、Cayleyを本番環境で使うことはおすすめできません。
Neo4j
Neo4jは、すべての中で最も成熟したソリューションです。背後にあるエンタープライズは、サポートのための説得力のある企業向けソリューションと、追加機能(監視、バックアップ、クエリの改善など)を提供しています。これは、クライアントとの厳格な契約があるなど、非常に安全である必要がある場合に大きな利点です。しかし始めるにあたっては、コミュニティ版を使うことをおすすめします。この記事では、そのバージョンを扱います。
私たちはまだ試行錯誤の段階なので、公式のDockerを使ってNeo4jを触ります:
docker run \ --publish=7474:7474 --publish=7687:7687 \ --volume=$HOME/neo4j/data:/data \ neo4j次に、Pythonドライバをインストールします:pip install neo4j-driver
まず最初に、データベースとの接続とクエリセッションを初期化しましょう。最初の接続時にはパスワードを設定するよう求められます。以下のスニペットの最後の行で設定できます:from neo4j.v1 import GraphDatabase, basic_authdriver = GraphDatabase.driver(
"bolt://localhost:7687",
auth=basic_auth("neo4j", "tests")
)
session = driver.session()
# session.run("CALL dbms.changePassword('tests')")
次に、アセットノード、ショットノード、関係のエッジを作成するためのヘルパーを追加します。Pythonクライアントは強力なAPIを提供していませんが、Neo4jが社内で名付けた言語Cypherを使って、直接リクエストを実行できるようになっています。CREATEコマンドもありますが、存在しない場合はCREATEとして動作するため、MERGEを使います:def create_asset(name):
session.run(
"MERGE (a:Asset { name: $name })",
name=name
)def create_shot(name):
session.run(
"MERGE (a:Shot { name: $name })",
name=name
)def create_relation(asset1, asset2):
session.run(
"MATCH (a:Asset { name: $asset1 }), (b:Asset { name: $asset2 })"
"MERGE (a)-[r:ELEMENT_OF]->(b)",
asset1=asset1, asset2=asset2
)def create_casting(asset, shot):
session.run(
"MATCH (a:Asset { name: $asset }), (b:Shot { name: $shot })"
"MERGE (a)-[r:CASTED_IN]->(b)",
asset=asset, shot=shot
)
ご覧の通り、構文は読みやすく、学びやすいです。1つのノードに好きなだけフィールドを追加できます。
これで関数ができました。ではグラフを投入します:create_asset("Props 1 concept")
create_asset("Props 1 mesh")
create_asset("Props 1 texture")
create_asset("Props 1 rig")
create_asset("Props 1 model")
create_asset("Props 1 keys")
create_shot("Shot 1")create_relation("Props 1 concept", "Props 1 texture")
create_relation("Props 1 concept", "Props 1 mesh")
create_relation("Props 1 mesh", "Props 1 model")
create_relation("Props 1 texture", "Props 1 model")
create_relation("Props 1 mesh", "Props 1 rig")
create_relation("Props 1 mesh", "Props 1 keys")
create_relation("Props 1 rig", "Props 1 keys")create_casting("Props 1 model", "Shot 1")
create_casting("Props 1 keys", "Shot 1")
次は、表現力のあるクエリ言語を活用してトラバーサルを行います。矢印の中の星(*)に注目してください。これは、これ以上外側への接続がなくなるまで、すべてのノードを辿ることを意味します。result = session.run(
"MATCH (:Asset { name: 'Props 1 mesh' })-[*]->(out)"
"RETURN out.name as name"
)for record in result:
print("%s" % record["name"])session.close()
完了です!結果のレコードは表示や分析が簡単です。作成時に指定したフィールドを含むPythonの辞書です。このリクエストを1万回実行するのに3.5秒かかりました(毎回セッションを開閉する場合は17秒に落ちます)。

総合的に、Neo4jは機能が充実していて、ちゃんと仕事をこなし、他のものよりも速いです。強力なクエリ言語と多くの機能により、グラフで扱うであろう最も一般的なユースケースを実行できます。公式のPythonクライアントはやや薄めですが、コミュニティが ORMのように作られたクライアント という興味深い代替案を提供しています。最後に、データベース自体は長い間存在しており、その企業も非常に活発です。だからこそ、Neo4jはこのレビューの中でより安全な選択肢になります。
注:Neo4jを1年使った実生活のフィードバックもあります。
ArangoDBの場合
ArangoDBは、ドキュメント保存とグラフ保存の両方を扱える汎用的なデータベースです。最近人気が高まってきたため、テストに含めました。クラウド基盤での簡単なデプロイや、REST APIを構築するためのヘルパーなど、便利な機能がいくつもあります。しかしこの記事では、グラフ保存とそのクエリシステムに焦点を当てます。
さっそくコードを書きましょう!テストを行うには、まずArangoのインスタンスを起動しておく必要があります。ここでもDockerを使って起動します:docker run -p 8529:8529 -e ARANGO_ROOT_PASSWORD=openSesame arangodb/arangodb:3.2.1
次にPythonクライアントをインストールします:pip install python-arango
次にPythonスクリプトを書きます。最初のステップはデータベースの初期化です:from arango.client import ArangoClientclient = ArangoClient(username='root', password='openSesame')
db = client.create_database('cgproduction')
ご覧の通り、データベース作成は非常にわかりやすいです。唯一の問題は、すでにデータベースが存在している場合に例外が発生することです。つまり、スクリプトで冪等性(何度実行しても同じ結果になる状態)を達成したいなら、自分で「get or create(取得または作成)」メソッドを書く必要があります。以下のすべての作成処理でも同じです。このPythonドライバを拡張する準備をしておきましょう。
次のステップは、グラフを定義し、頂点とエッジ情報を保存するコレクションを設定することです:dependencies = db.create_graph('dependencies')shots = dependencies.create_vertex_collection('shots')
assets = dependencies.create_vertex_collection('assets')casting = dependencies.create_edge_definition(
name='casting',
from_collections=['assets'],
to_collections=['shots']
)
elements = dependencies.create_edge_definition(
name='element',
from_collections=['assets'],
to_collections=['assets']
)
Arangoのグラフストレージは、自前のドキュメント保存システムに基づいています。各頂点は、コレクション内のjsonエントリとして保存されます。エッジは少し異なります。同様の方法で保存されますが、コレクション定義にはより多くの情報が必要です。内部の頂点コレクションと外部のコレクションです。エッジは常に有向です。
これでデータベースは正しく設定できたので、データを追加します:# 頂点を挿入
assets.insert(
{'_key': 'props1-concept', 'name': 'Props 1 Concept'})
assets.insert(
{'_key': 'props1-texture', 'name': 'Props 1 Texture'})
assets.insert(
{'_key': 'props1-mesh', 'name': 'Props 1 Mesh'})
assets.insert({'_key': 'props1-rig', 'name': 'Props 1 Rig'})
assets.insert({'_key': 'props1-model', 'name': 'Props 1 Model'})
assets.insert({'_key': 'props1-keys', 'name': 'Props 1 Keys'})
shots.insert(
{'_key': 'shot1-image-sequence',
'name': 'Shot 1 Image sequence'})# エッジを挿入
elements.insert(
{'_from': 'assets/props1-concept',
'_to': 'assets/props1-texture'})
elements.insert(
{'_from': 'assets/props1-concept',
'_to': 'assets/props1-mesh'})
elements.insert(
{'_from': 'assets/props1-texture',
'_to': 'assets/props1-model'})
elements.insert(
{'_from': 'assets/props1-mesh',
'_to': 'assets/props1-rig'})
elements.insert(
{'_from': 'assets/props1-mesh',
'_to': 'assets/props1-model'})
elements.insert(
{'_from': 'assets/props1-mesh',
'_to': 'assets/props1-keys'})
elements.insert(
{'_from': 'assets/props1-rig',
'_to': 'assets/props1-keys'})
casting.insert(
{'_from': 'assets/props1-model',
'_to': 'shots/shot1-image-sequence'})
casting.insert(
{'_from': 'assets/props1-keys',
'_to': 'shots/shot1-image-sequence'})
データが正しく取り込めたら、クエリに進みます:traversal_results = dependencies.traverse(
start_vertex=’assets/props1-mesh’,
direction=’outbound’
)for result in traversal_results[“vertices”]:
print(result[“name”])
このシンプルなリクエストで、props 1 meshの変更が及ぼす影響をすべて得られます。結果の分析は簡単で、クエリは設定可能です(例えば、深さ優先トラバーサルと幅優先トラバーサルのどちらかを選べます)。
Arangoは、特定のパスを構築できるトラバーサルオブジェクトを提供しています。最短経路の探索やパス長の取得などのヘルパーもあります。グラフクエリの観点で、たいていのニーズはカバーできるはずです。
最後に、ArangoのWeb UIでグラフを可視化できます:

総合的に、ArangoDBとPythonクライアントは理解しやすく、ドキュメントも整っています。グラフで遊ぶためのヘルパーが多く、可視化ツールのおかげでさらに簡単になります。しかし、neo4jより遅いように見えます。クエリを1万回実行するのに26秒かかりました。これらの結果にもかかわらず、このテストにおける私たちのお気に入りのデータベースです。Arangoは非常に開発者に優しいです。グラフデータベースを素早く試すには最適です。そして、その背後にある企業がとても活発に見えるため、本番利用においても安全な選択肢のように思えます。
OrientDB
OrientDBはもうしばらく前からあります(2010年から)。しかし、それに関する非常に悪いフィードバックがあるため(コメントも参照)、この記事ではこのデータベースを取り上げないことにしました。CG制作環境で使うにはリスクが大きすぎます。
代替案
まだ代替案はあります。従来のデータベースを使っていじることで、グラフデータベースと同様の機能を得られます。選択肢の1つは、再帰的な結合(recursive joins)を使ったPostgresです。これにより、グラフトラバーサルのシンプルなユースケースをカバーできます。
もう1つの選択肢は、曖昧検索(ファジー検索)をできるようにしたい場合にとても良さそうで、Elastic Searchを使い、すべての頂点とエッジをJSONドキュメントとして保存することです(ArangoDBと似たアプローチです)。この件について詳しく知るには、こちらの 詳細記事 を読んでください。
可視化
グラフデータがあるのは素晴らしいですが、どこかのタイミングで(また、組み込みのUIの外で)データを表示するツールを作りたくなるかもしれません。
Qtには、グラフを簡単に構築できる優れたライブラリが2つあります:
- ZodiacGraph:強力なC++ライブラリで、高速かつ柔軟です。
- Nodz:使いやすいPythonライブラリです。
別の選択肢として、ブラウザ内、あるいは Electron アプリケーション向けのJavaScriptライブラリを使う方法もあります。例はこちら:
まとめ
私たちの調査から見ると、ArangoDBは最もユーザーフレンドリーなデータベースであり、ドキュメント保存の側面により、制作データの管理がより簡単になるはずです。ただし、まだ若いDBでもあります。スピードが必要である場合、あるいはお金の面で大きなリスクがあり、より安全な選択肢を探しているなら、Neo4jを選ぶべきです。Neo4jは仕事をきちんとこなし、より堅牢に見えます。最後に、Cayleyは多くの面で良さそうで、すでにあるリレーショナルデータベースを補完する最良の選択になり得ますが、ドキュメントがまだ乏しく、若いため本番で使うには向いていません。つまり要約すると、まずArangoDBを試してみてください!
パイプラインTDにとって、グラフ表現と保存が解決する課題は何か、という問いは残っています。私たちの主要なユースケースは、変更が起きたときにショットを再構築するために必要な一連のアクションを簡単に生成することです。もう1つは、制作を人々が議論できる形で簡単に表現することです。
この記事を楽しんでいただければ幸いです。私たちはまだグラフデータベースについてはとても新しい段階です。ぜひ、この点についてあなたの意見を聞かせてください。そして、これらの技術での制作経験を読ませてください。コメント歓迎です!
このブログはCGパイプラインと制作管理に捧げられています。CG制作におけるグラフデータベースに興味があるなら、きっと私たちの記事をすべて楽しめるはずです。私たちの 最初のブログ記事を読んで、もっと私たちのことを知ってください!



