検索結果をページ分けする

ウェブ アプリケーションでは、多くの場合、データをページネーションしてユーザーに表示します。エンドユーザーには 1 ページの結果が提供され、ユーザーが次のページに移動すると、次のバッチの結果が取得されて表示されます。このページでは、Spanner で全文検索を実行するときに検索結果にページネーションを追加する方法について説明します。

ページネーション オプション

Spanner でページ分けされたクエリを実装するには、キーベースのページネーション(推奨)とオフセットベースのページネーションの 2 つの方法があります。

キーベースのページネーションは、リクエスト間で一貫した結果を確保しながら、より小さく管理しやすいチャンクで検索結果を取得する方法です。ページの最後の結果の一意の識別子(「キー」)が、次の結果セットを取得するための参照ポイントとして使用されます。

通常、Spanner ではキーベースのページネーションを使用することをおすすめします。オフセットベースのページネーションの方が実装は簡単ですが、2 つの大きな欠点があります。

  1. クエリ費用の増加: オフセットベースのページネーションでは、同じ結果が繰り返し取得されて破棄されるため、費用が増加し、パフォーマンスが低下します。
  2. 結果の不整合: ページネーションされたクエリでは、通常、各ページが異なる読み取りタイムスタンプで取得されます。たとえば、最初のページは午後 1 時のクエリから取得され、次のページは午後 1 時 10 分のクエリから取得される場合があります。つまり、クエリごとに検索結果が変化し、ページ間で結果が一致しなくなる可能性があります。

一方、キーベースのページ設定では、ページの最後の結果の一意の識別子(キー)を使用して、次の結果セットを取得します。これにより、基盤となるデータが変更されても、効率的な取得と一貫した結果が保証されます。

ページ結果の安定性を確保するため、アプリケーションは異なるページのすべてのクエリを同じタイムスタンプで発行できます。ただし、クエリがバージョン保持期間(デフォルトは 1 時間)を超えると、失敗することがあります。たとえば、version_gc が 1 時間で、エンドユーザーが午後 1 時に最初の結果を取得し、午後 3 時に [次へ] をクリックした場合、このエラーが発生します。

キーベースのページネーションを使用する

キーベースのページ分割では、前のページの最後のアイテムを記憶し、それを次のページのクエリの出発点として使用します。これを実現するには、クエリで ORDER BY 句で指定された列を返し、LIMIT を使用して行数を制限する必要があります。

キーベースのページネーションを機能させるには、クエリで結果を厳密な総順序で並べ替える必要があります。最も簡単な方法は、任意の合計順序を選択し、必要に応じてタイブレーカー列を追加することです。ほとんどの場合、合計順序は検索インデックスの並べ替え順序であり、列の一意の組み合わせはベーステーブルの主キーです。

Albums サンプル スキーマを使用して、最初のページのクエリは次のようになります。

SELECT AlbumId, ReleaseTimestamp
FROM Albums
WHERE SEARCH(AlbumTitle_Tokens, "fifth symphony")
ORDER BY ReleaseTimestamp DESC, AlbumId
LIMIT 10;

ReleaseTimestamp はキーではないため、AlbumId がタイブレーカーになります。ReleaseTimestamp の値が同じ 2 つの異なるアルバムが存在する場合があります。

再開するには、アプリケーションは同じクエリを再度実行しますが、前のページの結果を制限する WHERE 句を使用します。追加の条件では、キーの方向(昇順と降順)、タイブレーカー、NULL 値を指定できる列の NULL 値の順序を考慮する必要があります。

この例では、AlbumId が唯一のキー列(昇順)であり、NULL にできないため、条件は次のようになります。

SELECT AlbumId, ReleaseTimestamp
FROM Albums
WHERE (ReleaseTimestamp < @last_page_release_timestamp
       OR (ReleaseTimestamp = @last_page_release_timestamp
           AND AlbumId > @last_page_album_id))
      AND SEARCH(AlbumTitle_Tokens, @p)
ORDER BY ReleaseTimestamp DESC, AlbumId ASC
LIMIT @page_size;

Spanner は、この種の条件をseekableとして解釈します。つまり、除外するドキュメントのインデックスは読み取られません。この最適化により、キーベースのページ設定はオフセットベースのページ設定よりもはるかに効率的になります。

オフセットベースのページネーションを使用する

オフセットベースのページネーションでは、SQL クエリの LIMIT 句と OFFSET 句を使用してページをシミュレートします。LIMIT 値は、ページあたりの結果の数を示します。OFFSET 値は、最初のページでは 0、2 番目のページではページサイズ、3 番目のページではページサイズの 2 倍に設定されます。

たとえば、次のクエリは、ページサイズが 50 の 3 ページ目を取得します。

SELECT AlbumId
FROM Albums
WHERE SEARCH(AlbumTitle_Tokens, "fifth symphony")
ORDER BY ReleaseTimestamp DESC, AlbumId
LIMIT 50 OFFSET 100;

使用上の注意:

  • ページ間で並べ替えが一致するように、ORDER BY 句を使用することを強くおすすめします。
  • 本番環境のクエリでは、定数ではなくクエリ パラメータを使用して LIMITOFFSET を指定し、クエリ キャッシュをより効率的にします。詳細については、クエリ パラメータをご覧ください。

次のステップ