🎃

Rust+OxigraphでサーバレスSPARQLエンドポイント構築

2021/03/27に公開
4

これまで、AWS LambdaとAPI Gateway上でSPARQLエンドポイントを動かす試みをいくつか行ってきました。

https://qiita.com/uedayou/items/bdf7a802e27fe330044e

https://qiita.com/uedayou/items/18f968e3d07b5aa2db50

今回は、Rust製のOxigraphを使って、AWSサーバレス上で動くプログラムを作ってみました。

https://github.com/uedayou/oxigraph-sparql-api-serverless

ここでは、oxigraph-sparql-api-serverlessを紹介します。

SPARQLとは?

SPARQLはRDF用のクエリ言語です。SPARQLクエリで検索できるエンドポイントは、DBpediaWikidataなど、すでにさまざまなものが公開されています。
詳しくは以下を参照してください。

https://qiita.com/uedayou/items/9e4c6029a2cb6b76de9f

使い方

oxigraph-sparql-api-serverlessは、AWS Lambda関数部分をRustで実装しています。Dockerを使って Amazon Linux 2上でビルドし、AWS SAM CLIでAWSにデプロイします。

ビルド

SPARQLエンドポイントで検索したいRDFファイルをTurtle形式で準備してください。
dump.ttlにリネームして rdf/ディレクトリに上書きします。

以下のDockerコマンドでRustプログラムをビルドします。

$ cd oxigraph-sparql-api-serverless/
$ docker image build -t oxigraph-build -f Dockerfile.build .
$ docker container run --rm -v $PWD:/code -v $HOME/.cargo/registry:/root/.cargo/registry -v $HOME/.cargo/git:/root/.cargo/git oxigraph-build

ビルドが完了すると lambda.zip が生成されます。

デプロイ

AWS SDKやAWS SAM CLIのインストール、設定後以下を実行するとデプロイできます。

$ sam package --template-file template.yaml --output-template-file packaged.yaml --s3-bucket {デプロイ用S3バケット}
$ sam deploy --template-file packaged.yaml --stack-name {スタック名} --capabilities CAPABILITY_IAM

