検索体験を改善し「0件」を減らす(クエリ緩和+ランキング調整)

  • elasticsearch
    elasticsearch
2023/11/21に公開

※本稿で扱うデータや検索ロジックの例は、クライアント事例を一般化したものです。
説明のわかりやすさを優先し、Airbnb公開データを用いて再現しています。実際の顧客データやシステム構成とは異なります。

まとめ

観点 内容
課題 完全一致検索により、条件が少しずれただけで「0件」になりやすく、ユーザーが再検索を繰り返して離脱につながっていた。
対応 検索候補に点数をつけて、より「条件に近いもの」から順に表示する仕組みを導入。距離や一致度が少し違っても、なだらかに点数が下がるようなスコア調整(function_score + gauss)を適用。
運用 緩和ステップ/重み/閾値をダッシュボードで継続モニタリングし、A/Bテストによる副作用の検証と調整を継続。
結果・成果 「0件検索」を大幅に減らし、再検索回数の減少と滞在時間の向上を達成。
代替提案の受容率向上により、検索体験が分断されずスムーズに。
効果 手応えのある検索体験が実現し、在庫消化率・予約率の改善に寄与。今後のML化 / ベクトル検索への拡張余地も確保。

背景

事業文脈

宿泊施設の予約検索では、期間・人数・価格帯・位置情報・設備・即時予約可など、多くの条件が設定できる。
しかし、条件が厳しい場合に「該当 0 件」となるケースが一定数あり、ユーザーは細かく条件を変えながら再検索を繰り返していた。結果として、離脱率が高くなっていた。

現状の課題

  • 検索条件が 完全一致型 で、少し条件を外すだけで 0 件になりやすい。
  • 「条件を少し緩めた候補」や「代替提案(近隣日程・周辺エリア)」を自然に提示できず、体験が分断されていた。

ビジネス・非機能要件

  • ゼロ件検索の発生を継続的に減らし、検索結果の「手応え」を高める。
  • ノード負荷やコストを抑えつつ、正確な価格・在庫情報を維持する。
  • 検索改善が離脱や通報などの副作用を生まないか、定期的に検証する。

対応方針

基本方針

ゼロ件を減らしつつ、ユーザーの意図を外さない。
そのために、検索条件を段階的にゆるめていく多層ロジックと、近さを保つスコアリング、理由の可視化を組み合わせる。

ロジック設計

  1. 多段階の緩和ステップ
    完全一致 → 日付の微緩和 → エリア拡大 → 価格帯拡大 → 代替(人気・評価など)。
    上位で結果が出なければ次段へ進め、各ステップで「何をどれだけ緩めたか」をメタデータとして保持する。

  2. 近さを保つスコアリング
    Elasticsearch の script_scoregauss を用い、距離・価格差・希望日に対する近接度などを重み付け。
    「条件に近い候補」から自然に並ぶよう順位付けする。

  3. 理由の可視化(説明可能性)
    UI 上で「日付を±1日緩和」「価格を+10%拡大」など、緩和の内容を明示。
    ユーザー自身がトグルやスライダーで緩和幅を調整できるようにする。

  4. 性能と運用を両立
    緩和ステップはクエリを分離しつつキャッシュ再利用を設計。
    地図移動や絞り込み変更によるバーストにも耐えられるよう、クエリパターンと ES 負荷を継続的に最適化する。

検証フェーズ

検証の目的

検索ロジックの変更によって「ゼロ件を減らしつつ、ユーザーの意図に沿った結果が提示できているか」を確認する。
在庫や価格の正確性を前提としたうえで、UX・性能・副作用 の3点を評価対象とした。

検証観点

  1. 有効性
    各緩和ステップがどれだけ「ゼロ件検索」を回避できたかを測定。
    緩和タイプ別ヒット率と代替案クリック率をトラッキングし、ユーザーが代替提案を自然に受け入れているかを分析した。

  2. 性能
    検索頻度の高い操作(地図の移動や拡大縮小、日付変更など)の応答時間を計測。
    クエリ構造・キャッシュヒット率・Elasticsearch ノード負荷を可視化し、UXを損なわない範囲でレスポンスを安定化させた。

  3. 副作用
    緩和のしすぎで「期待外れな結果」が増えていないかを監視。
    離脱率・問い合わせ件数などの指標をA/Bテストで比較し、緩和幅やスコア設計のバランスを調整した。

改善サイクル

  • ダッシュボードで主要指標(ゼロ件率・クリック率・遅延・負荷)を常時モニタリング。
  • 月次で緩和ロジックとスコア関数を見直し、閾値やウェイトを微調整。
  • 検証結果を次回リリース計画に反映し、検索体験の連続改善を継続的に行った。

結果・成果

