Fessのクリックログ・お気に入りログの自動パージに対応する

FessではPurgeLogJobによって古い検索ログが定期的にパージされる仕組みがあるが、クリックログとお気に入りログは対象外で、OpenSearch上に無制限に蓄積され続けていた。今回、これらのログも検索ログと同じ保持日数でパージされるようにした。

背景

Fessは検索体験の分析のために、以下の3種類のログをOpenSearchに記録する。

  • 検索ログ(Search Log) — 検索クエリやヒット件数などの情報
  • クリックログ(Click Log) — 検索結果がクリックされた際の情報
  • お気に入りログ(Favorite Log) — 検索結果がお気に入り登録された際の情報

クリックログとお気に入りログは、検索ログを親データとする子データとして扱われる。つまり、親となる検索ログが削除されても、子であるクリックログ・お気に入りログは残り続けるため、親を参照できない孤立したデータが蓄積していく状態となっていた。

PurgeLogJobはこれまで検索ログ・ジョブログ・ユーザー情報ログ・クロール情報をパージしていたが、クリックログとお気に入りログは対象外だったため、長期運用しているFess環境ではインデックスが無制限に膨らむ問題があった。

変更内容

SearchLogServiceに削除メソッドを追加

SearchLogServiceに、クリックログとお気に入りログを日数指定で削除するメソッドを追加した。

public void deleteClickLogBefore(final int days) {
    clickLogBhv.queryDelete(cb -> {
        cb.query().setRequestedAt_LessEqual(
            systemHelper.getCurrentTimeAsLocalDateTime().minusDays(days));
    });
}

public void deleteFavoriteLogBefore(final int days) {
    favoriteLogBhv.queryDelete(cb -> {
        cb.query().setCreatedAt_LessEqual(
            systemHelper.getCurrentTimeAsLocalDateTime().minusDays(days));
    });
}

クリックログはrequestedAt(検索リクエスト日時)、お気に入りログはcreatedAt(作成日時)を基準に、指定日数より古いドキュメントを削除する。

PurgeLogJobにパージ処理を追加

PurgeLogJob.execute()の検索ログパージの直後に、クリックログとお気に入りログのパージ処理を追加した。

// purge click logs
try {
    final int days = ComponentUtil.getFessConfig().getPurgeSearchLogDay();
    if (days >= 0) {
        searchLogService.deleteClickLogBefore(days);
    } else {
        resultBuf.append("Skipped to purge click logs.\n");
    }
} catch (final Exception e) {
    logger.error("Failed to purge click logs.", e);
    resultBuf.append(e.getMessage()).append("\n");
}

お気に入りログ側もほぼ同じ実装となっている。例外が発生しても他のパージ処理に影響しないよう、それぞれ独立したtry-catchで囲んでいる。

保持日数は検索ログと共有

クリックログ・お気に入りログの保持日数には、専用の設定は新設せず、既存のpurge.searchlog.dayを共用する設計とした。

これは、前述のとおりクリックログとお気に入りログが検索ログの子データだからである。親の検索ログが消えた時点で参照元を失うため、検索ログと同じ保持期間でパージするのが自然な振る舞いとなる。

将来的に保持期間を個別に設定したくなった場合は、専用の設定プロパティを追加し、未指定時はpurge.searchlog.dayにフォールバックする形で拡張できる。

まとめ

Fess 15.6.0から、purge.searchlog.dayで指定した日数に基づいて、検索ログ・クリックログ・お気に入りログが同時にパージされるようになる。これまで長期運用でクリックログやお気に入りログが肥大化していた環境では、アップグレード後に古いデータが自動的に整理されるため、OpenSearchのストレージ負荷軽減が期待できる。

Fessの全般設定にSSO関連の設定項目を追加

Fessの管理画面にある「全般」設定ページで設定できる項目を大幅に増やしました。これまでsystem.propertiesファイルを直接編集する必要があった設定を、管理画面から変更できるようにしています。

背景

Fessでは、system.propertiesで多くの設定項目を管理していますが、すべてが管理画面から設定できるわけではありませんでした。設定を変更するためにサーバー上のファイルを直接編集する必要があり、運用の手間が発生していました。今回、管理画面の全般設定に45項目を追加し、ほぼすべての設定をブラウザから変更できるようにしました。

追加した設定項目

各セクションに追加した項目は以下の通りです。

システム設定

  • 検索ファイルプロキシ: ファイルプロキシの有効/無効
  • ブラウザロケール使用: 検索時にブラウザのロケールを使用するかどうか
  • SSOタイプ: SSO認証方式の選択(none、oic、saml、spnego、entraid)

クローラー設定

  • User Agent: クローリング時に使用するユーザーエージェント文字列

LDAP設定

  • Security Authentication: LDAP認証タイプ
  • Initial Context Factory: LDAPコンテキストファクトリのクラス名

