Amazon Neptuneを使ってブログリーダー機能をリリースしました
ブログリーダー
先日、弊社運用サービスのにほんブログ村にてブログリーダー機能をリリース致しました。
アカウント登録だけで、様々なブログの新着記事が簡単に読めるようになりましたので是非ご活用下さい。
ブログリーダー機能ではAmazon Neptuneを利用しているので、今回はそちらについて書かせていただきます。
Amazon Neptune とは
2年ほど前にAWSでリリースされた、グラフDBと言われるグラフ構造を扱うためのサービス。
グラフDBでは頂点、エッジ、プロパティ、ラベル等の集合体を扱い様々な関係性を表現することが出来ます。
- 頂点 (vertex, ノード) - データエンティティ アカウント情報やブログ情報等
- エッジ (edge, 辺) - リレーションシップ 頂点間の関係性(フォロー、友達等)
- プロパティ(property) - 値 頂点やエッジの保持する値
- ラベル(label) - ラベル 頂点やエッジに付与する属性
上記のようなRDBでは表現しにくい構造を、グラフDBでは簡単に構築ができます。
グラフクエリ言語としては、GremlinとSPARQLをサポートしております。(後述)
選定理由
- RDBでは時間が掛かってしまうデータ構造でも、Neptuneで検証すると200ms程で取得可能
- Multi-AZ、フェイルオーバー、リードレプリカも対応しており高可用性とスケーラビリティを備えている
- データ構造によっては、レコメンデーション等の横展開が可能
- 自動でのクエリ最適化やインデックス作成
開発にあたって
Amazon Neptuneは今年の頭に東京リージョン対応され、日本での導入事例は少ないようで、試行錯誤しながらの開発になりました。
今後Neptuneを導入予定の方向けに、今回の開発で詰まった以下の3点について書いていきます。
- Gremlin って何?
- 開発環境はどうすればいいの?
- データ設計は?
Gremlin って何?
概要
Apache TinkerPopプロジェクトで開発されている、グラフデータベース用の操作言語(グラフトラバーサル言語)
頂点やエッジをステップと呼ばれる各段階を評価しながら探索します。
SPARQLというのもサポートされておりますが、弊社ではGremlinのほうがしっくりきたのでこちらにしました。
使い方
取得例
g.V() // 1, 2
.hasLabel("Account") // 3
.has("name", "john") // 4
.valueMap() .toList() // 5
- g: グラフにアクセスするためのトラバーサルオブジェクト
- V() :頂点を探索
- Accountラベルを所持
- nameプロパティがjohnであるか
- 該当頂点の全ての値をList形式で取得
メソッドチェーン形式で条件等を追加出来、そこまで違和感無く使えます。
頂点追加とエッジ作成例
g.addV("Blog") // 1, 2
.property(T.id, "blog:1") // 3
.property("createdAt", datetime("2019-07-11")) // 4
.addE("FOLLOW") // 5
.from("account:1") // 6
.to("blog:1") // 7
.next() // 8
- g: グラフにアクセスするためのトラバーサルオブジェクト
- Blogラベルを持った頂点を作成
- IDに blog:1 を追加
- createdAtプロパティに日付を追加
- FOLLOWラベルのエッジを作成
- IDがaccount:1 から
- IDがblog:1 の頂点にエッジを作る
- 実行
Amazon Neptuneでは各頂点やエッジにユニークのIDを指定することが出来、RDBでのプライマリーキーのような意味合いを持たせることが出来ます。
指定しないとUUIDが指定されます。
細かい仕様等については、公式やここが参考になります。
トランザクション
Neptune は、各 Gremlin トラバーサルの開始時に新しいトランザクションを開き、トラバーサルが正常に完了したときにトランザクションを閉じます。エラーが発生すると、トランザクションはロールバックされます。
tx.commit() および tx.rollback() を使用した手動トランザクションロジックはサポートされていません。
Neptune Gremlin 実装の相違点 - Amazon Neptune
Neptuneでは手動でのトランザクションが用意されておらず、自動でコミットやロールバックがされます。
整合性を保ちたいデータが有る場合、こちらのような形で一括登録やバルクロードを検討して下さい。
クエリヒント
探索アルゴリズムでは、DFS(深さ優先)、BFS(幅優先)がありますが、Neptuneでは基本BFSのようです。
クエリ最適化である程度切り分けされるかとは思いますが、一応明示的にDFSを指定出来ます。
Gremlin クエリヒント - Amazon Neptune
開発環境
ローカル
弊社の開発環境では、ローカルのDocker上にDB等を構築しているのですが、NeptuneはDockerイメージが存在しません。
代替としてOSSのグラフDBを検討したのですが、仕様を細かく見るとNeptuneとは細かい場所で違っており、問題が出そうでしたので断念。
NeptuneはVPC内に設置、SSLが必須となっておりローカルから接続は少々難があります。
結局あまり良い手が思いつかず、弊社では以下のように対応を致しました。
- 開発用Neptuneを起動
- DNSコンテナを作成し、Neptuneエンドポイントを127.0.0.1 に設定
- autosshで踏み台サーバー経由でNeptuneにポートフォワードするコンテナ作成 ポートは8182
- PCのDNSサーバーに127.0.0.1 を追加
このようにすることで、証明書のエラー等を回避しつつローカルからの接続を簡単に行えるようになりました。
検証環境等に繋ぎたい場合は、接続先のportを分ければ可能になります。
細かい設定等は機会がありましたら別記事に出来ればと思います。
接続
コンソール
Gremlin コンソールをセットアップして Neptune DB インスタンスに接続する - Amazon Neptune
AWS公式にもありますが、基本これで繋げることが出来ます。
Python
Python を使用して Neptune DB インスタンスに接続する - Amazon NeptunePythonに関しても、gremlinpythonパッケージをインポートすれば可能になります。
弊社では初期の挙動や検証時に、jupyter-notebook上で操作することが多かったです。
Java
Java を使用して Neptune DB インスタンスに接続する - Amazon Neptune
現在はgremlin-driverの3.4.1が推奨されていますが、以前は3.3.2でした。こっそり上がっているようです。
バージョンが上がることによりいくつかのメソッドが非推奨となっているのですが、Neptuneではそれでなくては動かない箇所がいくつかあります。
例えば、ソート条件にはdesc()があるのですが、非推奨のdecr()でないとエラーが起こりました。
org.apache.tinkerpop.gremlin.process.traversal.Order#desc(); # エラー
org.apache.tinkerpop.gremlin.process.traversal.Order#decr(); # 非推奨だけど動く
今後どの様になるのかは不明ですが、もし実装される方がいらっしゃればご注意下さい。
可視化ツール
GitHub - bricaud/graphexp: Interactive visualization of the Gremlin graph database with D3.js
実装時には可視化ツールを用いて、確認などを行いました。
Configuration に書かれているように、Neptuneでは少々の修正が必要となります。
接続もwssにすれば使えます。
データ構造
要件は、アカウントがブログをフォローしその先の記事情報を取得、とシンプル。
各エンティティを頂点とエッジに落とし込み、下記のような形に致しました。
2ブログをフォローした際のグラフになります。
大きい画像
アカウント→ブログ→サイト→記事
の形で頂点を作成しエッジも左から右への有向のみです。
この形であれば、レコメンドやタグ等の新機能にも柔軟に対応が出来ます。
ID
各頂点のIDは、blog:100 のように、redisの命名規則形式で、
エッジに関しては、site:10-author->post:100 の形でfrom→toで命名してます。
Neptuneではindexが自動で作成されるようですが、重複チェックやID指定時のスループットを意識して一意になるよう指定。
g.V("account:1").out("FOLLOW").out("OWN").out("AUTHOR").valueMap().toList()
もう少しプロパティ等は御座いますが、このようなクエリでフォロー先の記事情報が取得できました。頂点やエッジ数が多くなると、クエリによってはすごく重くなるので注意して下さい。ラベル
頂点、エッジ、プロパティで以下のように命名規則を設定
- 頂点:アッパーキャメルケース ex.) VehicleOwner
- エッジ : 大文字スネークケース ex.) OWNS_VEHICLE
- プロパティ:ローワーキャメルケース ex.) firstName
プロパティ変更
Neptune上のプロパティを変える場合
g.V("account:1")
.property(single, "name", "Alice")
.next()
single を付けないと、上書きされないので注意して下さい。
削除
論理削除は基本的に行わず、物理削除で対応しております。
レコメンド
上記のようなクエリで、自分がフォローしているブログを他にフォローしている人達の、フォロー先の総数が取得できます。
Amazonでよく表示される、「この商品を買った人はこの商品も買っています」の様な内容になります。
まとめ
- RDBだと尻込みするような処理が簡単に実装出来る
- Neptune単体で色々な表現が出来る
- 触ってて面白い
2017年のAWS SummitでAmazon Neptuneを知ってからずっと気になっていたので、今回使うことが出来て楽しかったです。
Neptune気になるけどよく分からないから断念。という話をたまに聞きますが、検証は簡単に行なえますので実際に触ってみて下さい。
併せて、にほんブログ村のブログリーダーも是非ご利用下さい。