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__