デプロイ後に表示されるURL(例えば:https://${ID}.execute-api.${Region}.amazonaws.com/Prod/)をコピーして以下のようにアクセスすれば、SPARQLエンドポイントとして動作します。

https://${ID}.execute-api.${Region}.amazonaws.com/Prod/sparql?query={URLエンコードされたSPARQLクエリ}&format={json(デフォルト) か xml}

パフォーマンス

検索速度

過去作成した、Node.js/Quadstore版Java/Apache Jena版と同様に検索速度を計測しました。

上記2記事では、「図書館及び関連組織のための国際標準識別子(ISIL)」試行版LODで公開されていた2020年版のRDFファイル(計160491トリプル)と以下の3種類のSPARQLクエリを使いました。
今回も同じ条件で行いました。

(1) トリプルを100件取得

select * where {?s ?p ?o} limit 100

(2) 全トリプル数を取得

select (count(*) as ?count) where {?s ?p ?o}

(3) filterを使って文字列の絞り込み

prefix schema: <http://schema.org/>
prefix org:   <http://www.w3.org/ns/org#>
prefix dbpedia: <http://dbpedia.org/ontology/>

select * where {
  ?uri dbpedia:originalName ?name;
  org:hasSite/org:siteAddress/schema:addressRegion ?pref.
  filter( regex(?pref, "東京") )
}
limit 10

結果は以下の通りとなりました。

実装別 (1) (2) (3)
Rust/Oxigraph 88ms 279ms 80ms
Node.js/Quadstore 220ms 12910ms 12910ms
Java/Apache Jena 141ms 579ms 104ms

Node.js/Quadstore と Java/Apache Jena の結果も比較のためにのせました。すべてにおいてRust/Oxigraph が最も早い結果となりました。

初回起動時間

AWS Lambda には所謂コールドスタートと呼ばれる、最初にプログラムを実行するときに初期化に時間がかかることがあるという問題があります。

Rust/Oxigraph と Node.js/Quadstore、Java/Apache Jena の3つでAWS Lambda で実行したときの初期所要時間(Init Duration)を調べました。

実装別 初期所要時間 メモリサイズ
Rust/Oxigraph 81.96 ms 1024MB
Node.js/Quadstore 383.82 ms 1024MB
Java/Apache Jena 3690.75 ms 2048MB
※ 初期所要時間は一定ではなく、場合によってはこれよりももっと時間がかかるときがあります。

ここでも Rust/Oxigraph が最も早いという結果になりました。

まとめ

これまでは、Java/Apache Jena版がコールドスタート問題に目をつぶれば一番安定したパフォーマンスが期待できましたが、Rust/Oxigraph版はコールドスタート問題もほぼ無視できるレベルで早く、15万トリプルレベルであれば検索速度も安定しているようなので十分使えると思いました。

ただ、100万トリプル以上のRDFファイルでも試したところ、フルスキャンがかかりそうなクエリ(例えば、filterとかorder byとか)の検索速度はRust/Oxigraph版(10秒以上)よりもJava/Apache Jena版(1秒未満)が早かったので、場合によっては Java/Apache Jena版 のほうが有利な場合がありそうです。

Oxigraph は2021年3月時点ではまだバージョンが若く、最適化はまだこれからとのことなので、今後に大きく期待が持てそうです。

Rust/Oxigraph版の検索は以下で試すことができます。
https://uedayou.net/isillod/

Discussion

kiaikiai

Issueにも書いたんですが,rdf/dump.ttl が大きいとLambda の250MB制限に引っかかってしまいました…😢

AWSもRustも全然わからんすぎて行き詰まってしまったので,どうにかして回避する方法がありそうであれば,なにとぞご教示いただきたく思います

uedayouuedayou

コメントありがとうございます。
Lambda内での解凍後サイズ(250MB)の制約を受けないようにするには、
sparql.dbディレクトリ(Oxigraphで生成したDBファイル)を/tmp 内(上限512MB)か、別途EFSをマウントして解凍する方法があると思います。

私のほうでこの方法を使って以下のエンドポイントを運用していますが、
https://uedayou.net/isillod/
トリプル数 167319 で sparql.dbディレクトリ内のファイルサイズは17MBでした。
以前、100万トリプルほどのRDFファイルをテストしたときもファイルサイズも確か50MB程度で、250MBの制約を受けるほどの大きなものにはならなかったのですが...

近々に運用中以外の比較的大きめのRDFファイルを試して、sparql.db 並びに、デプロイされるzipファイルのファイルサイズを調べて、GitHubの issue の方に投稿したいと思います。

ご参考になれば幸いです。

kiaikiai

丁寧な返信ありがとうございます.見直したところ,10411951 (約1000万)トリプルで625MBにまで膨れ上がっていることがわかりました ><

sparql.dbディレクトリ(Oxigraphで生成したDBファイル)を/tmp 内(上限512MB)か、別途EFSをマウントして解凍する方法があると思います。

前者だとまだ制限に引っかかるので,後者のEFSマウントの方法で自分でもできないか検討してみます!


あるいは,そもそものRDFデータ(turtle形式)に無駄がありすぎるのでしょうか……こういうデータを自作するのは初めての試みで,またあまり和文リソースには「オープンデータの(上手な)作り方」みたいなものは言及されておらず(泣) 手探りで独自に進めるのがだいぶつらい領域だなと感じているこの頃です

uedayouuedayou

お返事が遅れましたが、まず私がこのプログラムを作成するに至った経緯をお知らせしたいと思います。
個人的・コミュニティとして RDFデータを作成したり・公開したりすることが多いのですが、それらをSPARQLエンドポイント(FusekiやVirtuoso等)として公開・運用していると途中で動かなくなったり、Webサーバ内のリソースを無駄に使用したりと結構手がかかって悩んでいました。
何とか、安価でメンテナンスに手がかからない方法でSPARQLエンドポイントが公開できないかと試行錯誤した中の一つががこの記事の方法になります。
個人で作成するデータセットなので、大体中規模(100万トリプルぐらい)のデータセットがストレスなく検索ができる仕組みであれば十分だと思っていますし、Lambda内で動くのでエンドポイントが落ちる心配もなく、楽に運用できる仕組みかなと思っています。


さて、質問いただいた件ですが、どのように利用したいのかわからないのですが、1000万トリプルのデータを丸々使わないといけないのであればですが、ローカル環境内で動けばいいのであれば、Fusekiや Virtuoso などをローカル内で立てて、データ投入して使用するといいかなと思いました。

外部公開を目的にするのであれば、自身でWebサーバを構築するか、たぶん高額になると思いますがAmazon Neptureを利用する手があると思いました。

Qiita の GCP 無料範囲で Virutuoso を構築するという記事がありますので、参考になるかもしれません。
https://qiita.com/mirkohm/items/30991fec120541888acd

GitHub の issue にも書きましたが、外部公開するデータは最低限のデータに限って公開するというのもありかなと思います。

例えば、
https://uedayou.net/loa/
こちらの住所のデータについて、SPARQLエンドポイントを公開していますが、番地のデータまで入れるとかなりトリプル数が大きくなってしまうので、番地データはSPARQLエンドポイントには入れていません。
上記サイトがLODのサイトで、https://uedayou.net/loa/東京都千代田区丸の内.ttl のようにRDFデータしてもダウンロードできるようにしています。番地が欲しい人はこちらから取ってくださいというスタンスにしています。

個人的にはRDFにしておくと、将来的に他の人がデータを再利用しやすくなると思いますし、SPARQLエンドポイントでWeb API をデータ変換なしに構築できるのもメリットだと思います。
是非頑張って引き続きRDFでのデータ作成に挑戦してもらえればと思っています。

ご参考になれば幸いです。