ザ・ベロシティ

そこそこのページ数だったが、夜なべして後半は一気に読んでしまった。

ザ・ベロシティ

ザ・ベロシティ

  • 作者: ディー・ジェイコブ,スーザン・バーグランド,ジェフ・コックス,スーザン・バーグランド/ジェフ・コックス,三本木亮
  • 出版社/メーカー: ダイヤモンド社
  • 発売日: 2010/11/12
  • メディア: 単行本(ソフトカバー)
  • 購入: 2人 クリック: 23回
  • この商品を含むブログ (14件) を見る

内容についてはザ・ゴールからつづく一連のやり方と大体同じで、ある問題が多い会社を舞台にTOCを活用して業績を上げるというおなじみかつ分かりやすいストーリー。楽しめたし、相変わらずいい読後感。

リーンだのTPSだのTOCだのみたいな概念について、いろんな人が共通認識として持っていると偉い楽だなあと思ってる。さらに、エンジニアだったらその工学的な側面では捉えていないと困るとも思う。「ボトルネック」という言葉が通じなかったりするとorzとなるし。

そのためにも、小難しく聞こえがちなものをこういう小説仕立てで見せるのはいい。もし高校野球の女子マネージャーがドラッカーの『マネジメント』を読んだら の偉いところもそこだよね。あれはもっとデフォルメが極端だけど、抽象的だったり分かりづらかったりするのを具体的なストーリーで見せるのは同じ。

そういうのが教養を形成する、と言うのかどうかはわからないけど、そういう方面の共通のコンテキストを作るのが会社という組織だけでは難しい側面もあるから、こういうのはもっと売れて欲しい。もしドラレベルとまでは言わないけど。

Lucene3.1でのQueryParserの挙動変更

デフォルトでPhraseQueryを生成しなくなったQueryParserに注意(3.1)を読んで意味が分からなかったので、プログラムを実行してみた

public class TestLucene2458 {
	static final Version V = Version.LUCENE_31;

	public static void main(String args[]) throws CorruptIndexException,
			IOException, ParseException {
		Directory directory = createIndex();
		IndexSearcher searcher = new IndexSearcher(directory);

		QueryParser qp = new QueryParser(V, "F", new CJKAnalyzer(V));
		Query query = qp.parse("あいえお");
		TopDocs docs = searcher.search(query, 5);
		System.out.println(docs.totalHits);
	}

	private static Directory createIndex() throws CorruptIndexException, IOException {
		Directory directory = new RAMDirectory();
		Analyzer analyzer = new CJKAnalyzer(V);
		MaxFieldLength maxlen = new MaxFieldLength(100);
		IndexWriter writer = new IndexWriter(directory, analyzer, true, maxlen);
		Document doc = new Document();
		doc.add(new Field("F", "あいうえお", Field.Store.YES, Field.Index.ANALYZED));
		writer.addDocument(doc);
		writer.close();
		return directory;
	}
}

ようするに、"あいうえお"というドキュメントにたいして"あいえお"で検索している。なんと、これがhitする。

1

		qp.setAutoGeneratePhraseQueries(true);

を入れると0になる。

ええーーーー。議論読んでないんで、良く意味が分からない。。

QueryElevationComponent

QueryElevationComponentを読んでみる。

http://www.jarvana.com/jarvana/view/org/apache/solr/solr-core/1.4.1/solr-core-1.4.1-sources.jar!/org/apache/solr/handler/component/QueryElevationComponent.java?format=ok

QueryElevationComponentの実装は以下のようになっている。

  • prepareメソッドでクエリとソートのルールを差し替えている
  • processメソッドはからっぽになっていてなにもしない
  • QueryElevationComponentは分散検索には対応していないため(?)その他のメソッドも実装されていない。

だから、QueryElevationComponentを理解するにはprepareメソッドを理解すれば十分だ。


prepareメソッドでやっていることを先にまとめると、以下のような感じ。

  1. パラメータの読み込み
  2. 設定(elevation.xml)に応じたboosterインスタンス生成
  3. クエリを操作する
  4. ソート順を操作する

