NaviPlus Engineers' Blog

[Solr] CoreContainerの罠

編集長の佐藤(http://cocky.exblog.jp/)です。

NaviPlusサーチでは検索のコアコンポーネントとしてSolrを使ってますが、そんな中で最近ハマった点を一つ。

サーチで最近商品マスタの取り込みをマルチスレッド化したついでに、レコメンドとの連携のところの処理を変更した際の話です。
新しい処理の中で内部でEmbeddedSolrServerを使うようにしたんですが、ハマったのはその初期化の部分。

※以下のコードは全てSolr 4.2.xがベースです。Solr 4.4以降だとCoreContainerの初期化方法が変更になってるのでご注意ください。

最初は

CoreContainer.Initializer initializer = new CoreContainer.Initializer();
CoreContainer container = initializer.initialize();
container.load(solrHome, f);
solrServer = new EmbeddedSolrServer(container, aid);

みたいな感じでインスタンスを作ってたんですが、なぜか「メモリがリークする」「index作成中に他のcoreのreplicationが失敗する時がある」という問題が発生。なんでだろうと思ってCoreContainerのソースコードを見たら、

public static class Initializer {
  (中略)

    // core container instantiation
    public CoreContainer initialize() {
        CoreContainer cores = null;
        String solrHome = SolrResourceLoader.locateSolrHome();
        File fconf = new File(solrHome, containerConfigFilename == null ? "solr.xml"
            : containerConfigFilename);
        log.info("looking for solr.xml: " + fconf.getAbsolutePath());
        cores = new CoreContainer(solrHome);
      
        if (fconf.exists()) {
            cores.load(solrHome, fconf);
        } else {
            (中略)
        }
      
        containerConfigFilename = cores.getConfigFile().getName();
      
        return cores;
    }
}

ってことで、initialize()時に一度coreをロードしているため、呼び出し元でcontainer.load()すると二重ロードになってしまうことが発覚。

なのでcontainer.load()を外したんですが、それでも問題が完全には解消せず。
よくよく考えてみたら、initialize()中のload()だと、solr.xml内で「loadOnStartUp=”true”」かつ「transient=”false”」なcoreを全部読み込んでしまってることに気づき、余計なcoreを読み込まないようにすればいいのではと考えました。
というわけで最終的には、インスタンスの作成部分のコードを以下のようにして解決しました。

// CoreContainerを初期化(自分自身なんでinitialize()だけで十分)
CoreContainer.Initializer initializer = new CoreContainer.Initializer();
CoreContainer container = initializer.initialize();

// 今回使うもの以外のcoreをCoreContainerから外してcloseする(他コアへの干渉を防ぐ)
Collection<String> coreNames = container.getCoreNames();
for(String cn : coreNames) {
    if(!cn.equals(aid)) {
        SolrCore c = container.remove(cn);
        c.close();
    }
}

// EmbeddedSolrServerのインスタンスを作る
solrServer = new EmbeddedSolrServer(container, aid);

ポイントはcontainer.remove()した上で、返されたSolrCoreに対してclose()を実行する点でしょうか。
単にcontainer.remove()しただけではSolrCoreの参照数が減らないので、明示的にclose()を実行して参照数を減らしてやる必要があります。

こんな感じで最近はJavaと格闘する毎日です…。