Solrjクライアントにおいて、`solrj.impl.Http2SolrClient`の`ThreadLocal`を介したメモリリークが発生する可能性はありますか?

トピック作成者:ks-solruserml-bot (2024/08/24 22:08 投稿)
10
OpenOpen

(The bot translated the original post https://lists.apache.org/thread/n2rgq5l5jngbnpz8my9flk52zk7zg2xb into Japanese and reposted it under Apache License 2.0. The copyright of posted content is held by the original poster.)

8.Xから9.3のsolrjクライアントライブラリに切り替えようとしました。同時に、他のクライアントが非推奨とされていたため、Http2SolrClientに切り替えました。私たちは以下のようなパターンでクライアントを使用しています。

try (SolrClient client = createSolrClient()) {
    response = client.query(solrQuery);
    // レスポンスを使って何かをする
}

このパターンでは、クライアントが自動的にクローズされ、リソースがクリーンアップされるはずです。しかし、Tomcatのシャットダウン時に以下のようなエラーがログに表示されることに気付きました。

22-Aug-2023 11:22:04.645 SEVERE [Catalina-utility-1]
org.apache.catalina.loader.WebappClassLoaderBase.checkThreadLocalMapForLeaks
The web application [foo] created a ThreadLocal with key of type
[java.lang.ThreadLocal] (value [java.lang.ThreadLocal@727c756]) and a value
of type [org.eclipse.jetty.util.Pool.MonoEntry] (value [MonoEntry@2447031
{IDLE,pooled=RetainableByteBuffer@447004d6{DirectByteBuffer@6554e973[p=0,l=0,c=16384,r=0]={<<<>>>/16\"","p...ce":"15},r=0}}])
but failed to remove it when the web application was stopped. Threads are
going to be renewed over time to try and avoid a probable memory leak.

さらに悪いことに、別の環境では、たくさんのクエリが実行されたまま長時間動作していた結果、すべてのThreadLocalがクリーンアップされないためにOutOfMemoryエラーが発生することに気付きました。

ここで質問です。
1) これはリークでしょうか?
2) 私の使い方は間違っていますか?代わりに、すべての並行リクエストで共有されるSingleton(ish)インスタンスを作成して使用すべきでしょうか?
3) その他のアプローチはありますか?

-Tim

返信投稿者:ks-solruserml-bot (2024/08/24 22:08 投稿)

この問題の原因は、JettyのHttpClientクリーンアップコードの低レベルな部分(Http2SolrClient.close()時にSolrが適切にクリーンアップを行うべき部分)か、またはcreateSolrClient()メソッドの実装に関する何かが原因であり、Http2SolrClient.close()が基盤となるHttpClientの完全なクリーンアップを行えないエッジケースを引き起こしている可能性があります。

createSolrClient()の詳細を教えていただけますか?

追記の質問に答えると、SolrClientインスタンスはスレッドセーフで、複数の並行スレッドが並行リクエストを行うために再利用されるように設計されています。ただし、だからといって、あなたが使用している方法でメモリリークが発生するはずはありません。

-Hoss
http://www.lucidworks.com/

返信投稿者:ks-solruserml-bot (2024/08/24 22:09 投稿)

メッセージの最後にgetSolrClient()メソッドがあります。(私たちは言語ごとに異なるコアを使用しています。)また、非本番環境ではSolrの前にApache(HTTP)リバースプロキシを配置し、本番環境ではHAProxyでロードバランシングを行っているため、HTTP 1_1を使用しています。

先ほど(最初の質問の後に)テストを行った際に、close()にメモリリークは見られませんでした。問題はもっとコアの深い部分にあるようです。これに気づいたのは、私のアプリケーションインスタンスに対してab -n 5000000 -c 200やその他のランダムなリクエストを送信した後、アプリケーションを再起動した際、ログに3つのThreadLocalリークがこのウェブアプリに関連付けられ、MonoEntryの署名がログに記録されていたからです。別のアプリ(より小さいもの)をリファクタリングして、単一のSolrClientを共有するようにしたところ、同じ量のトラフィックを処理してもそのアプリではリークエラーメッセージが表示されませんでした。

public SolrClient getSolrClient(String locale) {
    Http2SolrClient.Builder builder = null;
    try {
        String baseUrl = "http://" + solrHost + ":" + solrPort + "/solr/" + getSolrCore(locale);
        builder = new Http2SolrClient.Builder(baseUrl);

        if (StringUtil.hasText(solrUser)) {
            builder = builder.withBasicAuthCredentials(solrUser, solrPassword);
        }

        builder.useHttp1_1(true);

        return builder.build();
    } catch (Exception e) {
        log_.fatal("error getting client", e);
    }

    return null;
}
返信投稿者:ks-solruserml-bot (2024/08/24 22:09 投稿)

このようなtry-with-resourcesアプローチは、SolrClientオブジェクトのclose()メソッドを実行するため、問題を解決するはずです。

エラーメッセージに出てくるクラスはJettyクラスです。これは問題がJettyにある可能性が高いですが、確実ではありません。

複数のコアがあるからといって複数のクライアントオブジェクトを作成する必要はありません。Solrにアクセスするためのホスト名とポートの組み合わせごとに一つのHttp2SolrClientオブジェクトがあれば十分で、それらはアプリケーションの起動時に作成し、終了時に閉じるだけでよいです。

Http2SolrClientとHttpSolrClientを比較して見つけたことの一つとして、後者(HttpSolrClient)は内部のHTTPクライアントスレッドをデーモンスレッドとして作成するので、Javaによって自動的にクリーンアップされますが、前者(Http2SolrClient)はそうではありません。以下のコードで、内部のJetty HTTPクライアントスレッドをデーモンスレッドとして作成するようにHttp2SolrClientの作成方法を変更できます。

final AtomicInteger scThreadCounter = new AtomicInteger();

// <snip>

final ExecutorService executorService = Executors.newFixedThreadPool(256, runnable -> {
    final Thread thread = Executors.defaultThreadFactory().newThread(runnable);
    thread.setDaemon(true); // スレッドをデーモンとしてマーク
    thread.setName("h2sc-" + scThreadCounter.incrementAndGet());
    return thread;
});

final Http2SolrClient.Builder clientBuilder = new Http2SolrClient.Builder(
    "http://localhost:8983/solr").withExecutor(executorService);
final Http2SolrClient client = clientBuilder.build();

最後に一つ付け加えると、HttpSolrClientはまだ使用可能です。これはバージョン10.0で削除されますが、9.xリリースにはまだ残っています。

ありがとうございました。

Shawn

返信投稿者:ks-solruserml-bot (2024/08/24 22:09 投稿)

クールですね - とりあえずは、HttpSolrClientに戻すか、単一のクライアントを使用するか(どちらをリファクタリングするかによりますが)します。

共有クライアントに関して唯一心配なのは、誰かが「うっかり」close()を呼んでしまった場合、クライアントが閉じられたかどうかを簡単に確認して、それを破棄して新しいクライアントを作成する方法が見つからないことです。(webアプリケーションの再起動に頼らずに)

-Tim

返信投稿者:ks-solruserml-bot (2024/08/24 22:09 投稿)

追加の情報 - ThreadLocalのリークは異なっており、新しいHttp2SolrClientを毎回作成・閉じることとは無関係のようです。共有のHttp2SolrClientを使用しても、QA環境で同じ問題が発生し、ThreadLocalが漏れていることに気付きました。現時点での修正策としては、HttpSolrClientに戻すのが良いようです。

クライアントはOpenJDK 11.0.17です。

-Tim

返信投稿者:ks-solruserml-bot (2024/08/24 22:09 投稿)

私の意見ですが、私は常にSolrクライアントをシングルトンとして使用してきました。一度だけインスタンス化して、その後ずっと再利用するべきです。

返信投稿者:ks-solruserml-bot (2024/08/24 22:10 投稿)

こんにちは、Tim。問題は解決しましたか?最終的にどう対処したのか興味があります。

--
Vincenzo D'Amore

返信投稿者:ks-solruserml-bot (2024/08/24 22:10 投稿)

HttpSolrClient に戻しました。それがリークを防いでいるようです。根本的な原因については、さらに調査する時間が取れていません。SolrClient を再利用するか、新しいものをインスタンス化するかに関わらずこの問題が発生するので、これが興味深いデータポイントになることを期待しています。しかし、再現するための「簡単な」テストを構築する時間が近い将来に確保できるかは不明です。

将来の対策としては、以下のいずれかを試してみるつもりです:

  • エンドポイントを変更して Http2 を使用する(builder.useHttp1_1(true) を無効化)
  • Http2Client に戻して、既存のアプリケーションサーバーにタイマーやロガーを追加し、ThreadLocals をカウントしてパターンを探す
  • スタンドアロンのクライアントを作成し、シングルスレッドで ThreadLocals を時間経過でカウントする
  • スタンドアロンのクライアントを作成し、新しい異なるスレッドで実行し、時折スレッドを再利用する

-Tim

返信投稿者:ks-solruserml-bot (2024/08/24 22:10 投稿)

こんにちは、Tim。お知らせいただきありがとうございます。私も同じ問題を経験しました。アプリケーションが不安定になり、クラッシュしました。
最初の実装は非常に似ており、CloudSolrClient とともに try-with-resources の Java ステートメントに大きく依存していました。
以前のメールで述べたように、私は最終的に Solr クライアントをシングルトンとして使用し、Solr インスタンス/コレクションごとに一つのインスタンスを再利用しています。

--
Vincenzo D'Amore

返信投稿者:ks-solruserml-bot (2024/08/24 22:10 投稿)

solrj 9.3 は jetty 10.0.15 上で構築されました。いくつか検索したところ、10.0.11 付近で似たようなリークがあり、修正されているようです。しかし、10.0.16 にも別のリーク修正があるようです。10.0.16 は5日前にタグ付けされたようです。リリースされたら、その実装に切り替えて、問題が修正されるかどうかを試してみようと思います。

-Tim

トピックへ返信するには、ログインが必要です。

KandaSearch

Copyright © 2006-2024 RONDHUIT Co, Ltd. All Rights Reserved.

投稿の削除

この投稿を削除します。よろしいですか?