通知設定

  • 詳細検索ページ通知: 詳細検索ページに表示する通知メッセージ
  • Slack Webhook URL: Slack通知用のWebhook URL
  • Google Chat Webhook URL: Google Chat通知用のWebhook URL

OpenID Connect設定(新規セクション)

クライアントID、クライアントシークレット、認証サーバーURL、トークンサーバーURL、リダイレクトURL、スコープ、ベースURL、デフォルトグループ、デフォルトロールの9項目を設定できます。

SAML設定(新規セクション)

SPベースURL、グループ属性名、ロール属性名、デフォルトグループ、デフォルトロールの5項目を設定できます。

SPNEGO設定(新規セクション)

Kerberos設定ファイルパス、ログイン設定ファイルパス、クライアント/サーバーモジュール名、事前認証のユーザー名/パスワード、Basic認証やNTLMプロンプトの有効/無効、localhost許可、委任許可、除外ディレクトリの12項目を設定できます。

Entra ID設定(新規セクション)

クライアントID、クライアントシークレット、テナントID、Authority URL、Reply URL、State TTL、デフォルトグループ、デフォルトロール、パーミッションフィールド、ドメインサービス利用の10項目を設定できます。

セキュリティへの配慮

クライアントシークレットやパスワードなどの機密情報は、管理画面上ではマスク表示(**********)されます。値が設定済みかどうかは確認でき、新しい値を入力して更新することも可能ですが、現在の値がそのまま表示されることはありません。

まとめ

今回の変更により、SSO関連の設定を含む45項目が管理画面から設定可能になりました。サーバー上のファイルを直接編集する必要がなくなるため、運用の効率化が期待できます。

関連リンク

Fess 15.6.0のリリース

Fess 15.6.0をリリースしました。今回もかなりの修正が入っていて、マイルストーンを見てもらうと分かりますが、ボリュームのあるリリースになりました。

主なトピックとしては、まずOpenSearch 3.6に対応しました。バンドルしているkopfプラグインも合わせて更新しています。OpenSearch側のアップデートに追従しつつ、Fessとしても安定して動くように整えています。

次に、複数インスタンスでの運用を想定した分散コーディネーションの仕組みを入れました。同じクラスタに対して複数のFessを動かすときに、スケジューラやメンテナンスタスクが競合しないようにするための基盤になります。

パスワード周りも見直しており、ローカルユーザーのパスワードハッシュをBCrypt(Spring Securityの{bcrypt}形式)に切り替えました。既存のSHA-256/512/MD5ハッシュもそのまま検証可能で、次回ログイン成功時にBCryptへ透過的に移行されます(app.password.upgrade.enabled=trueがデフォルト)。ただし、BCrypt導入前のバージョンにダウングレードすると{bcrypt}でエンコードされたパスワードは検証できなくなるので、ロールバックする場合は管理者パスワードのリセットを前提にしてください。

AI関連では、これまで「AI Chat」と呼んでいた機能を「AI Search Mode」にリネームして、RAGパイプラインも大幅に作り直しました。LLMプロバイダをプラグイン化して、OpenAIのreasoningモデルやGemini 3のthinking budgetにも対応しています。スマートサマリーモードや、会話履歴のターン単位でのパッキング、クエリの再生成によるフォールバック、検索フィルタUI、Markdownレンダリング、go URL経由でのソースへの遷移など、普段使いで気になりそうなところをかなり改善しました。エラーメッセージも構造化したLlmExceptionのエラーコードとしてUIまで届くようになっています。

運用面では、ログベースの通知機能を追加しました。ERRORやWARNのログを、SlackやGoogle Chat、メールに転送できるので、既存のアラート基盤と組み合わせて使えます。加えて、プロンプトインジェクション対策や、IndexExportJobのパストラバーサル・シンボリックリンク攻撃対策、EntraIdやSPNEGO周りのログマスクなど、セキュリティ面の強化もまとめて入れています。

管理画面まわりも地味に便利になっていて、system.propertiesの設定項目をAdminの一般設定からまとめて触れるようになったり、メンテナンスページに設定インデックスの再構築アクションを追加したり、クロール設定の複製アクションを追加したりしました。通知設定も「Notice」と「Notify」に分割して整理しています。

そのほか、Servlet APIを6.0から6.1に上げて、残っていたjavaxからjakartaへの移行も完了させました。クロール周りでもRankFusionProcessorのバウンダリ修正や、相対パスのURL解決のフォールバック強化などを入れています。

今回も、コード修正からリリースノート作成、多言語ドキュメントの更新までClaude Codeにかなり助けてもらいました。開発の回転が上がっているので、引き続き細かい改善を積み上げていこうと思います。ぜひ新しいFessを使ってみてください。