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__