定量的成果

  • ゼロ件検索率を大幅に削減し、検索結果が常に何らかの候補を返せる状態を実現。
  • 緩和ステップの導入により、再検索回数が減少し、1セッションあたりの滞在時間が向上。
  • 緩和タイプ別ヒット率の分布から、日付・エリアの微調整が特に有効であることを確認。
  • 検索結果ページの応答速度も安定し、負荷増加に対してもスループットを維持。

定性的成果

  • 「少し条件を変えるとすぐ 0 件になる」という不満が解消され、検索体験の連続性が向上。
  • 条件を緩めた理由を明示することで、ユーザーの納得感と信頼感を確保。
  • 検索の手応えが増した結果、ブックマークや再訪問などのアクション率が向上。
  • ビジネス側でも、在庫消化率・予約率の改善に波及し、検索導線の価値が高まった。

内省・学び

  • ゼロ件を単純に減らすのではなく、「どの緩和が受け入れられるか」をデータで測る重要性を確認。
  • スコアリング設計とUX表現(理由の見せ方)が表裏一体であることを再認識。

工夫した点・苦労した点

性能と柔軟性の両立(バックエンド × フロントエンド協調)

緩和クエリを段階的に実行する構成は柔軟だが、処理数が増える分だけ ES ノード負荷やネットワーク往復が増えやすい。
バックエンド最適化(クエリ再利用・短期キャッシュ)に加え、フロントエンド側でも次の工夫で無駄なリクエストを抑制した。

  • デバウンス/スロットリング

    • 「地図の移動や拡大縮小」「スライダー調整」などの連続操作は、短い遅延でデバウンスし、ドラッグ中はスロットリングで送信頻度を制御。
    • 中間状態の連投を避け、ユーザーが意図した操作の“落ち着き”を待ってから検索を投げる。
  • 重複クエリの折りたたみ(コアレス)

    • 同一の正規化クエリ(例:丸めたビューポート、ソート/ページング除く主要条件)には結果を再利用。
    • ビューポートや条件をグリッド/バケット化して、ほぼ同じ問い合わせをまとめる。

副作用の把握と調整

緩和を強めた結果として離脱や通報が増えるケースも初期には見られた。
A/B テストの結果を定常的に分析し、「どこまで緩めると不満が出るか」を段階的に計測しながら設計を更新した。
データを見ながらチーム全体で意思決定する体制を整えたことが、長期的な安定改善につながった。

参考

Elasticsearchの検索クエリ

  • gauss
    • 中心から離れるほどなめらかに減点。距離・日付・数値の“近さ”に最適。
  • script_score
    • ビジネスルールを数式化(例:目標価格からの差、レビュー数/評価の加点、滞在可能人数の減点)。
  • function_score
    • クエリでヒットしたドキュメントに「追加スコア」を与える枠。複数関数を合成して“総合点”を作れる。
    • weightscore_mode で、“距離×価格×レビュー”のバランスを取る。

{
  "function_score": {
    "query": { "bool": { "filter": [ /* 基本条件 */ ] } },
    "functions": [
      {
        "gauss": {
          "location": {
            "origin": { "lat": 35.7058, "lon": 139.7514 },
            "scale": "1500m",
            "offset": "200m",
            "decay": 0.7
          }
        },
        "weight": 2.0
      },
      {
        "script_score": {
          "script": {
            "source": """
              double p = (doc.containsKey('price') && !doc['price'].empty) ? doc['price'].value : 0;
              double diff = Math.abs(p - params.target);
              // ±1万円ごとに 0.2 減点(調整可)
              return Math.max(0, 1.0 - (diff / 10000.0) * 0.2);
            """,
            "params": { "target": 15000 }
          }
        },
        "weight": 1.0
      },
      {
        "script_score": {
          "script": {
            "source": """
              // レビュー信頼度:件数と評価を合わせて0〜1へ
              double n = (doc.containsKey('number_of_reviews') && !doc['number_of_reviews'].empty) ? doc['number_of_reviews'].value : 0;
              double r = (doc.containsKey('review_scores_rating') && !doc['review_scores_rating'].empty) ? doc['review_scores_rating'].value : 0;
              // 件数は100件で頭打ち、評価は100点満点を想定
              double nScore = Math.min(n, 100) / 100.0;
              double rScore = r / 100.0;
              return 0.5 * nScore + 0.5 * rScore; // 係数は調整可
            """
          }
        },
        "weight": 0.8
      }
    ],
    "score_mode": "sum",
    "boost_mode": "sum"
  }
}
  • weight:各要素の比重(例:距離2.0、価格1.0、レビュー0.8)。
  • score_mode:関数同士の合成(sum は直感的、multiply は一要素が低いと全体が落ちる)。
  • boost_mode:base _score(テキスト一致)との合成。テキストを重視したければ multiplysum+boost を検討。

パフォーマンス改善

データベースや配信経路の最適化によって、システム全体の応答速度と安定性を向上。

開発生産性向上

品質保証の自動化とビルドパイプラインの改善により、継続的な開発速度を維持。