BH16.12/SuperFastRDFSearch
提供:TogoWiki
| 目次 | 
高速RDF検索サービス
背景
RDFデータの検索にはSPARQLが利用できるが、SPARQLエンドポイントの所在が所与である必要がある。 また、あるURIが与えられたときに、当該URIの示す概念に関する情報を取得するには、Linked Dataの原則に従って情報提供がなされていれば、HTTP GETすることで得られる。 しかし、当該URIを含むデータセットの提供主体以外の第三者によるデータセット内から当該URIが参照されているときに、その情報を取得することはできない。 そこで、URIやリテラルをキーとして、それを含むエンドポイントの一覧が得られるサービスを提供することで、これらの問題に対応する。
準備
以下のリソースを使用する。
- N-Triple形式のRDFデータセット
- Perl (RDF::Trineモジュールを利用)
- Sedueflex (超高速検索エンジン)
結果例
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__
				
				
	