prepareメソッドを細かく読んでいく。

  @Override
  public void prepare(ResponseBuilder rb) throws IOException
  {
    SolrQueryRequest req = rb.req;
    SolrParams params = req.getParams();
    // A runtime param can skip 
    if( !params.getBool( ENABLE, true ) ) {
      return;
    }

    // A runtime parameter can alter the config value for forceElevation
    boolean force = params.getBool( FORCE_ELEVATION, forceElevation );

ここまでがパラメータの読み込み。

    Query query = rb.getQuery();
    String qstr = rb.getQueryString();
    if( query == null || qstr == null) {
      return;
    }

    qstr = getAnalyzedQuery(qstr);
    IndexReader reader = req.getSearcher().getReader();
    ElevationObj booster = null;
    try {
      booster = getElevationMap( reader, req.getCore() ).get( qstr );
    }
    catch( Exception ex ) {
      throw new SolrException( SolrException.ErrorCode.SERVER_ERROR,
          "Error loading elevation", ex );      
    }

クエリの解析とキーワードに対応したboosterの取得をしている。
boosterの内容は以下の通り

  • 型はElevationObjクラス
    • Elevation別途インナークラスとして定義されている。
    • ElevationObjはコンストラクタだけのクラス

ElevationObjのコンストラクタは以下の定義で、引数は以下を意味する。

  • qstrはキーワード
  • elevateはexclude=trueに指定されていないコンテンツのIDのリスト
  • excludeはexclude=trueに指定されているコンテンツのIDのリスト
    ElevationObj( String qstr, List<String> elevate, List<String> exclude ) throws IOException
    {
      this.text = qstr;
      this.analyzed = getAnalyzedQuery( this.text );
      
      this.include = new BooleanQuery();
      this.include.setBoost( 0 );
      this.priority = new HashMap<String, Integer>();
      int max = elevate.size()+5;
      for( String id : elevate ) {
        TermQuery tq = new TermQuery( new Term( idField, id ) );
        include.add( tq, BooleanClause.Occur.SHOULD );
        this.priority.put( id, max-- );
      }

includeフィールドにはIDをキーにしたOR検索(SHOULD条件)のBooleanQueryが入っている。

      if( exclude == null || exclude.isEmpty() ) {
        this.exclude = null;
      }
      else {
        this.exclude = new BooleanClause[exclude.size()];
        for( int i=0; i<exclude.size(); i++ ) {
          TermQuery tq = new TermQuery( new Term( idField, exclude.get(i) ) );
          this.exclude[i] = new BooleanClause( tq, BooleanClause.Occur.MUST_NOT );
        }
      }

excludeフィールドにはIDが含まれないという条件のBooleanClauseのリストが入っている。

      this.comparatorSource = new ElevationComparatorSource(priority);
    }

最後に、comparatorSourceフィールドには独自のソート用比較クラスを入れている。

prepareメソッドに戻る。

    if( booster != null ) {
      // Change the query to insert forced documents
      BooleanQuery newq = new BooleanQuery( true );
      newq.add( query, BooleanClause.Occur.SHOULD );
      newq.add( booster.include, BooleanClause.Occur.SHOULD );
      if( booster.exclude != null ) {
        for( BooleanClause bq : booster.exclude ) {
          newq.add( bq );
        }
      }
      rb.setQuery( newq );

検索条件を以下のように組み替えている

  • booster.includeをクエリに追加し、elevationしたいコンテンツが検索結果に入るようにする
  • boolean.excludeもクエリに追加し、excludeしたいコンテンツが検索結果に入らないようにする
      // if the sort is 'score desc' use a custom sorting method to 
      // insert documents in their proper place 
      SortSpec sortSpec = rb.getSortSpec();
      if( sortSpec.getSort() == null ) {
        sortSpec.setSort( new Sort( new SortField[] {
            new SortField(idField, booster.comparatorSource, false ),
            new SortField(null, SortField.SCORE, false)
        }));
      }
      else {
        // Check if the sort is based on score
        boolean modify = false;
        SortField[] current = sortSpec.getSort().getSort();
        ArrayList<SortField> sorts = new ArrayList<SortField>( current.length + 1 );
        // Perhaps force it to always sort by score
        if( force && current[0].getType() != SortField.SCORE ) {
          sorts.add( new SortField(idField, booster.comparatorSource, false ) );
          modify = true;
        }
        for( SortField sf : current ) {
          if( sf.getType() == SortField.SCORE ) {
            sorts.add( new SortField(idField, booster.comparatorSource, sf.getReverse() ) );
            modify = true;
          }
          sorts.add( sf );
        }
        if( modify ) {
          sortSpec.setSort( new Sort( sorts.toArray( new SortField[sorts.size()] ) ) );
        }
      }

さらにソートの方法の指定にbooster.comparatorSourceを追加している。
これにより、include指定したコンテンツが必ず一番上に登場する。

最後は省略するが、debug=trueの場合の情報追加で終わる。


最初、QueryElevationQueryは検索結果の先頭に無理やりコンテンツを詰め込んだり、検索結果から無理やり削除したりしてinclude/excludeを実現しているのかと思っていたんだけど、ソースを読んだらクエリとソート順を検索前(prepareメソッド)に操作して、順番を変化させていた。

こんなにシンプルに検索結果の操作ができるんだなあと改めてSolrの柔軟性に感心した次第。

ラフィアン09年産出資馬

マイネアモーレ09に出資することにしました。

ラフィアンの紹介ページは以下。
http://www.ruffian.co.jp/pc/search?m=detail&type=1&code=109132

最初は1番のアイアイサクラ09に出資したかったのですが、抽選除外でこの馬をハズレ一位の形で買いました。

アグネスデジタル産駒は初めて持ちますけど、今まで持ったことのないタイプの馬ですね。ハーティーやモルゲンとも違うし、ミモーゼみたいな中距離馬とも違う。かといってダート馬かというとそんな感じもしないし、僕の経験からでは適性が良くわかんないですね。

ところで、出資馬追加でプロフィールの文言を変更したんですが、
http://d.hatena.ne.jp/Hayato/about

これで21頭目の出資なのね。10年以上やってればそんなに速いペースでもないですけども、もうそんなにやってたかなと言う気持ち。最近はクラッチが気を吐いてくれているくらいで3歳世代は惨憺たる状況なので、今年の2歳馬、1歳馬には本当に何とかして欲しいという勝手な期待をかけてます。

Apache Solrソースコードリーディング SearchHandler/SearchComponent

Solrのソースコードを読んでいる。
拡張ポイントが多いので、読むと結構工夫できそうなところがあって面白い。

記録に残すため、少しずつ書いてみようかと思う。

まず、エントリーポイントに近いSearchHandlerと、SearchComponent


SearchHandlerの前に、RequestHandlerとはなんぞやという話から。

solrconfig.xmlに設定されているDataImportHandlerとか、SearchHandlerなどは、/solr/以下のURLと対応づけられ、データを取り込むなり、検索するなりの機能が実装されている。基本的にRequestHandlerBaseというクラスを継承して実装される。

そのRequestHandlerBaseのサブクラスの一つに、検索を行う機能が実装されているo.a.s.handler.SearchHandlerがある。

(o.a.sというのはorg.apache.solrのパッケージ名のこと)

SearchHandlerは、検索結果を処理するために複数のSearchComponentを持っている。

その設定の部分がおそらく以下

public class SearchHandler extends RequestHandlerBase implements SolrCoreAware
{
  static final String INIT_COMPONENTS = "components";
  static final String INIT_FIRST_COMPONENTS = "first-components";
  static final String INIT_LAST_COMPONENTS = "last-components";

中略

  @SuppressWarnings("unchecked")
  public void inform(SolrCore core)
  {
    Object declaredComponents = initArgs.get(INIT_COMPONENTS);
    List<String> first = (List<String>) initArgs.get(INIT_FIRST_COMPONENTS);
    List<String> last  = (List<String>) initArgs.get(INIT_LAST_COMPONENTS);

    List<String> list = null;
    boolean makeDebugLast = true;
    if( declaredComponents == null ) {
      // Use the default component list
      list = getDefaultComponents();

      if( first != null ) {
        List<String> clist = first;
        clist.addAll( list );
        list = clist;
      }

      if( last != null ) {
        list.addAll( last );
      }
    }
    else {
      list = (List<String>)declaredComponents;
      if( first != null || last != null ) {
        throw new SolrException( SolrException.ErrorCode.SERVER_ERROR,
            "First/Last components only valid if you do not declare 'components'");
      }
      makeDebugLast = false;
    }

initArgsはRequestHandlerBaseのフィールドで、型はNamedList。おそらくここにsolrconfig.xmlで定義された内容が、XMLのname属性をキーにして格納される。だから、declaredComponentsにはname="components"以下の内容のListが入るのだろう。

クラス図チックに書くと、こんな感じ。

RequestHandlerBase---<|----SearchHandler----SearchComponent

RequestHandlerは、メイン処理をhandleRequestBodyメソッドに記述する。SearchHandlerは検索が役割だから当然検索をするが、実は検索処理自体はSearchHandlerには含まれない。SearchHandlerは定義されたSearchComponentを順に処理させるだけ。定義されたComponentの中に検索処理を行うQueryComponentが含まれることによって検索を行うことができる。

さっきのSearchHandlerのinformメソッドは、以下のように続く

    // Build the component list
    components = new ArrayList<SearchComponent>( list.size() );
    DebugComponent dbgCmp = null;
    for(String c : list){
      SearchComponent comp = core.getSearchComponent( c );
      if (comp instanceof DebugComponent && makeDebugLast == true){
        dbgCmp = (DebugComponent) comp;
      } else {
        components.add(comp);
        log.info("Adding  component:"+comp);
      }
    }

componentsにSearchComponentのリストを作っている

で、handleRequestBodyの中では、以下のように全コンポーネントの処理を順に行っている。

      for( SearchComponent c : components ) {
        c.prepare(rb);
      }
        for( SearchComponent c : components ) {
          c.process(rb);
        }

rbというのはResponseBuilderで、レスポンス内容を組み立てるために利用されるクラスだ。各ComponentはそこまでのResponseBuilderの内容を参考に処理を行い、結果を追加する。

たぶん、最後にhttpレスポンスを返すときにはこのクラスの内容をXMLなり、jsonなりにシリアライズして返しているのだろう。

component.processを呼び出すところは、rb.shards ==null,つまりdistributed searchをしているかどうかで処理を分岐しているんだけど、分散検索の方はちょっと呼んだだけじゃなにがどうなってんのか良くわかんなかったけど、componentのdistributedProcess,handleResponses,finishStageといったメソッドを呼び出してる。多分distributed searchに対応したsearchComponentを実装するにはこれらのメソッドを実装する必要があるんだと思う。

これを僕が調べたのは、特定キーワードの場合に特定のコンテンツを一番上に表示させたり検索結果から除外するために利用されるQueryElevationComponentを調べたかったから。これもSearchComponentの一種。


次はそのQueryElevationComponentを読んでみよう。

2010ラフィアン一次募集

1アイアイサクラの09で投函したら、必要ポイント数が12000ポイント超えてました。

1200万ですか。。。ポイント持ちの会員の方の10口出資がいくつかあった感じなのかなあ。この抽選は厳しい。

IA100

IA100 ?ユーザーエクスペリエンスデザインのための情報アーキテクチャ設計

IA100 ?ユーザーエクスペリエンスデザインのための情報アーキテクチャ設計

最初は、PDCAっぽいのかなと思って、

次は、アレグザンダーなんだ!と思ったけど

後半がどっちっぽくもなくてちょっとがっかりした。