BH16.12/SuperFastRDFSearch
提供:TogoWiki
目次 |
高速RDF検索サービス
背景
RDFデータの検索にはSPARQLが利用できるが、SPARQLエンドポイントの所在が所与である必要がある。 また、あるURIが与えられたときに、当該URIの示す概念に関する情報を取得するには、Linked Dataの原則に従って情報提供がなされていれば、HTTP GETすることで得られる。 しかし、当該URIを含むデータセットの提供主体以外の第三者によるデータセット内から当該URIが参照されているときに、その情報を取得することはできない。 そこで、URIやリテラルをキーとして、それを含むエンドポイントの一覧が得られるサービスを提供することで、これらの問題に対応する。
準備
以下のリソースを使用する。
- N-Triple形式のRDFデータセット
- Perl (RDF::Trineモジュールを利用) あるいは Java
- Sedueflex (超高速検索エンジン)
rdfファイル(RDF/XML形式、圧縮可)から索引付けに必要なファイルを出力する(ep)
java -jar TokenizeRDF.jar input.rdf.gz
結果例
AllieのRDFデータセットを対象として試験的に立ち上げ。
以下のように動作する。
$ echo 'Specific pathogen' | ./search_allieRDF.rb allieRDF O pLiteral Specific pathogen Free allieRDF O pLiteral Specific pathogen free allieRDF O pLiteral Specific pathogen-free allieRDF O pLiteral Specific pathogene-free $ echo 'Specific pathogen' | ./search_allieRDF.pl allieRDF O pLiteral Specific pathogen Free allieRDF O pLiteral Specific pathogen free allieRDF O pLiteral Specific pathogen-free allieRDF O pLiteral Specific pathogene-free
$ echo '2980434' | ./search_allieRDF.rb allieRDF S URI http://purl.org/allie/id/pair/2980434 allieRDF S URI http://togows.dbcls.jp/entry/ncbi-pubmed/22980434 allieRDF S URI http://togows.dbcls.jp/entry/ncbi-pubmed/2980434 allieRDF O URI http://purl.org/allie/id/pair/2980434 allieRDF O URI http://togows.dbcls.jp/entry/ncbi-pubmed/22980434 allieRDF O URI http://togows.dbcls.jp/entry/ncbi-pubmed/2980434 $ echo '2980434' | ./search_allieRDF.pl allieRDF S URI http://purl.org/allie/id/pair/2980434 allieRDF S URI http://togows.dbcls.jp/entry/ncbi-pubmed/22980434 allieRDF S URI http://togows.dbcls.jp/entry/ncbi-pubmed/2980434 allieRDF O URI http://purl.org/allie/id/pair/2980434 allieRDF O URI http://togows.dbcls.jp/entry/ncbi-pubmed/22980434 allieRDF O URI http://togows.dbcls.jp/entry/ncbi-pubmed/2980434
現時点ではPerlの低レベルファイルIOの問題(utf8周り)?がありそうです。回避方法は他の言語での実装か? → 問題ないことを確認( 12/28 )
$ grep 2980434 allie_rdf_so_unique.txt 0 1 http://purl.org/allie/id/pair/2980434 0 1 http://togows.dbcls.jp/entry/ncbi-pubmed/22980434 0 1 http://togows.dbcls.jp/entry/ncbi-pubmed/2980434 2 1 http://purl.org/allie/id/pair/2980434 2 1 http://togows.dbcls.jp/entry/ncbi-pubmed/22980434 2 1 http://togows.dbcls.jp/entry/ncbi-pubmed/2980434
結果はタブ区切りの行志向で、第一列はエンドポイントを示すラベルで、続いて、トリプルのどの位置であるかを示す文字(S:主語、P:述語、O:目的語、ただし今回は述語は検索対象外)、URIかリテラル(型付きリテラル/プレインリテラルの違いを含む)、そして実際にマッチしたRDFタームの順で表示される。
検索対象のRDFターム数は33,460,464で、「レセプター」をキーとして初めて検索した場合、結果は813あり、timeコマンドで得られる値は以下の通り。
0.235u 0.033s 0:00.28 92.8% 0+0k 0+0io 0pf+0w
なお、現時点ではUTF8へ未対応のため、一部文字化けが生じる場合がある。→ 生じていない模様( 12/28 )
クライアントプログラム
Ruby
require 'xmlrpc/client' positions = ["S", "P", "O"] nodetypes = ["Blank", "URI", "tLiteral", "pLiteral"] buffersize = 64*1024 port = 37201 relmax = 1000 client = XMLRPC::Client.new2("http://localhost:#{port}/") File.open("allie_rdf_so_unique4sedue.txt") do |file| STDIN.each do |line| h = Hash.new h["query"] = line.chomp h["k"] = 0 h["from"] = 0 h["to"] = 0 result = client.call2("flex_query",h) if (result[1]["error"] == "occured") then print "ERROR: ", result[1]["value"]["faultString"], "\n" elsif ( result[1]["total-hits"] > relmax ) then print "Too many results: ", result[1]["total-hits"], " (Max: ", relmax, ")\n" exit end h.delete("from") h.delete("to") result = client.call2("flex_query",h) result[1]["results"].each do |hit| pos = hit["position"] file.seek(pos) af = file.read(buffersize) eowpos = af.index("\t") pb = hit["position"] - buffersize if ( pb < 0 ) then pb = 0 end file.seek(pb) l = pos - pb bf = file.read(l) tabpos = bf.rindex(/[0-5]\t[0-5]\t/) bf = bf[tabpos..-1].sub(/^(\d)\t(\d)/) do |match| "#{positions[$1.to_i]}\t#{nodetypes[$2.to_i]}" end if (tabpos) then print hit["part-name"],"\t",bf,af[0..eowpos],"\n" else puts bf end end end end
Perl
use warnings; use strict; use Fatal qw/open/; use Frontier::Client; use Getopt::Std; use FindBin qw/$Bin/; my %ops; my $lookbehind = 64*1024; my $lookahead = 64*1024; getopt('km', \%ops); my $sedue_k = $ops{"k"} || 0; my $max_result = $ops{"m"} || 1000; my $file = $Bin. "/allie_rdf_so_unique4sedue.txt"; my @positions = qw/S P O/; my @nodetypes = qw/Blank URI tLiteral pLiteral/; my $lockvar; my $port = 37201; my $client = Frontier::Client->new(url => "http://localhost:$port/"); while(<>) { chomp; &sdreq(0, $_, $sedue_k, 1, $client, $max_result); } exit 0; sub sdreq { my ($idx, $_query, $_sedue_k, $_no_gap, $_req, $_relmax) = @_; eval { my $r = $_req->call('flex_query', {query => $_req->string($_query), k => $_sedue_k, 'no-gap' => $_no_gap, 'from' => 0, 'to' => 0, }); if( $r->{error} eq 'occured' ){ print "ERROR: ".$r->value->{'faultString'}."\n"; } elsif( $r->{"total-hits"} > $_relmax ){ print "Too many results: ", $r->{"total-hits"}, ". (Max: $_relmax)\n"; return 0; } $r = $_req->call('flex_query', {query => $_req->string($_query), k => $_sedue_k, 'no-gap' => $_no_gap, }); if( $r->{error} eq 'occured' ){ print "ERROR: ".$r->value->{'faultString'}."\n"; } else { &display( $r->{'results'} ); } }; if($@){ warn "SOMETHING WRONG? (Count:$idx) $@"; } return 0; } # Handling failure to search for tab hasn't be coded yet. sub display { my $res = shift; open F, $file or die "$file:$!\n"; foreach $a (@$res) { my %hash2 = %{$a}; my $qstr = $hash2{"query-based-string"}; my $opos = $hash2{"position"}; my $posforID = $opos - $lookbehind; if ($posforID < 0){ $posforID = 0; } my ($stc_be, $stc_ah) = ("", ""); unless (sysseek F, $posforID, 0){ my $em = "$file>SEEK_ERROR\n"; syswrite(STDOUT, $em, length($em)); } else { my $part = ""; if(sysread(F, $part, $opos - $posforID)){ $part =~ m/[0-5]\t[0-5]\t[^\t]*$/; $stc_be = substr($part, $-[0]); $stc_be =~ s/^(\d)\t(\d)/$positions[$1]."\t".$nodetypes[$2]/e; }else{ $stc_be = "xxx---xxx"; } } unless (sysseek F, $opos, 0){ my $em = "$file>SEEK_ERROR\n"; syswrite(STDOUT, $em, length($em)); } else { if(sysread(F, $stc_ah, $lookahead)){ $stc_ah = substr($stc_ah, 0, index($stc_ah, "\t")); $lockvar = join("\t", ($hash2{"part-name"}, $stc_be.$stc_ah))."\n"; syswrite(STDOUT, $lockvar, length($lockvar)); } } } close F; } __END__